feat(explore): Explore tab with catalog detail, search, and Seerr requests
This commit is contained in:
@@ -6,7 +6,7 @@ import '../i18n/strings.g.dart';
|
||||
import '../utils/platform_detector.dart';
|
||||
|
||||
/// Navigation tab identifiers
|
||||
enum NavigationTabId { discover, libraries, liveTv, search, downloads, settings }
|
||||
enum NavigationTabId { discover, explore, libraries, liveTv, search, downloads, settings }
|
||||
|
||||
/// Represents a navigation tab with its configuration
|
||||
class NavigationTab {
|
||||
@@ -22,16 +22,21 @@ class NavigationTab {
|
||||
}
|
||||
|
||||
/// Get the index for a tab ID in the visible tabs list
|
||||
static int indexFor(NavigationTabId id, {required bool isOffline, bool hasLiveTv = false}) {
|
||||
final tabs = getVisibleTabs(isOffline: isOffline, hasLiveTv: hasLiveTv);
|
||||
static int indexFor(NavigationTabId id, {required bool isOffline, bool hasLiveTv = false, bool hasExplore = false}) {
|
||||
final tabs = getVisibleTabs(isOffline: isOffline, hasLiveTv: hasLiveTv, hasExplore: hasExplore);
|
||||
return tabs.indexWhere((tab) => tab.id == id);
|
||||
}
|
||||
|
||||
/// Get tabs filtered by offline mode and feature availability
|
||||
static List<NavigationTab> getVisibleTabs({required bool isOffline, bool hasLiveTv = false}) {
|
||||
static List<NavigationTab> getVisibleTabs({
|
||||
required bool isOffline,
|
||||
bool hasLiveTv = false,
|
||||
bool hasExplore = false,
|
||||
}) {
|
||||
return allNavigationTabs.where((tab) {
|
||||
if (isOffline && tab.onlineOnly) return false;
|
||||
if (tab.id == NavigationTabId.liveTv && !hasLiveTv) return false;
|
||||
if (tab.id == NavigationTabId.explore && !hasExplore) return false;
|
||||
if (tab.id == NavigationTabId.downloads && PlatformDetector.isAppleTV()) return false;
|
||||
return true;
|
||||
}).toList();
|
||||
@@ -45,9 +50,10 @@ class NavigationTab {
|
||||
static NavigationTabId resolveDefaultTab({
|
||||
required bool isOffline,
|
||||
required bool hasLiveTv,
|
||||
bool hasExplore = false,
|
||||
required NavigationTabId? preferredStartup,
|
||||
}) {
|
||||
final tabs = getVisibleTabs(isOffline: isOffline, hasLiveTv: hasLiveTv);
|
||||
final tabs = getVisibleTabs(isOffline: isOffline, hasLiveTv: hasLiveTv, hasExplore: hasExplore);
|
||||
if (isOffline && tabs.any((t) => t.id == NavigationTabId.downloads)) {
|
||||
return NavigationTabId.downloads;
|
||||
}
|
||||
@@ -60,6 +66,7 @@ class NavigationTab {
|
||||
|
||||
// Label getters (must be top-level for const constructor)
|
||||
String _getHomeLabel() => t.common.home;
|
||||
String _getExploreLabel() => t.navigation.explore;
|
||||
String _getLibrariesLabel() => t.navigation.libraries;
|
||||
String _getLiveTvLabel() => t.navigation.liveTv;
|
||||
String _getSearchLabel() => t.common.search;
|
||||
@@ -76,6 +83,12 @@ const allNavigationTabs = [
|
||||
getLabel: _getLibrariesLabel,
|
||||
),
|
||||
NavigationTab(id: NavigationTabId.liveTv, onlineOnly: true, icon: Symbols.live_tv_rounded, getLabel: _getLiveTvLabel),
|
||||
NavigationTab(
|
||||
id: NavigationTabId.explore,
|
||||
onlineOnly: true,
|
||||
icon: Symbols.explore_rounded,
|
||||
getLabel: _getExploreLabel,
|
||||
),
|
||||
NavigationTab(id: NavigationTabId.search, onlineOnly: true, icon: Symbols.search_rounded, getLabel: _getSearchLabel),
|
||||
NavigationTab(
|
||||
id: NavigationTabId.downloads,
|
||||
|
||||
@@ -10,18 +10,22 @@ import '../media/media_server_client.dart';
|
||||
import '../profiles/active_profile_provider.dart';
|
||||
import '../profiles/plex_home_service.dart';
|
||||
import '../profiles/profile_connection_registry.dart';
|
||||
import '../providers/catalog_sources_provider.dart';
|
||||
import '../providers/companion_remote_provider.dart';
|
||||
import '../providers/discover_provider.dart';
|
||||
import '../providers/explore_provider.dart';
|
||||
import '../providers/hidden_libraries_provider.dart';
|
||||
import '../providers/libraries_provider.dart';
|
||||
import '../providers/multi_server_provider.dart';
|
||||
import '../providers/playback_state_provider.dart';
|
||||
import '../providers/trakt_account_provider.dart';
|
||||
import '../providers/seerr_account_provider.dart';
|
||||
import '../providers/trackers_provider.dart';
|
||||
import '../providers/watch_state_store.dart';
|
||||
import '../database/app_database.dart';
|
||||
import '../screens/main_screen.dart';
|
||||
import '../services/api_cache.dart';
|
||||
import '../services/catalog/catalog_library_matcher.dart';
|
||||
import '../services/music/music_playback_service.dart';
|
||||
import '../services/music/music_playback_service_impl.dart';
|
||||
import '../services/offline_watch_sync_service.dart';
|
||||
@@ -143,6 +147,50 @@ class _ProfileSessionScreenState extends State<ProfileSessionScreen> {
|
||||
return provider;
|
||||
},
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) {
|
||||
final provider = SeerrAccountProvider();
|
||||
provider.bindPlexTokenSupplier(
|
||||
buildSeerrPlexTokenSupplier(
|
||||
activeProfile: context.read<ActiveProfileProvider>(),
|
||||
connections: context.read<ConnectionRegistry>(),
|
||||
profileConnections: context.read<ProfileConnectionRegistry>(),
|
||||
),
|
||||
);
|
||||
unawaited(
|
||||
provider.onActiveProfileChanged(activeId).catchError((Object e, StackTrace s) {
|
||||
appLogger.w('Seerr profile hydrate failed', error: e, stackTrace: s);
|
||||
}),
|
||||
);
|
||||
return provider;
|
||||
},
|
||||
),
|
||||
ChangeNotifierProxyProvider3<
|
||||
TraktAccountProvider,
|
||||
TrackersProvider,
|
||||
SeerrAccountProvider,
|
||||
CatalogSourcesProvider
|
||||
>(
|
||||
create: (context) {
|
||||
final provider = CatalogSourcesProvider();
|
||||
unawaited(
|
||||
provider.onActiveProfileChanged(activeId).catchError((Object e, StackTrace s) {
|
||||
appLogger.w('Catalog sources profile hydrate failed', error: e, stackTrace: s);
|
||||
}),
|
||||
);
|
||||
return provider;
|
||||
},
|
||||
update: (_, trakt, trackers, seerr, previous) {
|
||||
final provider = previous ?? CatalogSourcesProvider();
|
||||
provider.update(trakt, trackers, seerr);
|
||||
return provider;
|
||||
},
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) => ExploreProvider(context.read<CatalogSourcesProvider>()),
|
||||
lazy: true,
|
||||
),
|
||||
Provider(create: (context) => CatalogLibraryMatcher(context.read<MultiServerProvider>()), lazy: true),
|
||||
ChangeNotifierProvider(
|
||||
create: (context) =>
|
||||
HiddenLibrariesProvider(storageService: context.read<StorageService>(), profileId: activeId),
|
||||
|
||||
Reference in New Issue
Block a user