feat: route search through QML shell
This commit is contained in:
@@ -44,7 +44,7 @@
|
|||||||
| 2 | 用户首页 | 收藏 `views/user/FavoritesView` | favorites-agent | done | MediaCard/List、收藏模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;分类、详情、文件夹、人物跳转及返回 |
|
| 2 | 用户首页 | 收藏 `views/user/FavoritesView` | favorites-agent | done | MediaCard/List、收藏模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;分类、详情、文件夹、人物跳转及返回 |
|
||||||
| 2 | 用户首页 | 分类 `views/user/CategoryView` | category-agent | done | MediaCard/List、筛选/分页模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;分类切换、分页、空态、返回状态保持 |
|
| 2 | 用户首页 | 分类 `views/user/CategoryView` | category-agent | done | MediaCard/List、筛选/分页模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;分类切换、分页、空态、返回状态保持 |
|
||||||
| 2 | 媒体浏览 | 媒体库/人物/过滤结果(共用 `views/media/LibraryView`) | library-agent | done | MediaCard/Grid、排序筛选菜单、分页模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;海报/平铺视图、排序筛选、滚动续载、人物页 |
|
| 2 | 媒体浏览 | 媒体库/人物/过滤结果(共用 `views/media/LibraryView`) | library-agent | done | MediaCard/Grid、排序筛选菜单、分页模型 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;海报/平铺视图、排序筛选、滚动续载、人物页 |
|
||||||
| 2 | 应用壳 | `MainWindow` 标题栏、全局搜索、HomeView 侧栏与路由栈 | core | in-progress | `NavigationController`、单引擎 `Router.qml`、Home/Favorites singleton 已接入;待 Search/浏览 route、侧栏与标题导航 | `cmake --build cmake-build-debug --target qEmbyNavigationTest qEmbyApp_qmllint qEmbyApp -j2 && ./cmake-build-debug/bin/qEmbyNavigationTest`;登录→首页→收藏→详情→返回→设置→退出全链路,检查窗口按钮与托盘 |
|
| 2 | 应用壳 | `MainWindow` 标题栏、全局搜索、HomeView 侧栏与路由栈 | core | in-progress | `NavigationController`、单引擎 `Router.qml`、Home/Favorites/Search 已接入;待 Category/Library 等浏览 route、侧栏与标题导航 | `cmake --build cmake-build-debug --target qEmbyNavigationTest qEmbyApp_qmllint qEmbyApp -j2 && ./cmake-build-debug/bin/qEmbyNavigationTest`;登录→首页→搜索→返回→收藏→详情→返回→设置→退出全链路,检查窗口按钮与托盘 |
|
||||||
| 3 | 媒体详情 | 详情 `views/media/DetailView` | detail-agent | done | MediaCard、Flow/Tag、动作菜单、编辑/识别弹窗桥接 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;电影/剧集/人物各一项,播放、收藏、版本选择、编辑入口 |
|
| 3 | 媒体详情 | 详情 `views/media/DetailView` | detail-agent | done | MediaCard、Flow/Tag、动作菜单、编辑/识别弹窗桥接 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;电影/剧集/人物各一项,播放、收藏、版本选择、编辑入口 |
|
||||||
| 3 | 媒体详情 | 季 `views/media/SeasonView` | season-agent | done | Episode 列表模型、MediaCard、播放动作 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;切季、剧集续播/播放、已播状态、返回 |
|
| 3 | 媒体详情 | 季 `views/media/SeasonView` | season-agent | done | Episode 列表模型、MediaCard、播放动作 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;切季、剧集续播/播放、已播状态、返回 |
|
||||||
| 3 | 管理 | 媒体库 `PageLibraries` | admin-libraries-agent | done | 管理卡片/Grid、拖拽排序、Library CRUD 对话框 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;新增/编辑/删除/扫描/取消扫描/排序 |
|
| 3 | 管理 | 媒体库 `PageLibraries` | admin-libraries-agent | done | 管理卡片/Grid、拖拽排序、Library CRUD 对话框 | `cmake --build cmake-build-debug --target qEmbyApp_qmllint qEmbyApp -j2`;新增/编辑/删除/扫描/取消扫描/排序 |
|
||||||
@@ -65,7 +65,9 @@
|
|||||||
4. **迁移复杂业务页**:详情/季与六个剩余管理页。先提取 C++ ViewModel/`QAbstractListModel`,QML 不直接持有 service 协程或业务缓存。
|
4. **迁移复杂业务页**:详情/季与六个剩余管理页。先提取 C++ ViewModel/`QAbstractListModel`,QML 不直接持有 service 协程或业务缓存。
|
||||||
5. **最后处理播放与长尾弹窗**:播放器涉及 libmpv/OpenGL/原生窗口和输入焦点,是最高风险项。平台集成可以保留 C++,目标应是“QML 表现层 + C++ 原生能力”,而不是消灭所有 QWidget/C++。
|
5. **最后处理播放与长尾弹窗**:播放器涉及 libmpv/OpenGL/原生窗口和输入焦点,是最高风险项。平台集成可以保留 C++,目标应是“QML 表现层 + C++ 原生能力”,而不是消灭所有 QWidget/C++。
|
||||||
|
|
||||||
当前开发批次(2026-07-15):导航 Phase 0/1 已落地,Phase 2 首批完成。新增强类型 `NavigationRoute`、`NavigationController` 和轻量契约测试;Widgets 页面统一先转换为规范 route,再进入旧页面栈。`QuickRouterHost` 以单个 QML engine 承载 `Router.qml`,Home/Favorites 共享该 host 与图片 provider,并由 `HomeQuickRouterView` 协调 ViewModel 生命周期和 BaseView 媒体动作。其他 route 暂时保持旧 Widgets host,切入时 Router 保留底层页面且暂停生命周期,避免过渡闪屏。可用 `QEMBY_USE_LEGACY_HOME_PAGES=1` 临时回退旧 Home/Favorites 双 host。下一批先接 Search,再处理 Category/Library 的参数加载与 LRU ViewModel 所有权。
|
当前开发批次(2026-07-15):导航 Phase 0/1 已落地,Phase 2 首批完成。新增强类型 `NavigationRoute`、`NavigationController` 和轻量契约测试;Widgets 页面统一先转换为规范 route,再进入旧页面栈。`QuickRouterHost` 以单个 QML engine 承载 `Router.qml`,Home/Favorites 共享该 host 与图片 provider,并由 `HomeQuickRouterView` 协调 ViewModel 生命周期和 BaseView 媒体动作。其他 route 暂时保持旧 Widgets host,切入时 Router 保留底层页面且暂停生命周期,避免过渡闪屏。可用 `QEMBY_USE_LEGACY_HOME_PAGES=1` 临时回退旧 Home/Favorites 双 host。
|
||||||
|
|
||||||
|
Phase 2 第二批(2026-07-15):Search 已加入根 Router。`HomeView::triggerSearch()` 现在生成规范 `search` route;同一 Quick host 内的 Home/Favorites/Search 切换只更新 Controller/Loader,进入 legacy 详情页时仍保存兼容历史。`SearchViewModel` 新增 active/generation 生命周期,隐藏或离开搜索时淘汰在途结果,返回时仅按需恢复;旧 `SearchView` 默认 active 行为保持兼容。下一批进入 Category/Library/Person/Filtered,重点解决带参数 route 的 ViewModel 实例所有权与滚动状态。
|
||||||
|
|
||||||
阶段门禁:每阶段至少通过 QML 静态检查、Debug 构建、明暗主题、窗口缩放、键盘焦点、页面反复进入退出、网络失败/空数据检查;依赖真实服务器的行为必须用 Emby/Jellyfin 各冒烟一次。
|
阶段门禁:每阶段至少通过 QML 静态检查、Debug 构建、明暗主题、窗口缩放、键盘焦点、页面反复进入退出、网络失败/空数据检查;依赖真实服务器的行为必须用 Emby/Jellyfin 各冒烟一次。
|
||||||
|
|
||||||
|
|||||||
@@ -402,3 +402,10 @@ qemby://search?q=<percent-encoded-query>
|
|||||||
- 播放、收藏、详情/媒体库/人物/分类导航、通用菜单及 Dashboard 管理媒体库专用菜单继续复用 `BaseView`/C++ action bridge,避免页面迁移造成功能回退。
|
- 播放、收藏、详情/媒体库/人物/分类导航、通用菜单及 Dashboard 管理媒体库专用菜单继续复用 `BaseView`/C++ action bridge,避免页面迁移造成功能回退。
|
||||||
- Home/Favorites 单引擎路径默认开启;设置环境变量 `QEMBY_USE_LEGACY_HOME_PAGES=1` 可回退旧双 `QuickPageHost`,用于一个发布周期内的兼容验证。
|
- Home/Favorites 单引擎路径默认开启;设置环境变量 `QEMBY_USE_LEGACY_HOME_PAGES=1` 可回退旧双 `QuickPageHost`,用于一个发布周期内的兼容验证。
|
||||||
- 首批无服务器 offscreen 冒烟和 legacy fallback 冒烟均可稳定运行至测试超时,未出现 QML runtime error。期间发现并修复 `AdminTranscodingPage.DoubleField` 覆盖 `QQuickItem` final `bottom/top` 属性的问题。
|
- 首批无服务器 offscreen 冒烟和 legacy fallback 冒烟均可稳定运行至测试超时,未出现 QML runtime error。期间发现并修复 `AdminTranscodingPage.DoubleField` 覆盖 `QQuickItem` final `bottom/top` 属性的问题。
|
||||||
|
|
||||||
|
2026-07-15 完成 Phase 2 第二批 Search:
|
||||||
|
|
||||||
|
- `Router.qml` 增加 Search 静态组件;Search 与 Home/Favorites 共享根 engine、图片 provider 和 action bridge。
|
||||||
|
- `HomeView::triggerSearch()` 改为 push 规范 search route;同一 Router widget 内切换也会记录历史,因此 Home → Search → Back 可以恢复 Home,Search → legacy Detail → Back 可以恢复原搜索。
|
||||||
|
- `SearchViewModel` 增加 active/generation 取消语义;inactive 时只记录待搜索 query,重新激活后按需恢复,避免隐藏页面回写陈旧结果。
|
||||||
|
- 导航契约测试新增 search key 和返回用例;默认 Router 与 legacy fallback 的无服务器 offscreen 冒烟均无 runtime 输出。
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Item {
|
|||||||
property var navigationController: navigation
|
property var navigationController: navigation
|
||||||
// Keep this allow-list deliberately small until each page's context
|
// Keep this allow-list deliberately small until each page's context
|
||||||
// ViewModel has been moved to the root Quick host.
|
// ViewModel has been moved to the root Quick host.
|
||||||
property var enabledRoutes: ["home", "favorites"]
|
property var enabledRoutes: ["home", "favorites", "search"]
|
||||||
property var currentRoute: navigationController
|
property var currentRoute: navigationController
|
||||||
? navigationController.currentRoute
|
? navigationController.currentRoute
|
||||||
: ({ "name": "navigationError",
|
: ({ "name": "navigationError",
|
||||||
@@ -69,7 +69,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function routeImplemented(name) {
|
function routeImplemented(name) {
|
||||||
return name === "home" || name === "favorites";
|
return name === "home" || name === "favorites" || name === "search";
|
||||||
}
|
}
|
||||||
|
|
||||||
function routeKnown(name) {
|
function routeKnown(name) {
|
||||||
@@ -102,6 +102,7 @@ Item {
|
|||||||
switch (name) {
|
switch (name) {
|
||||||
case "home": return homeComponent;
|
case "home": return homeComponent;
|
||||||
case "favorites": return favoritesComponent;
|
case "favorites": return favoritesComponent;
|
||||||
|
case "search": return searchComponent;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -219,6 +220,7 @@ Item {
|
|||||||
|
|
||||||
Component { id: homeComponent; DashboardPage { anchors.fill: parent } }
|
Component { id: homeComponent; DashboardPage { anchors.fill: parent } }
|
||||||
Component { id: favoritesComponent; FavoritesPage { anchors.fill: parent } }
|
Component { id: favoritesComponent; FavoritesPage { anchors.fill: parent } }
|
||||||
|
Component { id: searchComponent; SearchPage { anchors.fill: parent } }
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: recoveryComponent
|
id: recoveryComponent
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ int main(int argc, char **argv)
|
|||||||
|
|
||||||
navigation.enterAuthenticatedSession(false);
|
navigation.enterAuthenticatedSession(false);
|
||||||
ok &= check(navigation.currentRoute().value("name") == "home", "session starts at home");
|
ok &= check(navigation.currentRoute().value("name") == "home", "session starts at home");
|
||||||
|
ok &= check(navigation.push({{"name", "search"},
|
||||||
|
{"params", QVariantMap{{"query", "star wars"}}}}),
|
||||||
|
"accepts a valid search route");
|
||||||
|
ok &= check(navigation.currentRoute().value("key") == "search:star%20wars",
|
||||||
|
"search query participates in the route key");
|
||||||
|
ok &= check(navigation.back() && navigation.currentRoute().value("name") == "home",
|
||||||
|
"back returns from search to its session root");
|
||||||
ok &= check(navigation.push({{"name", "library"},
|
ok &= check(navigation.push({{"name", "library"},
|
||||||
{"params", QVariantMap{{"libraryId", "lib/1"},
|
{"params", QVariantMap{{"libraryId", "lib/1"},
|
||||||
{"title", "Movies"}}}}),
|
{"title", "Movies"}}}}),
|
||||||
|
|||||||
@@ -136,6 +136,28 @@ void SearchViewModel::setCurrentTab(int tab) {
|
|||||||
launchCurrentSearch();
|
launchCurrentSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SearchViewModel::setActive(bool active) {
|
||||||
|
if (m_active == active)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_active = active;
|
||||||
|
if (!active) {
|
||||||
|
++m_searchGeneration;
|
||||||
|
if (m_loading)
|
||||||
|
m_retryOnActivate = true;
|
||||||
|
if (m_loading) {
|
||||||
|
m_loading = false;
|
||||||
|
emit stateChanged();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_retryOnActivate && !m_query.isEmpty()) {
|
||||||
|
m_retryOnActivate = false;
|
||||||
|
launchCurrentSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QString SearchViewModel::includeItemTypes(int tab) {
|
QString SearchViewModel::includeItemTypes(int tab) {
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -155,6 +177,10 @@ QString SearchViewModel::includeItemTypes(int tab) {
|
|||||||
void SearchViewModel::launchCurrentSearch() {
|
void SearchViewModel::launchCurrentSearch() {
|
||||||
if (m_query.isEmpty())
|
if (m_query.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
if (!m_active) {
|
||||||
|
m_retryOnActivate = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
QCoro::connect(search(m_query), this, []() {});
|
QCoro::connect(search(m_query), this, []() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,7 +189,16 @@ QCoro::Task<void> SearchViewModel::search(QString query) {
|
|||||||
if (query.isEmpty() || !m_core || !m_core->mediaService())
|
if (query.isEmpty() || !m_core || !m_core->mediaService())
|
||||||
co_return;
|
co_return;
|
||||||
|
|
||||||
|
const bool queryChanged = m_query != query;
|
||||||
m_query = query;
|
m_query = query;
|
||||||
|
if (!m_active) {
|
||||||
|
m_retryOnActivate = true;
|
||||||
|
if (queryChanged)
|
||||||
|
emit stateChanged();
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_retryOnActivate = false;
|
||||||
const quint64 generation = ++m_searchGeneration;
|
const quint64 generation = ++m_searchGeneration;
|
||||||
const QString types = includeItemTypes(m_currentTab);
|
const QString types = includeItemTypes(m_currentTab);
|
||||||
m_loading = true;
|
m_loading = true;
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ public:
|
|||||||
|
|
||||||
void setTileView(bool tileView);
|
void setTileView(bool tileView);
|
||||||
void setCurrentTab(int tab);
|
void setCurrentTab(int tab);
|
||||||
|
void setActive(bool active);
|
||||||
QCoro::Task<void> search(QString query);
|
QCoro::Task<void> search(QString query);
|
||||||
void updateItem(const MediaItem &item);
|
void updateItem(const MediaItem &item);
|
||||||
|
|
||||||
@@ -92,6 +93,8 @@ private:
|
|||||||
QString m_statsText;
|
QString m_statsText;
|
||||||
QString m_errorText;
|
QString m_errorText;
|
||||||
bool m_loading = false;
|
bool m_loading = false;
|
||||||
|
bool m_active = true;
|
||||||
|
bool m_retryOnActivate = false;
|
||||||
bool m_tileView = false;
|
bool m_tileView = false;
|
||||||
int m_currentTab = 0;
|
int m_currentTab = 0;
|
||||||
quint64 m_searchGeneration = 0;
|
quint64 m_searchGeneration = 0;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "../../components/moderntoast.h"
|
#include "../../components/moderntoast.h"
|
||||||
#include "../../quick/navigationcontroller.h"
|
#include "../../quick/navigationcontroller.h"
|
||||||
#include "../../quick/quickrouterhost.h"
|
#include "../../quick/quickrouterhost.h"
|
||||||
|
#include "../search/searchviewmodel.h"
|
||||||
|
|
||||||
#include <qembycore.h>
|
#include <qembycore.h>
|
||||||
#include <services/admin/adminservice.h>
|
#include <services/admin/adminservice.h>
|
||||||
@@ -37,11 +38,14 @@ HomeQuickRouterView::HomeQuickRouterView(QEmbyCore *core,
|
|||||||
m_host = new QuickRouterHost(core, navigation, this);
|
m_host = new QuickRouterHost(core, navigation, this);
|
||||||
m_dashboardViewModel = new UserDashboardViewModel(core, this);
|
m_dashboardViewModel = new UserDashboardViewModel(core, this);
|
||||||
m_favoritesViewModel = new FavoritesViewModel(core, this);
|
m_favoritesViewModel = new FavoritesViewModel(core, this);
|
||||||
|
m_searchViewModel = new SearchViewModel(core, this);
|
||||||
|
|
||||||
m_host->setContextProperty(QStringLiteral("dashboardPage"),
|
m_host->setContextProperty(QStringLiteral("dashboardPage"),
|
||||||
m_dashboardViewModel);
|
m_dashboardViewModel);
|
||||||
m_host->setContextProperty(QStringLiteral("favoritesPage"),
|
m_host->setContextProperty(QStringLiteral("favoritesPage"),
|
||||||
m_favoritesViewModel);
|
m_favoritesViewModel);
|
||||||
|
m_host->setContextProperty(QStringLiteral("searchPage"),
|
||||||
|
m_searchViewModel);
|
||||||
m_host->loadRouter();
|
m_host->loadRouter();
|
||||||
layout->addWidget(m_host);
|
layout->addWidget(m_host);
|
||||||
|
|
||||||
@@ -96,6 +100,18 @@ HomeQuickRouterView::HomeQuickRouterView(QEmbyCore *core,
|
|||||||
{
|
{
|
||||||
handleMoreMenuRequested(item, QCursor::pos());
|
handleMoreMenuRequested(item, QCursor::pos());
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(m_searchViewModel, &SearchViewModel::itemActivated,
|
||||||
|
this, &HomeQuickRouterView::forwardItemActivation);
|
||||||
|
connect(m_searchViewModel, &SearchViewModel::playRequested,
|
||||||
|
this, &BaseView::handlePlayRequested);
|
||||||
|
connect(m_searchViewModel, &SearchViewModel::favoriteRequested,
|
||||||
|
this, &BaseView::handleFavoriteRequested);
|
||||||
|
connect(m_searchViewModel, &SearchViewModel::moreRequested,
|
||||||
|
this, [this](const MediaItem &item, const QPoint &localPos)
|
||||||
|
{
|
||||||
|
handleMoreMenuRequested(item, m_host->mapToGlobal(localPos));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeQuickRouterView::refreshHome()
|
void HomeQuickRouterView::refreshHome()
|
||||||
@@ -125,6 +141,7 @@ void HomeQuickRouterView::onMediaItemUpdated(const MediaItem &item)
|
|||||||
{
|
{
|
||||||
m_dashboardViewModel->updateItem(item);
|
m_dashboardViewModel->updateItem(item);
|
||||||
m_favoritesViewModel->updateItem(item);
|
m_favoritesViewModel->updateItem(item);
|
||||||
|
m_searchViewModel->updateItem(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeQuickRouterView::onMediaItemRemoved(const QString &itemId)
|
void HomeQuickRouterView::onMediaItemRemoved(const QString &itemId)
|
||||||
@@ -199,21 +216,50 @@ void HomeQuickRouterView::dispatchCardContextMenuRequest(
|
|||||||
|
|
||||||
void HomeQuickRouterView::updateRouteActivity()
|
void HomeQuickRouterView::updateRouteActivity()
|
||||||
{
|
{
|
||||||
const QString routeName = m_navigation
|
const QVariantMap route = m_navigation
|
||||||
? m_navigation->currentRoute().value(QStringLiteral("name")).toString()
|
? m_navigation->currentRoute()
|
||||||
: QString();
|
: QVariantMap{};
|
||||||
|
const QString routeName = route.value(QStringLiteral("name")).toString();
|
||||||
const bool hostActive = m_host && m_host->isActive();
|
const bool hostActive = m_host && m_host->isActive();
|
||||||
|
|
||||||
m_dashboardViewModel->setActive(hostActive &&
|
m_dashboardViewModel->setActive(hostActive &&
|
||||||
routeName == QLatin1String("home"));
|
routeName == QLatin1String("home"));
|
||||||
m_favoritesViewModel->setActive(hostActive &&
|
m_favoritesViewModel->setActive(hostActive &&
|
||||||
routeName == QLatin1String("favorites"));
|
routeName == QLatin1String("favorites"));
|
||||||
|
|
||||||
|
const bool searchActive = hostActive &&
|
||||||
|
routeName == QLatin1String("search");
|
||||||
|
if (!searchActive)
|
||||||
|
{
|
||||||
|
m_searchViewModel->setActive(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QVariantMap params = route.value(QStringLiteral("params")).toMap();
|
||||||
|
const QString query = params.value(QStringLiteral("query"))
|
||||||
|
.toString().trimmed();
|
||||||
|
const QString routeKey = route.value(QStringLiteral("key")).toString();
|
||||||
|
const bool routeChanged = routeKey != m_searchRouteKey ||
|
||||||
|
query != m_searchQuery;
|
||||||
|
if (!query.isEmpty() && routeChanged)
|
||||||
|
{
|
||||||
|
m_searchRouteKey = routeKey;
|
||||||
|
m_searchQuery = query;
|
||||||
|
|
||||||
|
// SearchViewModel deliberately remembers a query while inactive.
|
||||||
|
// Queue the new query before activation so an interrupted previous
|
||||||
|
// query cannot be restarted immediately before this one.
|
||||||
|
QCoro::connect(m_searchViewModel->search(query), this, []() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_searchViewModel->setActive(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeQuickRouterView::deactivateRoutes()
|
void HomeQuickRouterView::deactivateRoutes()
|
||||||
{
|
{
|
||||||
m_dashboardViewModel->setActive(false);
|
m_dashboardViewModel->setActive(false);
|
||||||
m_favoritesViewModel->setActive(false);
|
m_favoritesViewModel->setActive(false);
|
||||||
|
m_searchViewModel->setActive(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeQuickRouterView::forwardItemActivation(const MediaItem &item)
|
void HomeQuickRouterView::forwardItemActivation(const MediaItem &item)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
class FavoritesViewModel;
|
class FavoritesViewModel;
|
||||||
class NavigationController;
|
class NavigationController;
|
||||||
class QuickRouterHost;
|
class QuickRouterHost;
|
||||||
|
class SearchViewModel;
|
||||||
class UserDashboardViewModel;
|
class UserDashboardViewModel;
|
||||||
|
|
||||||
// Bridges the first application-level QML routes to the existing BaseView
|
// Bridges the first application-level QML routes to the existing BaseView
|
||||||
@@ -52,4 +53,7 @@ private:
|
|||||||
QuickRouterHost *m_host = nullptr;
|
QuickRouterHost *m_host = nullptr;
|
||||||
UserDashboardViewModel *m_dashboardViewModel = nullptr;
|
UserDashboardViewModel *m_dashboardViewModel = nullptr;
|
||||||
FavoritesViewModel *m_favoritesViewModel = nullptr;
|
FavoritesViewModel *m_favoritesViewModel = nullptr;
|
||||||
|
SearchViewModel *m_searchViewModel = nullptr;
|
||||||
|
QString m_searchRouteKey;
|
||||||
|
QString m_searchQuery;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1085,7 +1085,12 @@ void HomeView::triggerSearch(const QString &query)
|
|||||||
|
|
||||||
|
|
||||||
m_libraryList->clearSelection();
|
m_libraryList->clearSelection();
|
||||||
pushView(createSearchView(trimmedQuery));
|
const QVariantMap searchRequest{
|
||||||
|
{QStringLiteral("name"), QStringLiteral("search")},
|
||||||
|
{QStringLiteral("params"),
|
||||||
|
QVariantMap{{QStringLiteral("query"), trimmedQuery}}}};
|
||||||
|
if (!pushQuickRoute(searchRequest))
|
||||||
|
pushView(createSearchView(trimmedQuery));
|
||||||
}
|
}
|
||||||
|
|
||||||
void HomeView::setupSearchHistory()
|
void HomeView::setupSearchHistory()
|
||||||
@@ -1263,6 +1268,39 @@ void HomeView::pushView(QWidget *view)
|
|||||||
Q_EMIT canNavigateBackChanged(!m_navStack.isEmpty());
|
Q_EMIT canNavigateBackChanged(!m_navStack.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HomeView::pushQuickRoute(const QVariantMap &request)
|
||||||
|
{
|
||||||
|
if (!m_useQuickHomeRouter || !m_homeQuickRouterView ||
|
||||||
|
!m_navigationController || !m_contentSwitcher)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QWidget *current = m_contentSwitcher->currentWidget();
|
||||||
|
if (!m_navigationController->push(request))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (current)
|
||||||
|
{
|
||||||
|
RouteInfo info;
|
||||||
|
info.widget = current;
|
||||||
|
info.isDynamic = current->property("isDynamic").toBool();
|
||||||
|
info.routeType = current->property("routeType").toString();
|
||||||
|
info.routeId = current->property("routeId").toString();
|
||||||
|
info.routeTitle = current->property("routeTitle").toString();
|
||||||
|
info.routeExtraId = current->property("routeExtraId").toString();
|
||||||
|
m_navStack.push(info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current != m_homeQuickRouterView)
|
||||||
|
{
|
||||||
|
if (m_contentSwitcher->indexOf(m_homeQuickRouterView) == -1)
|
||||||
|
m_contentSwitcher->addWidget(m_homeQuickRouterView);
|
||||||
|
m_contentSwitcher->slideInWgt(m_homeQuickRouterView,
|
||||||
|
SlidingStackedWidget::RightToLeft);
|
||||||
|
}
|
||||||
|
Q_EMIT canNavigateBackChanged(!m_navStack.isEmpty());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void HomeView::navigateBack()
|
void HomeView::navigateBack()
|
||||||
{
|
{
|
||||||
QWidget *current = m_contentSwitcher->currentWidget();
|
QWidget *current = m_contentSwitcher->currentWidget();
|
||||||
@@ -1349,6 +1387,12 @@ void HomeView::navigateBack()
|
|||||||
if (!targetView)
|
if (!targetView)
|
||||||
targetView = homeRootWidget();
|
targetView = homeRootWidget();
|
||||||
|
|
||||||
|
if (targetView == current)
|
||||||
|
{
|
||||||
|
Q_EMIT canNavigateBackChanged(!m_navStack.isEmpty());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ private:
|
|||||||
|
|
||||||
|
|
||||||
void pushView(QWidget* view);
|
void pushView(QWidget* view);
|
||||||
|
bool pushQuickRoute(const QVariantMap &request);
|
||||||
void resetToView(QWidget* view, const QString &rootRoute = QString());
|
void resetToView(QWidget* view, const QString &rootRoute = QString());
|
||||||
QWidget* homeRootWidget() const;
|
QWidget* homeRootWidget() const;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user