feat: route library browsing through QML shell
This commit is contained in:
@@ -9,7 +9,8 @@ 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", "search"]
|
||||
property var enabledRoutes: ["home", "favorites", "search", "category",
|
||||
"library", "person", "filtered"]
|
||||
property var currentRoute: navigationController
|
||||
? navigationController.currentRoute
|
||||
: ({ "name": "navigationError",
|
||||
@@ -69,7 +70,9 @@ Item {
|
||||
}
|
||||
|
||||
function routeImplemented(name) {
|
||||
return name === "home" || name === "favorites" || name === "search";
|
||||
return name === "home" || name === "favorites" || name === "search"
|
||||
|| name === "category" || name === "library" || name === "person"
|
||||
|| name === "filtered";
|
||||
}
|
||||
|
||||
function routeKnown(name) {
|
||||
@@ -103,6 +106,11 @@ Item {
|
||||
case "home": return homeComponent;
|
||||
case "favorites": return favoritesComponent;
|
||||
case "search": return searchComponent;
|
||||
case "category": return categoryComponent;
|
||||
case "library":
|
||||
case "person":
|
||||
case "filtered":
|
||||
return libraryComponent;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@@ -185,6 +193,13 @@ Item {
|
||||
return true;
|
||||
}
|
||||
|
||||
function scrollToTop() {
|
||||
if (!pageLoader.item)
|
||||
return false;
|
||||
callHook(pageLoader.item, "scrollToTop", undefined);
|
||||
return true;
|
||||
}
|
||||
|
||||
onCurrentRouteChanged: applyRoute(currentRoute)
|
||||
onActiveChanged: {
|
||||
if (!pageLoader.item)
|
||||
@@ -221,6 +236,8 @@ Item {
|
||||
Component { id: homeComponent; DashboardPage { anchors.fill: parent } }
|
||||
Component { id: favoritesComponent; FavoritesPage { anchors.fill: parent } }
|
||||
Component { id: searchComponent; SearchPage { anchors.fill: parent } }
|
||||
Component { id: categoryComponent; CategoryPage { anchors.fill: parent } }
|
||||
Component { id: libraryComponent; LibraryPage { anchors.fill: parent } }
|
||||
|
||||
Component {
|
||||
id: recoveryComponent
|
||||
|
||||
@@ -191,6 +191,9 @@ bool NavigationController::normalize(const QVariantMap &request,
|
||||
QStringList identity;
|
||||
for (const QString &field : required)
|
||||
identity << encoded(params.value(field).toString());
|
||||
if (name == QLatin1String("category") &&
|
||||
params.value(QStringLiteral("favorite")).toBool())
|
||||
identity << QStringLiteral("favorite");
|
||||
route->key = identity.isEmpty() ? name : name + QLatin1Char(':') + identity.join(QLatin1Char(':'));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -34,6 +34,14 @@ int main(int argc, char **argv)
|
||||
"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", "category"},
|
||||
{"params", QVariantMap{{"categoryId", "Movie"},
|
||||
{"favorite", true}}}}),
|
||||
"accepts a favorite category route");
|
||||
ok &= check(navigation.currentRoute().value("key") ==
|
||||
"category:Movie:favorite",
|
||||
"favorite category has a distinct route identity");
|
||||
ok &= check(navigation.back(), "returns from favorite category");
|
||||
ok &= check(navigation.push({{"name", "library"},
|
||||
{"params", QVariantMap{{"libraryId", "lib/1"},
|
||||
{"title", "Movies"}}}}),
|
||||
|
||||
@@ -214,119 +214,111 @@ void LibraryViewModel::setActive(bool active) {
|
||||
m_active = active;
|
||||
if (!active) {
|
||||
++m_generation;
|
||||
if (m_loading || m_loadingMore)
|
||||
if (m_routeRequestInFlight || m_loading || m_loadingMore)
|
||||
m_reloadOnActivate = true;
|
||||
m_routeRequestInFlight = false;
|
||||
m_loading = false;
|
||||
m_loadingMore = false;
|
||||
emit stateChanged();
|
||||
} else if (m_reloadOnActivate) {
|
||||
m_reloadOnActivate = false;
|
||||
launchReload();
|
||||
launchCurrentRouteLoad();
|
||||
}
|
||||
}
|
||||
|
||||
QCoro::Task<void> LibraryViewModel::loadLibrary(QString libraryId,
|
||||
QString libraryName) {
|
||||
libraryId = libraryId.trimmed();
|
||||
if (isCurrentRoute(LibraryMode, libraryId, {})) {
|
||||
if (m_title != libraryName) {
|
||||
m_title = std::move(libraryName);
|
||||
emit stateChanged();
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
const quint64 generation = ++m_generation;
|
||||
m_mode = LibraryMode;
|
||||
m_targetId = std::move(libraryId);
|
||||
m_title = std::move(libraryName);
|
||||
m_filterType.clear();
|
||||
m_filterValue.clear();
|
||||
configureRoute(LibraryMode, std::move(libraryId), std::move(libraryName));
|
||||
|
||||
if (!m_active) {
|
||||
m_reloadOnActivate = true;
|
||||
co_return;
|
||||
}
|
||||
co_await loadCurrentRoute(generation);
|
||||
}
|
||||
|
||||
QCoro::Task<void> LibraryViewModel::loadPerson(QString personId,
|
||||
QString personName) {
|
||||
personId = personId.trimmed();
|
||||
if (isCurrentRoute(PersonMode, personId, {})) {
|
||||
if (m_title != personName) {
|
||||
m_title = std::move(personName);
|
||||
emit stateChanged();
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
const quint64 generation = ++m_generation;
|
||||
configureRoute(PersonMode, std::move(personId), std::move(personName));
|
||||
|
||||
if (!m_active) {
|
||||
m_reloadOnActivate = true;
|
||||
co_return;
|
||||
}
|
||||
co_await loadCurrentRoute(generation);
|
||||
}
|
||||
|
||||
QCoro::Task<void> LibraryViewModel::loadFiltered(QString filterType,
|
||||
QString filterValue) {
|
||||
filterType = filterType.trimmed();
|
||||
filterValue = filterValue.trimmed();
|
||||
if (isCurrentRoute(FilteredMode, filterType, filterValue))
|
||||
co_return;
|
||||
|
||||
const quint64 generation = ++m_generation;
|
||||
configureRoute(FilteredMode, std::move(filterType), std::move(filterValue));
|
||||
if (!m_active) {
|
||||
m_reloadOnActivate = true;
|
||||
co_return;
|
||||
}
|
||||
co_await loadCurrentRoute(generation);
|
||||
}
|
||||
|
||||
bool LibraryViewModel::isCurrentRoute(Mode mode, const QString &primary,
|
||||
const QString &secondary) const {
|
||||
if (!m_routeInitialized || m_mode != mode)
|
||||
return false;
|
||||
if (mode == FilteredMode)
|
||||
return m_filterType == primary && m_filterValue == secondary;
|
||||
return m_targetId == primary;
|
||||
}
|
||||
|
||||
void LibraryViewModel::configureRoute(Mode mode, QString primary,
|
||||
QString secondary) {
|
||||
m_routeInitialized = true;
|
||||
m_mode = mode;
|
||||
m_currentItem = {};
|
||||
m_tabIndex = 0;
|
||||
m_firstTabText = tr("All");
|
||||
m_tabsVisible = false;
|
||||
m_favoriteVisible = false;
|
||||
m_reloadOnActivate = false;
|
||||
|
||||
if (mode == FilteredMode) {
|
||||
m_targetId.clear();
|
||||
m_filterType = std::move(primary);
|
||||
m_filterValue = std::move(secondary);
|
||||
m_title = m_filterValue;
|
||||
} else {
|
||||
m_targetId = std::move(primary);
|
||||
m_title = std::move(secondary);
|
||||
m_filterType.clear();
|
||||
m_filterValue.clear();
|
||||
}
|
||||
|
||||
restorePreferences();
|
||||
emit stateChanged();
|
||||
emit controlsChanged();
|
||||
|
||||
if (!m_active) {
|
||||
m_reloadOnActivate = true;
|
||||
co_return;
|
||||
}
|
||||
|
||||
QPointer<LibraryViewModel> guard(this);
|
||||
if (m_active && m_core && m_core->mediaService() && !m_targetId.isEmpty()) {
|
||||
try {
|
||||
const MediaItem item =
|
||||
co_await m_core->mediaService()->getItemDetail(m_targetId);
|
||||
if (!guard || !m_active || generation != m_generation)
|
||||
co_return;
|
||||
if (item.id == m_targetId)
|
||||
configureCurrentItem(item);
|
||||
} catch (const std::exception &error) {
|
||||
if (!guard || generation != m_generation)
|
||||
co_return;
|
||||
qWarning() << "[LibraryViewModel] detail probe failed:" << error.what();
|
||||
}
|
||||
}
|
||||
if (!guard || !m_active || generation != m_generation)
|
||||
co_return;
|
||||
co_await reload();
|
||||
}
|
||||
|
||||
QCoro::Task<void> LibraryViewModel::loadPerson(QString personId,
|
||||
QString personName) {
|
||||
const quint64 generation = ++m_generation;
|
||||
m_mode = PersonMode;
|
||||
m_targetId = std::move(personId);
|
||||
m_title = std::move(personName);
|
||||
m_filterType.clear();
|
||||
m_filterValue.clear();
|
||||
m_currentItem = {};
|
||||
m_tabIndex = 0;
|
||||
m_tabsVisible = false;
|
||||
m_favoriteVisible = false;
|
||||
restorePreferences();
|
||||
emit stateChanged();
|
||||
emit controlsChanged();
|
||||
|
||||
if (!m_active) {
|
||||
m_reloadOnActivate = true;
|
||||
co_return;
|
||||
}
|
||||
|
||||
QPointer<LibraryViewModel> guard(this);
|
||||
if (m_active && m_core && m_core->mediaService() && !m_targetId.isEmpty()) {
|
||||
try {
|
||||
const MediaItem item =
|
||||
co_await m_core->mediaService()->getItemDetail(m_targetId);
|
||||
if (!guard || !m_active || generation != m_generation)
|
||||
co_return;
|
||||
if (item.id == m_targetId) {
|
||||
m_currentItem = item;
|
||||
m_favoriteVisible = true;
|
||||
emit controlsChanged();
|
||||
}
|
||||
} catch (const std::exception &error) {
|
||||
if (!guard || generation != m_generation)
|
||||
co_return;
|
||||
qWarning() << "[LibraryViewModel] person detail failed:" << error.what();
|
||||
}
|
||||
}
|
||||
if (!guard || !m_active || generation != m_generation)
|
||||
co_return;
|
||||
co_await reload();
|
||||
}
|
||||
|
||||
QCoro::Task<void> LibraryViewModel::loadFiltered(QString filterType,
|
||||
QString filterValue) {
|
||||
++m_generation;
|
||||
m_mode = FilteredMode;
|
||||
m_filterType = std::move(filterType);
|
||||
m_filterValue = std::move(filterValue);
|
||||
m_targetId.clear();
|
||||
m_title = m_filterValue;
|
||||
m_currentItem = {};
|
||||
m_tabIndex = 0;
|
||||
m_tabsVisible = false;
|
||||
m_favoriteVisible = false;
|
||||
restorePreferences();
|
||||
emit stateChanged();
|
||||
emit controlsChanged();
|
||||
co_await reload();
|
||||
}
|
||||
|
||||
void LibraryViewModel::configureCurrentItem(const MediaItem &item) {
|
||||
@@ -499,11 +491,72 @@ void LibraryViewModel::updateStats() {
|
||||
emit stateChanged();
|
||||
}
|
||||
|
||||
void LibraryViewModel::launchCurrentRouteLoad() {
|
||||
if (!m_active || !m_routeInitialized) {
|
||||
m_reloadOnActivate = m_routeInitialized;
|
||||
return;
|
||||
}
|
||||
const quint64 generation = ++m_generation;
|
||||
QCoro::connect(loadCurrentRoute(generation), this, []() {});
|
||||
}
|
||||
|
||||
QCoro::Task<void> LibraryViewModel::loadCurrentRoute(quint64 generation) {
|
||||
if (!m_active || !m_routeInitialized || generation != m_generation) {
|
||||
if (!m_active)
|
||||
m_reloadOnActivate = true;
|
||||
co_return;
|
||||
}
|
||||
|
||||
m_routeRequestInFlight = true;
|
||||
m_loading = true;
|
||||
m_loadingMore = false;
|
||||
m_hasMore = false;
|
||||
m_totalCount = 0;
|
||||
m_errorText.clear();
|
||||
m_statsText = tr("Loading...");
|
||||
m_items.clear();
|
||||
emit stateChanged();
|
||||
|
||||
QPointer<LibraryViewModel> guard(this);
|
||||
if (m_mode != FilteredMode && m_core && m_core->mediaService() &&
|
||||
!m_targetId.isEmpty()) {
|
||||
try {
|
||||
const MediaItem item =
|
||||
co_await m_core->mediaService()->getItemDetail(m_targetId);
|
||||
if (!guard || !m_active || generation != m_generation)
|
||||
co_return;
|
||||
|
||||
if (item.id == m_targetId) {
|
||||
if (m_mode == PersonMode) {
|
||||
m_currentItem = item;
|
||||
m_favoriteVisible = true;
|
||||
emit controlsChanged();
|
||||
} else {
|
||||
configureCurrentItem(item);
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &error) {
|
||||
if (!guard || !m_active || generation != m_generation)
|
||||
co_return;
|
||||
qWarning() << (m_mode == PersonMode
|
||||
? "[LibraryViewModel] person detail failed:"
|
||||
: "[LibraryViewModel] detail probe failed:")
|
||||
<< error.what();
|
||||
}
|
||||
}
|
||||
|
||||
if (!guard || !m_active || generation != m_generation)
|
||||
co_return;
|
||||
m_routeRequestInFlight = false;
|
||||
co_await reload();
|
||||
}
|
||||
|
||||
void LibraryViewModel::launchReload() {
|
||||
if (!m_active) {
|
||||
m_reloadOnActivate = true;
|
||||
return;
|
||||
}
|
||||
m_routeRequestInFlight = false;
|
||||
QCoro::connect(reload(), this, []() {});
|
||||
}
|
||||
|
||||
@@ -512,6 +565,7 @@ QCoro::Task<void> LibraryViewModel::reload() {
|
||||
m_reloadOnActivate = true;
|
||||
co_return;
|
||||
}
|
||||
m_routeRequestInFlight = false;
|
||||
const QueryState query = buildQuery();
|
||||
const quint64 generation = ++m_generation;
|
||||
m_query = query;
|
||||
|
||||
@@ -138,6 +138,9 @@ private:
|
||||
};
|
||||
|
||||
QString preferenceTargetId() const;
|
||||
bool isCurrentRoute(Mode mode, const QString &primary,
|
||||
const QString &secondary) const;
|
||||
void configureRoute(Mode mode, QString primary, QString secondary);
|
||||
QString sortBy() const;
|
||||
QueryState buildQuery() const;
|
||||
bool queryValid(const QueryState &query) const;
|
||||
@@ -146,6 +149,8 @@ private:
|
||||
void saveSortPreference();
|
||||
void saveViewPreference();
|
||||
void updateStats();
|
||||
void launchCurrentRouteLoad();
|
||||
QCoro::Task<void> loadCurrentRoute(quint64 generation);
|
||||
void launchReload();
|
||||
QCoro::Task<void> reload();
|
||||
QCoro::Task<void> fetchNextPage(quint64 generation, QueryState query,
|
||||
@@ -172,6 +177,8 @@ private:
|
||||
bool m_tabsVisible = false;
|
||||
bool m_favoriteVisible = false;
|
||||
bool m_active = true;
|
||||
bool m_routeInitialized = false;
|
||||
bool m_routeRequestInFlight = false;
|
||||
bool m_reloadOnActivate = false;
|
||||
int m_tabIndex = 0;
|
||||
int m_sortIndex = 0;
|
||||
|
||||
@@ -237,13 +237,24 @@ void CategoryViewModel::setActive(bool active) {
|
||||
m_loadingMore = false;
|
||||
emit stateChanged();
|
||||
} else if (m_reloadOnActivate) {
|
||||
const bool clearRecommendCache = m_clearRecommendCacheOnActivate;
|
||||
m_reloadOnActivate = false;
|
||||
launchReload();
|
||||
m_clearRecommendCacheOnActivate = false;
|
||||
launchReload(clearRecommendCache);
|
||||
}
|
||||
}
|
||||
|
||||
QCoro::Task<void> CategoryViewModel::loadCategory(QString categoryType,
|
||||
QString title) {
|
||||
const bool routeChanged =
|
||||
m_category != categoryType || m_title != title;
|
||||
if (!routeChanged) {
|
||||
if (!m_active)
|
||||
m_reloadOnActivate = true;
|
||||
co_return;
|
||||
}
|
||||
|
||||
++m_generation;
|
||||
m_category = std::move(categoryType);
|
||||
m_title = std::move(title);
|
||||
configureCategory();
|
||||
@@ -251,6 +262,15 @@ QCoro::Task<void> CategoryViewModel::loadCategory(QString categoryType,
|
||||
emit stateChanged();
|
||||
emit sortChanged();
|
||||
emit viewChanged();
|
||||
|
||||
if (!m_active) {
|
||||
m_loading = false;
|
||||
m_loadingMore = false;
|
||||
m_reloadOnActivate = true;
|
||||
m_clearRecommendCacheOnActivate = false;
|
||||
co_return;
|
||||
}
|
||||
|
||||
co_await reload();
|
||||
}
|
||||
|
||||
@@ -346,6 +366,8 @@ QString CategoryViewModel::sortBy() const {
|
||||
void CategoryViewModel::launchReload(bool clearRecommendCache) {
|
||||
if (!m_active) {
|
||||
m_reloadOnActivate = true;
|
||||
m_clearRecommendCacheOnActivate =
|
||||
m_clearRecommendCacheOnActivate || clearRecommendCache;
|
||||
return;
|
||||
}
|
||||
QCoro::connect(reload(clearRecommendCache), this, []() {});
|
||||
@@ -354,6 +376,8 @@ void CategoryViewModel::launchReload(bool clearRecommendCache) {
|
||||
QCoro::Task<void> CategoryViewModel::reload(bool clearRecommendCache) {
|
||||
if (!m_active) {
|
||||
m_reloadOnActivate = true;
|
||||
m_clearRecommendCacheOnActivate =
|
||||
m_clearRecommendCacheOnActivate || clearRecommendCache;
|
||||
co_return;
|
||||
}
|
||||
if (!m_core || !m_core->mediaService() || m_category.isEmpty())
|
||||
@@ -361,6 +385,7 @@ QCoro::Task<void> CategoryViewModel::reload(bool clearRecommendCache) {
|
||||
|
||||
const quint64 generation = ++m_generation;
|
||||
m_reloadOnActivate = false;
|
||||
m_clearRecommendCacheOnActivate = false;
|
||||
m_loading = true;
|
||||
m_loadingMore = false;
|
||||
m_errorText.clear();
|
||||
|
||||
@@ -138,6 +138,7 @@ private:
|
||||
bool m_descending = true;
|
||||
bool m_active = true;
|
||||
bool m_reloadOnActivate = false;
|
||||
bool m_clearRecommendCacheOnActivate = false;
|
||||
int m_sortIndex = 0;
|
||||
quint64 m_generation = 0;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "dashboardviewmodel.h"
|
||||
#include "favoritesviewmodel.h"
|
||||
#include "categoryviewmodel.h"
|
||||
|
||||
#include "../../components/libraryactionmenu.h"
|
||||
#include "../../components/mediaimageeditdialog.h"
|
||||
@@ -9,6 +10,7 @@
|
||||
#include "../../quick/navigationcontroller.h"
|
||||
#include "../../quick/quickrouterhost.h"
|
||||
#include "../search/searchviewmodel.h"
|
||||
#include "../media/libraryviewmodel.h"
|
||||
|
||||
#include <qembycore.h>
|
||||
#include <services/admin/adminservice.h>
|
||||
@@ -18,6 +20,8 @@
|
||||
#include <QCursor>
|
||||
#include <QDebug>
|
||||
#include <QPointer>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickWidget>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
@@ -39,6 +43,8 @@ HomeQuickRouterView::HomeQuickRouterView(QEmbyCore *core,
|
||||
m_dashboardViewModel = new UserDashboardViewModel(core, this);
|
||||
m_favoritesViewModel = new FavoritesViewModel(core, this);
|
||||
m_searchViewModel = new SearchViewModel(core, this);
|
||||
m_categoryViewModel = new CategoryViewModel(core, this);
|
||||
m_libraryViewModel = new LibraryViewModel(core, this);
|
||||
|
||||
m_host->setContextProperty(QStringLiteral("dashboardPage"),
|
||||
m_dashboardViewModel);
|
||||
@@ -46,6 +52,10 @@ HomeQuickRouterView::HomeQuickRouterView(QEmbyCore *core,
|
||||
m_favoritesViewModel);
|
||||
m_host->setContextProperty(QStringLiteral("searchPage"),
|
||||
m_searchViewModel);
|
||||
m_host->setContextProperty(QStringLiteral("categoryPage"),
|
||||
m_categoryViewModel);
|
||||
m_host->setContextProperty(QStringLiteral("libraryPage"),
|
||||
m_libraryViewModel);
|
||||
m_host->loadRouter();
|
||||
layout->addWidget(m_host);
|
||||
|
||||
@@ -112,6 +122,32 @@ HomeQuickRouterView::HomeQuickRouterView(QEmbyCore *core,
|
||||
{
|
||||
handleMoreMenuRequested(item, m_host->mapToGlobal(localPos));
|
||||
});
|
||||
|
||||
connect(m_categoryViewModel, &CategoryViewModel::itemActivated,
|
||||
this, &HomeQuickRouterView::forwardItemActivation);
|
||||
connect(m_categoryViewModel, &CategoryViewModel::playRequested,
|
||||
this, &BaseView::handlePlayRequested);
|
||||
connect(m_categoryViewModel, &CategoryViewModel::favoriteRequested,
|
||||
this, &BaseView::handleFavoriteRequested);
|
||||
connect(m_categoryViewModel, &CategoryViewModel::moreRequested,
|
||||
this, [this](const MediaItem &item)
|
||||
{
|
||||
handleMoreMenuRequested(item, QCursor::pos());
|
||||
});
|
||||
|
||||
connect(m_libraryViewModel, &LibraryViewModel::itemActivated,
|
||||
this, &HomeQuickRouterView::forwardItemActivation);
|
||||
connect(m_libraryViewModel, &LibraryViewModel::playRequested,
|
||||
this, &BaseView::handlePlayRequested);
|
||||
connect(m_libraryViewModel, &LibraryViewModel::favoriteRequested,
|
||||
this, &BaseView::handleFavoriteRequested);
|
||||
connect(m_libraryViewModel, &LibraryViewModel::currentFavoriteRequested,
|
||||
this, &BaseView::handleFavoriteRequested);
|
||||
connect(m_libraryViewModel, &LibraryViewModel::moreRequested,
|
||||
this, [this](const MediaItem &item)
|
||||
{
|
||||
handleMoreMenuRequested(item, QCursor::pos());
|
||||
});
|
||||
}
|
||||
|
||||
void HomeQuickRouterView::refreshHome()
|
||||
@@ -135,6 +171,9 @@ void HomeQuickRouterView::scrollToTop()
|
||||
m_dashboardViewModel->scrollToTop();
|
||||
else if (routeName == QLatin1String("favorites"))
|
||||
m_favoritesViewModel->scrollToTop();
|
||||
else if (m_host && m_host->quickWidget()->rootObject())
|
||||
QMetaObject::invokeMethod(m_host->quickWidget()->rootObject(),
|
||||
"scrollToTop");
|
||||
}
|
||||
|
||||
void HomeQuickRouterView::onMediaItemUpdated(const MediaItem &item)
|
||||
@@ -142,12 +181,16 @@ void HomeQuickRouterView::onMediaItemUpdated(const MediaItem &item)
|
||||
m_dashboardViewModel->updateItem(item);
|
||||
m_favoritesViewModel->updateItem(item);
|
||||
m_searchViewModel->updateItem(item);
|
||||
m_categoryViewModel->updateItem(item);
|
||||
m_libraryViewModel->updateItem(item);
|
||||
}
|
||||
|
||||
void HomeQuickRouterView::onMediaItemRemoved(const QString &itemId)
|
||||
{
|
||||
m_dashboardViewModel->removeItem(itemId);
|
||||
m_favoritesViewModel->removeItem(itemId);
|
||||
m_categoryViewModel->removeItem(itemId);
|
||||
m_libraryViewModel->removeItem(itemId);
|
||||
}
|
||||
|
||||
CardContextMenuRequest HomeQuickRouterView::showCardContextMenu(
|
||||
@@ -227,8 +270,16 @@ void HomeQuickRouterView::updateRouteActivity()
|
||||
m_favoritesViewModel->setActive(hostActive &&
|
||||
routeName == QLatin1String("favorites"));
|
||||
|
||||
const bool searchActive = hostActive &&
|
||||
routeName == QLatin1String("search");
|
||||
updateSearchRoute(route, hostActive);
|
||||
updateCategoryRoute(route, hostActive);
|
||||
updateLibraryRoute(route, hostActive);
|
||||
}
|
||||
|
||||
void HomeQuickRouterView::updateSearchRoute(const QVariantMap &route,
|
||||
bool hostActive)
|
||||
{
|
||||
const QString routeName = route.value(QStringLiteral("name")).toString();
|
||||
const bool searchActive = hostActive && routeName == QLatin1String("search");
|
||||
if (!searchActive)
|
||||
{
|
||||
m_searchViewModel->setActive(false);
|
||||
@@ -255,11 +306,90 @@ void HomeQuickRouterView::updateRouteActivity()
|
||||
m_searchViewModel->setActive(true);
|
||||
}
|
||||
|
||||
void HomeQuickRouterView::updateCategoryRoute(const QVariantMap &route,
|
||||
bool hostActive)
|
||||
{
|
||||
const QString routeName = route.value(QStringLiteral("name")).toString();
|
||||
const bool categoryActive = hostActive &&
|
||||
routeName == QLatin1String("category");
|
||||
if (!categoryActive)
|
||||
{
|
||||
m_categoryViewModel->setActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const QVariantMap params = route.value(QStringLiteral("params")).toMap();
|
||||
QString categoryId = params.value(QStringLiteral("categoryId"))
|
||||
.toString().trimmed();
|
||||
if (params.value(QStringLiteral("favorite")).toBool())
|
||||
categoryId.prepend(QStringLiteral("Favorite_"));
|
||||
const QString title = params.value(QStringLiteral("title")).toString();
|
||||
const QString routeKey = route.value(QStringLiteral("key")).toString();
|
||||
const QString routeIdentity = routeKey + QLatin1Char('\n') + title;
|
||||
if (!categoryId.isEmpty() && routeIdentity != m_categoryRouteKey)
|
||||
{
|
||||
m_categoryViewModel->setActive(false);
|
||||
m_categoryRouteKey = routeIdentity;
|
||||
QCoro::connect(m_categoryViewModel->loadCategory(categoryId, title),
|
||||
this, []() {});
|
||||
}
|
||||
m_categoryViewModel->setActive(true);
|
||||
}
|
||||
|
||||
void HomeQuickRouterView::updateLibraryRoute(const QVariantMap &route,
|
||||
bool hostActive)
|
||||
{
|
||||
const QString routeName = route.value(QStringLiteral("name")).toString();
|
||||
const bool libraryActive = hostActive &&
|
||||
(routeName == QLatin1String("library") ||
|
||||
routeName == QLatin1String("person") ||
|
||||
routeName == QLatin1String("filtered"));
|
||||
if (!libraryActive)
|
||||
{
|
||||
m_libraryViewModel->setActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const QVariantMap params = route.value(QStringLiteral("params")).toMap();
|
||||
const QString routeKey = route.value(QStringLiteral("key")).toString();
|
||||
const QString title = params.value(QStringLiteral("title")).toString();
|
||||
const QString routeIdentity = routeKey + QLatin1Char('\n') + title;
|
||||
if (routeIdentity != m_libraryRouteKey)
|
||||
{
|
||||
m_libraryViewModel->setActive(false);
|
||||
m_libraryRouteKey = routeIdentity;
|
||||
if (routeName == QLatin1String("library"))
|
||||
{
|
||||
QCoro::connect(m_libraryViewModel->loadLibrary(
|
||||
params.value(QStringLiteral("libraryId")).toString(),
|
||||
title),
|
||||
this, []() {});
|
||||
}
|
||||
else if (routeName == QLatin1String("person"))
|
||||
{
|
||||
QCoro::connect(m_libraryViewModel->loadPerson(
|
||||
params.value(QStringLiteral("personId")).toString(),
|
||||
title),
|
||||
this, []() {});
|
||||
}
|
||||
else
|
||||
{
|
||||
QCoro::connect(m_libraryViewModel->loadFiltered(
|
||||
params.value(QStringLiteral("filterType")).toString(),
|
||||
params.value(QStringLiteral("filterValue")).toString()),
|
||||
this, []() {});
|
||||
}
|
||||
}
|
||||
m_libraryViewModel->setActive(true);
|
||||
}
|
||||
|
||||
void HomeQuickRouterView::deactivateRoutes()
|
||||
{
|
||||
m_dashboardViewModel->setActive(false);
|
||||
m_favoritesViewModel->setActive(false);
|
||||
m_searchViewModel->setActive(false);
|
||||
m_categoryViewModel->setActive(false);
|
||||
m_libraryViewModel->setActive(false);
|
||||
}
|
||||
|
||||
void HomeQuickRouterView::forwardItemActivation(const MediaItem &item)
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "../baseview.h"
|
||||
|
||||
class FavoritesViewModel;
|
||||
class CategoryViewModel;
|
||||
class LibraryViewModel;
|
||||
class NavigationController;
|
||||
class QuickRouterHost;
|
||||
class SearchViewModel;
|
||||
@@ -40,6 +42,9 @@ protected:
|
||||
|
||||
private:
|
||||
void updateRouteActivity();
|
||||
void updateSearchRoute(const QVariantMap &route, bool hostActive);
|
||||
void updateCategoryRoute(const QVariantMap &route, bool hostActive);
|
||||
void updateLibraryRoute(const QVariantMap &route, bool hostActive);
|
||||
void deactivateRoutes();
|
||||
void forwardItemActivation(const MediaItem &item);
|
||||
void launchDashboardTask(QCoro::Task<void> &&task);
|
||||
@@ -54,6 +59,10 @@ private:
|
||||
UserDashboardViewModel *m_dashboardViewModel = nullptr;
|
||||
FavoritesViewModel *m_favoritesViewModel = nullptr;
|
||||
SearchViewModel *m_searchViewModel = nullptr;
|
||||
CategoryViewModel *m_categoryViewModel = nullptr;
|
||||
LibraryViewModel *m_libraryViewModel = nullptr;
|
||||
QString m_searchRouteKey;
|
||||
QString m_searchQuery;
|
||||
QString m_categoryRouteKey;
|
||||
QString m_libraryRouteKey;
|
||||
};
|
||||
|
||||
@@ -259,9 +259,9 @@ void HomeView::setupUi()
|
||||
auto navigateToDetailSlot = [this](const QString &itemId, const QString &itemName, const MediaItem &seedItem)
|
||||
{ pushView(createDetailView(itemId, itemName, seedItem)); };
|
||||
auto navigateToFolderSlot = [this](const QString &id, const QString &name)
|
||||
{ pushView(createLibraryView(id, name)); };
|
||||
{ openLibraryRoute(id, name); };
|
||||
auto navigateToPersonSlot = [this](const QString &id, const QString &name)
|
||||
{ pushView(createPersonView(id, name)); };
|
||||
{ openPersonRoute(id, name); };
|
||||
auto navigateToPlayerSlot = [this](const QString &id, const QString &title, const QString &url, long long ticks,
|
||||
const QVariant &extraData) { launchPlayer(id, title, url, ticks, extraData); };
|
||||
auto navigateToSeasonSlot = [this](const QString &seriesId, const QString &seasonId, const QString &seasonName)
|
||||
@@ -278,12 +278,12 @@ void HomeView::setupUi()
|
||||
break;
|
||||
}
|
||||
}
|
||||
pushView(createLibraryView(id, name));
|
||||
openLibraryRoute(id, name);
|
||||
};
|
||||
const auto openCategory = [this](const QString &categoryId, const QString &title)
|
||||
{
|
||||
m_libraryList->clearSelection();
|
||||
pushView(createCategoryView(categoryId, title));
|
||||
openCategoryRoute(categoryId, title);
|
||||
};
|
||||
|
||||
m_useQuickHomeRouter = qEnvironmentVariableIntValue("QEMBY_USE_LEGACY_HOME_PAGES") != 1;
|
||||
@@ -410,11 +410,11 @@ QWidget *HomeView::createDetailView(const QString &itemId, const QString &itemNa
|
||||
connect(view, &DetailView::navigateToDetail, this,
|
||||
[this](const QString &id, const QString &name, const MediaItem &seed) { pushView(createDetailView(id, name, seed)); });
|
||||
connect(view, &DetailView::navigateToFolder, this,
|
||||
[this](const QString &id, const QString &name) { pushView(createLibraryView(id, name)); });
|
||||
[this](const QString &id, const QString &name) { openLibraryRoute(id, name); });
|
||||
connect(view, &BaseView::navigateToPerson, this,
|
||||
[this](const QString &id, const QString &name)
|
||||
{
|
||||
pushView(createPersonView(id, name));
|
||||
openPersonRoute(id, name);
|
||||
});
|
||||
|
||||
connect(view, &BaseView::navigateToPlayer, this,
|
||||
@@ -429,7 +429,7 @@ QWidget *HomeView::createDetailView(const QString &itemId, const QString &itemNa
|
||||
connect(view, &DetailView::triggerSearch, this, &HomeView::triggerSearch);
|
||||
connect(view, &BaseView::navigateToFilteredView, this,
|
||||
[this](const QString &type, const QString &value)
|
||||
{ pushView(createFilteredView(type, value)); });
|
||||
{ openFilteredRoute(type, value); });
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -449,10 +449,10 @@ QWidget *HomeView::createCategoryView(const QString &categoryId, const QString &
|
||||
[this](const QString &id, const QString &name, const MediaItem &seed) { pushView(createDetailView(id, name, seed)); });
|
||||
|
||||
connect(view, &CategoryView::navigateToFolder, this,
|
||||
[this](const QString &id, const QString &name) { pushView(createLibraryView(id, name)); });
|
||||
[this](const QString &id, const QString &name) { openLibraryRoute(id, name); });
|
||||
|
||||
connect(view, &CategoryView::navigateToPerson, this,
|
||||
[this](const QString &id, const QString &name) { pushView(createPersonView(id, name)); });
|
||||
[this](const QString &id, const QString &name) { openPersonRoute(id, name); });
|
||||
|
||||
|
||||
connect(view, &BaseView::navigateToPlayer, this,
|
||||
@@ -481,10 +481,10 @@ QWidget *HomeView::createLibraryView(const QString &libraryId, const QString &ti
|
||||
[this](const QString &id, const QString &name, const MediaItem &seed) { pushView(createDetailView(id, name, seed)); });
|
||||
|
||||
connect(view, &LibraryView::navigateToFolder, this,
|
||||
[this](const QString &id, const QString &name) { pushView(createLibraryView(id, name)); });
|
||||
[this](const QString &id, const QString &name) { openLibraryRoute(id, name); });
|
||||
|
||||
connect(view, &BaseView::navigateToPerson, this,
|
||||
[this](const QString &id, const QString &name) { pushView(createPersonView(id, name)); });
|
||||
[this](const QString &id, const QString &name) { openPersonRoute(id, name); });
|
||||
|
||||
|
||||
connect(view, &BaseView::navigateToPlayer, this,
|
||||
@@ -520,7 +520,7 @@ QWidget *HomeView::createPersonView(const QString &personId, const QString &pers
|
||||
|
||||
|
||||
connect(view, &BaseView::navigateToPerson, this,
|
||||
[this](const QString &id, const QString &name) { pushView(createPersonView(id, name)); });
|
||||
[this](const QString &id, const QString &name) { openPersonRoute(id, name); });
|
||||
|
||||
|
||||
connect(view, &BaseView::navigateToPlayer, this,
|
||||
@@ -556,10 +556,10 @@ QWidget *HomeView::createSearchView(const QString &query)
|
||||
[this](const QString &id, const QString &name, const MediaItem &seed) { pushView(createDetailView(id, name, seed)); });
|
||||
|
||||
connect(view, &SearchView::navigateToFolder, this,
|
||||
[this](const QString &id, const QString &name) { pushView(createLibraryView(id, name)); });
|
||||
[this](const QString &id, const QString &name) { openLibraryRoute(id, name); });
|
||||
|
||||
connect(view, &SearchView::navigateToPerson, this,
|
||||
[this](const QString &id, const QString &name) { pushView(createPersonView(id, name)); });
|
||||
[this](const QString &id, const QString &name) { openPersonRoute(id, name); });
|
||||
|
||||
|
||||
connect(view, &BaseView::navigateToPlayer, this,
|
||||
@@ -589,9 +589,9 @@ QWidget *HomeView::createFilteredView(const QString &filterType, const QString &
|
||||
connect(view, &BaseView::navigateToDetail, this,
|
||||
[this](const QString &id, const QString &name, const MediaItem &seed) { pushView(createDetailView(id, name, seed)); });
|
||||
connect(view, &BaseView::navigateToFolder, this,
|
||||
[this](const QString &id, const QString &name) { pushView(createLibraryView(id, name)); });
|
||||
[this](const QString &id, const QString &name) { openLibraryRoute(id, name); });
|
||||
connect(view, &BaseView::navigateToPerson, this,
|
||||
[this](const QString &id, const QString &name) { pushView(createPersonView(id, name)); });
|
||||
[this](const QString &id, const QString &name) { openPersonRoute(id, name); });
|
||||
connect(view, &BaseView::navigateToPlayer, this,
|
||||
[this](const QString &id, const QString &title, const QString &url, long long ticks,
|
||||
const QVariant &extraData) { launchPlayer(id, title, url, ticks, extraData); });
|
||||
@@ -998,7 +998,7 @@ void HomeView::setupSidebar()
|
||||
{
|
||||
return;
|
||||
}
|
||||
pushView(createLibraryView(viewId, cleanName));
|
||||
openLibraryRoute(viewId, cleanName);
|
||||
});
|
||||
|
||||
m_sidebarAnim = new QPropertyAnimation(m_sidebar, "pos", this);
|
||||
@@ -1301,6 +1301,59 @@ bool HomeView::pushQuickRoute(const QVariantMap &request)
|
||||
return true;
|
||||
}
|
||||
|
||||
void HomeView::openCategoryRoute(const QString &categoryId, const QString &title)
|
||||
{
|
||||
const bool favorite = categoryId.startsWith(QLatin1String("Favorite_"));
|
||||
const QString normalizedId = favorite ? categoryId.mid(9) : categoryId;
|
||||
const QVariantMap request{
|
||||
{QStringLiteral("name"), QStringLiteral("category")},
|
||||
{QStringLiteral("title"), title},
|
||||
{QStringLiteral("params"),
|
||||
QVariantMap{{QStringLiteral("categoryId"), normalizedId},
|
||||
{QStringLiteral("title"), title},
|
||||
{QStringLiteral("favorite"), favorite}}}};
|
||||
if (!pushQuickRoute(request))
|
||||
pushView(createCategoryView(categoryId, title));
|
||||
}
|
||||
|
||||
void HomeView::openLibraryRoute(const QString &libraryId, const QString &title)
|
||||
{
|
||||
const QVariantMap request{
|
||||
{QStringLiteral("name"), QStringLiteral("library")},
|
||||
{QStringLiteral("title"), title},
|
||||
{QStringLiteral("params"),
|
||||
QVariantMap{{QStringLiteral("libraryId"), libraryId},
|
||||
{QStringLiteral("title"), title}}}};
|
||||
if (!pushQuickRoute(request))
|
||||
pushView(createLibraryView(libraryId, title));
|
||||
}
|
||||
|
||||
void HomeView::openPersonRoute(const QString &personId, const QString &title)
|
||||
{
|
||||
const QVariantMap request{
|
||||
{QStringLiteral("name"), QStringLiteral("person")},
|
||||
{QStringLiteral("title"), title},
|
||||
{QStringLiteral("params"),
|
||||
QVariantMap{{QStringLiteral("personId"), personId},
|
||||
{QStringLiteral("title"), title}}}};
|
||||
if (!pushQuickRoute(request))
|
||||
pushView(createPersonView(personId, title));
|
||||
}
|
||||
|
||||
void HomeView::openFilteredRoute(const QString &filterType,
|
||||
const QString &filterValue)
|
||||
{
|
||||
const QVariantMap request{
|
||||
{QStringLiteral("name"), QStringLiteral("filtered")},
|
||||
{QStringLiteral("title"), filterValue},
|
||||
{QStringLiteral("params"),
|
||||
QVariantMap{{QStringLiteral("filterType"), filterType},
|
||||
{QStringLiteral("filterValue"), filterValue},
|
||||
{QStringLiteral("title"), filterValue}}}};
|
||||
if (!pushQuickRoute(request))
|
||||
pushView(createFilteredView(filterType, filterValue));
|
||||
}
|
||||
|
||||
void HomeView::navigateBack()
|
||||
{
|
||||
QWidget *current = m_contentSwitcher->currentWidget();
|
||||
|
||||
@@ -117,6 +117,10 @@ private:
|
||||
|
||||
void pushView(QWidget* view);
|
||||
bool pushQuickRoute(const QVariantMap &request);
|
||||
void openCategoryRoute(const QString &categoryId, const QString &title);
|
||||
void openLibraryRoute(const QString &libraryId, const QString &title);
|
||||
void openPersonRoute(const QString &personId, const QString &title);
|
||||
void openFilteredRoute(const QString &filterType, const QString &filterValue);
|
||||
void resetToView(QWidget* view, const QString &rootRoute = QString());
|
||||
QWidget* homeRootWidget() const;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user