feat: route search through QML shell

This commit is contained in:
dela
2026-07-15 13:47:20 +08:00
parent b117bbf99e
commit a411fd396a
10 changed files with 159 additions and 8 deletions
+4 -2
View File
@@ -9,7 +9,7 @@ Item {
property var navigationController: navigation
// Keep this allow-list deliberately small until each page's context
// ViewModel has been moved to the root Quick host.
property var enabledRoutes: ["home", "favorites"]
property var enabledRoutes: ["home", "favorites", "search"]
property var currentRoute: navigationController
? navigationController.currentRoute
: ({ "name": "navigationError",
@@ -69,7 +69,7 @@ Item {
}
function routeImplemented(name) {
return name === "home" || name === "favorites";
return name === "home" || name === "favorites" || name === "search";
}
function routeKnown(name) {
@@ -102,6 +102,7 @@ Item {
switch (name) {
case "home": return homeComponent;
case "favorites": return favoritesComponent;
case "search": return searchComponent;
default:
return null;
}
@@ -219,6 +220,7 @@ Item {
Component { id: homeComponent; DashboardPage { anchors.fill: parent } }
Component { id: favoritesComponent; FavoritesPage { anchors.fill: parent } }
Component { id: searchComponent; SearchPage { anchors.fill: parent } }
Component {
id: recoveryComponent
@@ -27,6 +27,13 @@ int main(int argc, char **argv)
navigation.enterAuthenticatedSession(false);
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"},
{"params", QVariantMap{{"libraryId", "lib/1"},
{"title", "Movies"}}}}),
@@ -136,6 +136,28 @@ void SearchViewModel::setCurrentTab(int tab) {
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) {
switch (tab) {
case 0:
@@ -155,6 +177,10 @@ QString SearchViewModel::includeItemTypes(int tab) {
void SearchViewModel::launchCurrentSearch() {
if (m_query.isEmpty())
return;
if (!m_active) {
m_retryOnActivate = true;
return;
}
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())
co_return;
const bool queryChanged = 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 QString types = includeItemTypes(m_currentTab);
m_loading = true;
@@ -64,6 +64,7 @@ public:
void setTileView(bool tileView);
void setCurrentTab(int tab);
void setActive(bool active);
QCoro::Task<void> search(QString query);
void updateItem(const MediaItem &item);
@@ -92,6 +93,8 @@ private:
QString m_statsText;
QString m_errorText;
bool m_loading = false;
bool m_active = true;
bool m_retryOnActivate = false;
bool m_tileView = false;
int m_currentTab = 0;
quint64 m_searchGeneration = 0;
@@ -8,6 +8,7 @@
#include "../../components/moderntoast.h"
#include "../../quick/navigationcontroller.h"
#include "../../quick/quickrouterhost.h"
#include "../search/searchviewmodel.h"
#include <qembycore.h>
#include <services/admin/adminservice.h>
@@ -37,11 +38,14 @@ HomeQuickRouterView::HomeQuickRouterView(QEmbyCore *core,
m_host = new QuickRouterHost(core, navigation, this);
m_dashboardViewModel = new UserDashboardViewModel(core, this);
m_favoritesViewModel = new FavoritesViewModel(core, this);
m_searchViewModel = new SearchViewModel(core, this);
m_host->setContextProperty(QStringLiteral("dashboardPage"),
m_dashboardViewModel);
m_host->setContextProperty(QStringLiteral("favoritesPage"),
m_favoritesViewModel);
m_host->setContextProperty(QStringLiteral("searchPage"),
m_searchViewModel);
m_host->loadRouter();
layout->addWidget(m_host);
@@ -96,6 +100,18 @@ HomeQuickRouterView::HomeQuickRouterView(QEmbyCore *core,
{
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()
@@ -125,6 +141,7 @@ void HomeQuickRouterView::onMediaItemUpdated(const MediaItem &item)
{
m_dashboardViewModel->updateItem(item);
m_favoritesViewModel->updateItem(item);
m_searchViewModel->updateItem(item);
}
void HomeQuickRouterView::onMediaItemRemoved(const QString &itemId)
@@ -199,21 +216,50 @@ void HomeQuickRouterView::dispatchCardContextMenuRequest(
void HomeQuickRouterView::updateRouteActivity()
{
const QString routeName = m_navigation
? m_navigation->currentRoute().value(QStringLiteral("name")).toString()
: QString();
const QVariantMap route = m_navigation
? m_navigation->currentRoute()
: QVariantMap{};
const QString routeName = route.value(QStringLiteral("name")).toString();
const bool hostActive = m_host && m_host->isActive();
m_dashboardViewModel->setActive(hostActive &&
routeName == QLatin1String("home"));
m_favoritesViewModel->setActive(hostActive &&
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()
{
m_dashboardViewModel->setActive(false);
m_favoritesViewModel->setActive(false);
m_searchViewModel->setActive(false);
}
void HomeQuickRouterView::forwardItemActivation(const MediaItem &item)
@@ -5,6 +5,7 @@
class FavoritesViewModel;
class NavigationController;
class QuickRouterHost;
class SearchViewModel;
class UserDashboardViewModel;
// Bridges the first application-level QML routes to the existing BaseView
@@ -52,4 +53,7 @@ private:
QuickRouterHost *m_host = nullptr;
UserDashboardViewModel *m_dashboardViewModel = nullptr;
FavoritesViewModel *m_favoritesViewModel = nullptr;
SearchViewModel *m_searchViewModel = nullptr;
QString m_searchRouteKey;
QString m_searchQuery;
};
+45 -1
View File
@@ -1085,7 +1085,12 @@ void HomeView::triggerSearch(const QString &query)
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()
@@ -1263,6 +1268,39 @@ void HomeView::pushView(QWidget *view)
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()
{
QWidget *current = m_contentSwitcher->currentWidget();
@@ -1349,6 +1387,12 @@ void HomeView::navigateBack()
if (!targetView)
targetView = homeRootWidget();
if (targetView == current)
{
Q_EMIT canNavigateBackChanged(!m_navStack.isEmpty());
return;
}
+1
View File
@@ -116,6 +116,7 @@ private:
void pushView(QWidget* view);
bool pushQuickRoute(const QVariantMap &request);
void resetToView(QWidget* view, const QString &rootRoute = QString());
QWidget* homeRootWidget() const;