feat: route search through QML shell
This commit is contained in:
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user