Merge branch 'feature/auto_router' into develop

This commit is contained in:
PartyDonut 2024-10-06 19:31:45 +02:00
commit e70858eb64
49 changed files with 1366 additions and 1102 deletions

View file

@ -2,13 +2,27 @@ targets:
$default: $default:
sources: sources:
- lib/$lib$ - lib/$lib$
- "**/models/**.dart" # Matches models folder at any depth in lib - "**/models/**.dart"
- "**/providers/**.dart" # Matches providers folder at any depth in lib - "**/providers/**.dart"
- lib/util/**.dart - lib/util/**.dart
- lib/jellyfin/**.dart - lib/jellyfin/**.dart
- "**/**_screen.dart"
- "**/**_page.dart"
- swagger/** - swagger/**
- lib/routes/auto_router.dart
- $package$ - $package$
builders: builders:
auto_route_generator:auto_router_generator:
options:
enable_cached_builds: true
generate_for:
- lib/routes/auto_router.dart
auto_route_generator:auto_route_generator:
options:
enable_cached_builds: true
generate_for:
- "**/**_screen.dart"
- "**/**_page.dart"
freezed|freezed: freezed|freezed:
options: options:
generate_for: generate_for:

View file

@ -1,21 +1,15 @@
import 'dart:io'; import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:collection/collection.dart';
import 'package:fladder/models/syncing/i_synced_item.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/providers/sync_provider.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:collection/collection.dart';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
// ignore: depend_on_referenced_packages
import 'package:flutter_web_plugins/url_strategy.dart';
import 'package:go_router/go_router.dart';
import 'package:isar/isar.dart'; import 'package:isar/isar.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:media_kit/media_kit.dart'; import 'package:media_kit/media_kit.dart';
@ -23,21 +17,23 @@ import 'package:package_info_plus/package_info_plus.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:universal_html/html.dart' as html;
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:fladder/models/account_model.dart'; import 'package:fladder/models/account_model.dart';
import 'package:fladder/models/syncing/i_synced_item.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/providers/shared_provider.dart'; import 'package:fladder/providers/shared_provider.dart';
import 'package:fladder/providers/sync_provider.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/providers/video_player_provider.dart'; import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/routes/app_routes.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/screens/login/lock_screen.dart'; import 'package:fladder/screens/login/lock_screen.dart';
import 'package:fladder/theme.dart'; import 'package:fladder/theme.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/application_info.dart'; import 'package:fladder/util/application_info.dart';
import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/util/string_extensions.dart';
import 'package:fladder/util/themes_data.dart'; import 'package:fladder/util/themes_data.dart';
import 'package:universal_html/html.dart' as html;
bool get _isDesktop { bool get _isDesktop {
if (kIsWeb) return false; if (kIsWeb) return false;
@ -63,9 +59,7 @@ class CustomCacheManager {
void main() async { void main() async {
if (kIsWeb) { if (kIsWeb) {
usePathUrlStrategy();
html.document.onContextMenu.listen((event) => event.preventDefault()); html.document.onContextMenu.listen((event) => event.preventDefault());
GoRouter.optionURLReflectsImperativeAPIs = true;
} }
_setupLogging(); _setupLogging();
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -89,9 +83,9 @@ void main() async {
} }
final applicationInfo = ApplicationInfo( final applicationInfo = ApplicationInfo(
name: kIsWeb ? "${packageInfo.appName.capitalize()} Web" : packageInfo.appName.capitalize(), name: packageInfo.appName.capitalize(),
version: "${packageInfo.version}(${packageInfo.buildNumber})", version: "${packageInfo.version}(${packageInfo.buildNumber})",
os: defaultTargetPlatform.name.capitalize(), os: !kIsWeb ? defaultTargetPlatform.name.capitalize() : "${defaultTargetPlatform.name.capitalize()} Web",
); );
runApp( runApp(
@ -111,7 +105,7 @@ void main() async {
)) ))
], ],
child: AdaptiveLayoutBuilder( child: AdaptiveLayoutBuilder(
fallBack: LayoutState.phone, fallBack: LayoutState.tablet,
layoutPoints: [ layoutPoints: [
LayoutPoints(start: 0, end: 599, type: LayoutState.phone), LayoutPoints(start: 0, end: 599, type: LayoutState.phone),
LayoutPoints(start: 600, end: 1919, type: LayoutState.tablet), LayoutPoints(start: 600, end: 1919, type: LayoutState.tablet),
@ -179,7 +173,7 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
await ref.read(videoPlayerProvider).pause(); await ref.read(videoPlayerProvider).pause();
if (context.mounted) { if (context.mounted) {
AdaptiveLayout.of(context).router.push(LockScreenRoute().route); AdaptiveLayout.of(context).router.push(const LockRoute());
} }
} }
} }
@ -316,22 +310,12 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
), ),
), ),
themeMode: themeMode, themeMode: themeMode,
routerConfig: AdaptiveLayout.of(context).router, routerConfig: AdaptiveLayout.routerOf(context).config(),
), ),
); );
}), }),
); );
} }
List<RouteBase> getRoutes(LayoutState state) {
switch (state) {
case LayoutState.phone:
return AppRoutes.linearRoutes;
case LayoutState.tablet:
case LayoutState.desktop:
return AppRoutes.nestedRoutes;
}
}
} }
final currentTitleProvider = StateProvider<String>((ref) { final currentTitleProvider = StateProvider<String>((ref) {

View file

@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:dart_mappable/dart_mappable.dart'; import 'package:dart_mappable/dart_mappable.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/models/book_model.dart'; import 'package:fladder/models/book_model.dart';
@ -5,8 +6,7 @@ import 'package:fladder/models/boxset_model.dart';
import 'package:fladder/models/items/media_streams_model.dart'; import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/library_search/library_search_options.dart'; import 'package:fladder/models/library_search/library_search_options.dart';
import 'package:fladder/models/playlist_model.dart'; import 'package:fladder/models/playlist_model.dart';
import 'package:fladder/routes/build_routes/home_routes.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/screens/details_screens/book_detail_screen.dart'; import 'package:fladder/screens/details_screens/book_detail_screen.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/util/string_extensions.dart';
@ -166,7 +166,7 @@ class ItemBaseModel with ItemBaseModelMappable {
} }
} }
Future<void> navigateTo(BuildContext context) async => context.routePush(DetailsRoute(id: id), extra: this); Future<void> navigateTo(BuildContext context) async => context.router.push(DetailsRoute(id: id, item: this));
factory ItemBaseModel.fromBaseDto(dto.BaseItemDto item, Ref ref) { factory ItemBaseModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
return switch (item.type) { return switch (item.type) {

View file

@ -1,31 +1,32 @@
import 'dart:developer'; import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:chopper/chopper.dart'; import 'package:chopper/chopper.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:page_transition/page_transition.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/collection_types.dart'; import 'package:fladder/models/collection_types.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/folder_model.dart'; import 'package:fladder/models/items/folder_model.dart';
import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/photos_model.dart'; import 'package:fladder/models/items/photos_model.dart';
import 'package:fladder/models/library_search/library_search_model.dart';
import 'package:fladder/models/library_search/library_search_options.dart'; import 'package:fladder/models/library_search/library_search_options.dart';
import 'package:fladder/models/playlist_model.dart'; import 'package:fladder/models/playlist_model.dart';
import 'package:fladder/models/view_model.dart';
import 'package:fladder/providers/api_provider.dart';
import 'package:fladder/providers/service_provider.dart'; import 'package:fladder/providers/service_provider.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/screens/photo_viewer/photo_viewer_screen.dart'; import 'package:fladder/screens/photo_viewer/photo_viewer_screen.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/util/item_base_model/play_item_helpers.dart'; import 'package:fladder/util/item_base_model/play_item_helpers.dart';
import 'package:fladder/util/list_extensions.dart'; import 'package:fladder/util/list_extensions.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/library_search/library_search_model.dart';
import 'package:fladder/models/view_model.dart';
import 'package:fladder/providers/api_provider.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/util/map_bool_helper.dart'; import 'package:fladder/util/map_bool_helper.dart';
import 'package:page_transition/page_transition.dart';
final librarySearchProvider = final librarySearchProvider =
StateNotifierProvider.family.autoDispose<LibrarySearchNotifier, LibrarySearchModel, Key>((ref, id) { StateNotifierProvider.family.autoDispose<LibrarySearchNotifier, LibrarySearchModel, Key>((ref, id) {
@ -652,6 +653,10 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
items.firstOrNull?.navigateTo(context); items.firstOrNull?.navigateTo(context);
} }
} }
void updateEverything() {
state = state.copyWith();
}
} }
extension SimpleSorter on List<ItemBaseModel> { extension SimpleSorter on List<ItemBaseModel> {

View file

@ -27,7 +27,7 @@ Map<String, dynamic> _$$SessionInfoModelImplToJson(
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$sessionInfoHash() => r'ab5afcada1c9677cadda954c9abf7eb361dc057d'; String _$sessionInfoHash() => r'024da7f8d05fb98f6e2e5395ed06c1cc9d003f79';
/// See also [SessionInfo]. /// See also [SessionInfo].
@ProviderFor(SessionInfo) @ProviderFor(SessionInfo)

View file

@ -7,7 +7,7 @@ part of 'background_download_provider.dart';
// ************************************************************************** // **************************************************************************
String _$backgroundDownloaderHash() => String _$backgroundDownloaderHash() =>
r'2bc7a06682cdcfa9a754dce9b7f7ea48f873682e'; r'9a9f91504ae4532ab37290ee9372d2e7a18380a9';
/// See also [backgroundDownloader]. /// See also [backgroundDownloader].
@ProviderFor(backgroundDownloader) @ProviderFor(backgroundDownloader)

View file

@ -1,121 +0,0 @@
import 'package:fladder/providers/shared_provider.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/home_routes.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/routes/build_routes/settings_routes.dart';
import 'package:fladder/screens/home.dart';
import 'package:fladder/screens/settings/settings_screen.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
class AppRoutes {
static final parentKey = GlobalKey<NavigatorState>();
static final homeShellKey = GlobalKey<NavigatorState>();
static final settingsKey = GlobalKey<NavigatorState>();
static final loginRoute = LoginRoute();
static final splashRoute = SplashRoute();
static final scrollController = ScrollController();
static GoRouter routes({required WidgetRef ref, required ScreenLayout screenLayout}) {
return GoRouter(
navigatorKey: parentKey,
initialLocation: splashRoute.route,
//Only useful for web if the user is not in an active session yet
redirect: kIsWeb
? (context, state) async {
if (state.uri.toString() == loginRoute.route || state.uri.toString() == splashRoute.route) return null;
await Future.microtask(() {
final lastUsedAccount = ref.read(sharedUtilityProvider).getActiveAccount();
if (lastUsedAccount == null) return loginRoute.route;
if (ref.read(userProvider) == null) ref.read(userProvider.notifier).loginUser(lastUsedAccount);
});
if (ref.read(userProvider) == null) {
return loginRoute.route;
} else {
return state.uri.toString();
}
}
: null,
routes: AppRoutes().getRoutes(screenLayout),
);
}
List<RouteBase> getRoutes(ScreenLayout screenLayout) {
switch (screenLayout) {
case ScreenLayout.single:
return linearRoutes;
case ScreenLayout.dual:
return nestedRoutes;
}
}
static List<RouteBase> linearRoutes = [
loginRoute.go,
splashRoute.go,
LockScreenRoute().go,
ShellRoute(
navigatorKey: homeShellKey,
pageBuilder: (context, state, child) {
return NoTransitionPage(
child: Home(
key: state.pageKey,
location: state.uri.toString(),
nestedChild: child,
),
);
},
routes: [
DashboardRoute().go,
FavouritesRoute().go,
SyncRoute().go,
],
),
DetailsRoute(navKey: parentKey).go,
LibrarySearchRoute(navKey: parentKey).go,
// Settings routes
SettingsRoute(navKey: parentKey).go,
ClientSettingsRoute(navKey: parentKey).go,
SecuritySettingsRoute(navKey: parentKey).go,
PlayerSettingsRoute(navKey: parentKey).go,
];
static List<RouteBase> nestedRoutes = [
loginRoute.go,
splashRoute.go,
LockScreenRoute().go,
ShellRoute(
navigatorKey: homeShellKey,
pageBuilder: (context, state, child) => NoTransitionPage(
child: Home(
key: state.pageKey,
location: state.uri.toString(),
nestedChild: child,
),
),
routes: [
ShellRoute(
navigatorKey: settingsKey,
pageBuilder: (context, state, child) => NoTransitionPage(
key: state.pageKey,
child: SettingsScreen(location: state.uri.toString(), child: child),
),
routes: [
ClientSettingsRoute(navKey: settingsKey).go,
SecuritySettingsRoute(navKey: settingsKey).go,
PlayerSettingsRoute(navKey: settingsKey).go,
],
),
DashboardRoute().go,
FavouritesRoute().go,
SyncRoute().go,
DetailsRoute(navKey: homeShellKey).go,
LibrarySearchRoute(navKey: homeShellKey).go,
],
),
];
}

138
lib/routes/auto_router.dart Normal file
View file

@ -0,0 +1,138 @@
import 'package:flutter/foundation.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/screens/login/lock_screen.dart';
import 'package:fladder/util/adaptive_layout.dart';
@AutoRouterConfig(replaceInRouteName: 'Screen|Page,Route')
class AutoRouter extends RootStackRouter {
AutoRouter({
required this.layout,
required this.ref,
});
final WidgetRef ref;
final ScreenLayout layout;
@override
List<AutoRouteGuard> get guards => [...super.guards, AuthGuard(ref: ref)];
@override
RouteType get defaultRouteType => kIsWeb ? const RouteType.material() : const RouteType.adaptive();
@override
List<AutoRoute> get routes => [
..._defaultRoutes,
...(layout == ScreenLayout.dual ? desktopRoutes : mobileRoutes),
];
final List<AutoRoute> mobileRoutes = [
_homeRoute.copyWith(
children: [
_dashboardRoute,
_favouritesRoute,
_syncedRoute,
],
),
AutoRoute(page: DetailsRoute.page, path: '/details', usesPathAsKey: true),
AutoRoute(page: LibrarySearchRoute.page, path: '/library', usesPathAsKey: true),
AutoRoute(page: SettingsRoute.page, path: '/settings'),
..._settingsChildren.map(
(e) => e.copyWith(path: "/$e", initial: false),
),
AutoRoute(page: LockRoute.page, path: '/locked'),
];
final List<AutoRoute> desktopRoutes = [
_homeRoute.copyWith(
children: [
_dashboardRoute,
_favouritesRoute,
_syncedRoute,
AutoRoute(page: DetailsRoute.page, path: 'details', usesPathAsKey: true),
AutoRoute(page: LibrarySearchRoute.page, path: 'library', usesPathAsKey: true),
AutoRoute(
page: SettingsRoute.page,
path: 'settings',
children: _settingsChildren,
)
],
),
AutoRoute(page: LockRoute.page, path: '/locked'),
];
}
final List<AutoRoute> _defaultRoutes = [
AutoRoute(page: SplashRoute.page, path: '/splash'),
AutoRoute(page: LoginRoute.page, path: '/login'),
];
final AutoRoute _homeRoute = AutoRoute(page: HomeRoute.page, path: '/');
final AutoRoute _dashboardRoute = CustomRoute(
page: DashboardRoute.page,
transitionsBuilder: TransitionsBuilders.fadeIn,
initial: true,
path: 'dashboard',
maintainState: false,
);
final AutoRoute _favouritesRoute = CustomRoute(
page: FavouritesRoute.page,
transitionsBuilder: TransitionsBuilders.fadeIn,
path: 'favourites',
maintainState: false,
);
final AutoRoute _syncedRoute = CustomRoute(
page: SyncedRoute.page,
transitionsBuilder: TransitionsBuilders.fadeIn,
path: 'synced',
maintainState: false,
);
final List<AutoRoute> _settingsChildren = [
CustomRoute(
page: ClientSettingsRoute.page, initial: true, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'client'),
CustomRoute(page: SecuritySettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'security'),
CustomRoute(page: PlayerSettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'player'),
];
class LockScreenGuard extends AutoRouteGuard {
final WidgetRef ref;
const LockScreenGuard({required this.ref});
@override
Future<void> onNavigation(NavigationResolver resolver, StackRouter router) async {
if (ref.read(lockScreenActiveProvider) && resolver.routeName != const LockRoute().routeName) {
router.replace(const LockRoute());
return;
} else {
return resolver.next(true);
}
}
}
class AuthGuard extends AutoRouteGuard {
final WidgetRef ref;
const AuthGuard({required this.ref});
@override
Future<void> onNavigation(NavigationResolver resolver, StackRouter router) async {
if (ref.read(userProvider) != null ||
resolver.routeName == const LoginRoute().routeName ||
resolver.routeName == SplashRoute().routeName) {
return resolver.next(true);
}
resolver.redirect<bool>(SplashRoute(loggedIn: (value) {
if (value) {
resolver.next(true);
} else {
router.navigate(const LoginRoute());
}
}));
}
}

View file

@ -0,0 +1,445 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// AutoRouterGenerator
// **************************************************************************
// ignore_for_file: type=lint
// coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:auto_route/auto_route.dart' as _i14;
import 'package:fladder/models/item_base_model.dart' as _i15;
import 'package:fladder/models/items/photos_model.dart' as _i18;
import 'package:fladder/models/library_search/library_search_options.dart'
as _i17;
import 'package:fladder/routes/nested_details_screen.dart' as _i3;
import 'package:fladder/screens/dashboard/dashboard_screen.dart' as _i2;
import 'package:fladder/screens/favourites/favourites_screen.dart' as _i4;
import 'package:fladder/screens/home_screen.dart' as _i5;
import 'package:fladder/screens/library_search/library_search_screen.dart'
as _i6;
import 'package:fladder/screens/login/lock_screen.dart' as _i7;
import 'package:fladder/screens/login/login_screen.dart' as _i8;
import 'package:fladder/screens/settings/client_settings_page.dart' as _i1;
import 'package:fladder/screens/settings/player_settings_page.dart' as _i9;
import 'package:fladder/screens/settings/security_settings_page.dart' as _i10;
import 'package:fladder/screens/settings/settings_screen.dart' as _i11;
import 'package:fladder/screens/splash_screen.dart' as _i12;
import 'package:fladder/screens/syncing/synced_screen.dart' as _i13;
import 'package:flutter/material.dart' as _i16;
/// generated route for
/// [_i1.ClientSettingsPage]
class ClientSettingsRoute extends _i14.PageRouteInfo<void> {
const ClientSettingsRoute({List<_i14.PageRouteInfo>? children})
: super(
ClientSettingsRoute.name,
initialChildren: children,
);
static const String name = 'ClientSettingsRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i1.ClientSettingsPage();
},
);
}
/// generated route for
/// [_i2.DashboardScreen]
class DashboardRoute extends _i14.PageRouteInfo<void> {
const DashboardRoute({List<_i14.PageRouteInfo>? children})
: super(
DashboardRoute.name,
initialChildren: children,
);
static const String name = 'DashboardRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i2.DashboardScreen();
},
);
}
/// generated route for
/// [_i3.DetailsScreen]
class DetailsRoute extends _i14.PageRouteInfo<DetailsRouteArgs> {
DetailsRoute({
String id = '',
_i15.ItemBaseModel? item,
_i16.Key? key,
List<_i14.PageRouteInfo>? children,
}) : super(
DetailsRoute.name,
args: DetailsRouteArgs(
id: id,
item: item,
key: key,
),
rawQueryParams: {'id': id},
initialChildren: children,
);
static const String name = 'DetailsRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
final queryParams = data.queryParams;
final args = data.argsAs<DetailsRouteArgs>(
orElse: () => DetailsRouteArgs(
id: queryParams.getString(
'id',
'',
)));
return _i3.DetailsScreen(
id: args.id,
item: args.item,
key: args.key,
);
},
);
}
class DetailsRouteArgs {
const DetailsRouteArgs({
this.id = '',
this.item,
this.key,
});
final String id;
final _i15.ItemBaseModel? item;
final _i16.Key? key;
@override
String toString() {
return 'DetailsRouteArgs{id: $id, item: $item, key: $key}';
}
}
/// generated route for
/// [_i4.FavouritesScreen]
class FavouritesRoute extends _i14.PageRouteInfo<void> {
const FavouritesRoute({List<_i14.PageRouteInfo>? children})
: super(
FavouritesRoute.name,
initialChildren: children,
);
static const String name = 'FavouritesRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i4.FavouritesScreen();
},
);
}
/// generated route for
/// [_i5.HomeScreen]
class HomeRoute extends _i14.PageRouteInfo<void> {
const HomeRoute({List<_i14.PageRouteInfo>? children})
: super(
HomeRoute.name,
initialChildren: children,
);
static const String name = 'HomeRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i5.HomeScreen();
},
);
}
/// generated route for
/// [_i6.LibrarySearchScreen]
class LibrarySearchRoute extends _i14.PageRouteInfo<LibrarySearchRouteArgs> {
LibrarySearchRoute({
String? viewModelId,
List<String>? folderId,
bool? favourites,
_i17.SortingOrder? sortOrder,
_i17.SortingOptions? sortingOptions,
_i18.PhotoModel? photoToView,
_i16.Key? key,
List<_i14.PageRouteInfo>? children,
}) : super(
LibrarySearchRoute.name,
args: LibrarySearchRouteArgs(
viewModelId: viewModelId,
folderId: folderId,
favourites: favourites,
sortOrder: sortOrder,
sortingOptions: sortingOptions,
photoToView: photoToView,
key: key,
),
rawQueryParams: {
'parentId': viewModelId,
'folderId': folderId,
'favourites': favourites,
'sortOrder': sortOrder,
'sortOptions': sortingOptions,
},
initialChildren: children,
);
static const String name = 'LibrarySearchRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
final queryParams = data.queryParams;
final args = data.argsAs<LibrarySearchRouteArgs>(
orElse: () => LibrarySearchRouteArgs(
viewModelId: queryParams.optString('parentId'),
folderId: queryParams.optList('folderId'),
favourites: queryParams.optBool('favourites'),
sortOrder: queryParams.get('sortOrder'),
sortingOptions: queryParams.get('sortOptions'),
));
return _i6.LibrarySearchScreen(
viewModelId: args.viewModelId,
folderId: args.folderId,
favourites: args.favourites,
sortOrder: args.sortOrder,
sortingOptions: args.sortingOptions,
photoToView: args.photoToView,
key: args.key,
);
},
);
}
class LibrarySearchRouteArgs {
const LibrarySearchRouteArgs({
this.viewModelId,
this.folderId,
this.favourites,
this.sortOrder,
this.sortingOptions,
this.photoToView,
this.key,
});
final String? viewModelId;
final List<String>? folderId;
final bool? favourites;
final _i17.SortingOrder? sortOrder;
final _i17.SortingOptions? sortingOptions;
final _i18.PhotoModel? photoToView;
final _i16.Key? key;
@override
String toString() {
return 'LibrarySearchRouteArgs{viewModelId: $viewModelId, folderId: $folderId, favourites: $favourites, sortOrder: $sortOrder, sortingOptions: $sortingOptions, photoToView: $photoToView, key: $key}';
}
}
/// generated route for
/// [_i7.LockScreen]
class LockRoute extends _i14.PageRouteInfo<void> {
const LockRoute({List<_i14.PageRouteInfo>? children})
: super(
LockRoute.name,
initialChildren: children,
);
static const String name = 'LockRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i7.LockScreen();
},
);
}
/// generated route for
/// [_i8.LoginScreen]
class LoginRoute extends _i14.PageRouteInfo<void> {
const LoginRoute({List<_i14.PageRouteInfo>? children})
: super(
LoginRoute.name,
initialChildren: children,
);
static const String name = 'LoginRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i8.LoginScreen();
},
);
}
/// generated route for
/// [_i9.PlayerSettingsPage]
class PlayerSettingsRoute extends _i14.PageRouteInfo<void> {
const PlayerSettingsRoute({List<_i14.PageRouteInfo>? children})
: super(
PlayerSettingsRoute.name,
initialChildren: children,
);
static const String name = 'PlayerSettingsRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i9.PlayerSettingsPage();
},
);
}
/// generated route for
/// [_i10.SecuritySettingsPage]
class SecuritySettingsRoute extends _i14.PageRouteInfo<void> {
const SecuritySettingsRoute({List<_i14.PageRouteInfo>? children})
: super(
SecuritySettingsRoute.name,
initialChildren: children,
);
static const String name = 'SecuritySettingsRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i10.SecuritySettingsPage();
},
);
}
/// generated route for
/// [_i11.SettingsScreen]
class SettingsRoute extends _i14.PageRouteInfo<void> {
const SettingsRoute({List<_i14.PageRouteInfo>? children})
: super(
SettingsRoute.name,
initialChildren: children,
);
static const String name = 'SettingsRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
return const _i11.SettingsScreen();
},
);
}
/// generated route for
/// [_i12.SplashScreen]
class SplashRoute extends _i14.PageRouteInfo<SplashRouteArgs> {
SplashRoute({
dynamic Function(bool)? loggedIn,
_i16.Key? key,
List<_i14.PageRouteInfo>? children,
}) : super(
SplashRoute.name,
args: SplashRouteArgs(
loggedIn: loggedIn,
key: key,
),
initialChildren: children,
);
static const String name = 'SplashRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
final args =
data.argsAs<SplashRouteArgs>(orElse: () => const SplashRouteArgs());
return _i12.SplashScreen(
loggedIn: args.loggedIn,
key: args.key,
);
},
);
}
class SplashRouteArgs {
const SplashRouteArgs({
this.loggedIn,
this.key,
});
final dynamic Function(bool)? loggedIn;
final _i16.Key? key;
@override
String toString() {
return 'SplashRouteArgs{loggedIn: $loggedIn, key: $key}';
}
}
/// generated route for
/// [_i13.SyncedScreen]
class SyncedRoute extends _i14.PageRouteInfo<SyncedRouteArgs> {
SyncedRoute({
_i16.ScrollController? navigationScrollController,
_i16.Key? key,
List<_i14.PageRouteInfo>? children,
}) : super(
SyncedRoute.name,
args: SyncedRouteArgs(
navigationScrollController: navigationScrollController,
key: key,
),
initialChildren: children,
);
static const String name = 'SyncedRoute';
static _i14.PageInfo page = _i14.PageInfo(
name,
builder: (data) {
final args =
data.argsAs<SyncedRouteArgs>(orElse: () => const SyncedRouteArgs());
return _i13.SyncedScreen(
navigationScrollController: args.navigationScrollController,
key: args.key,
);
},
);
}
class SyncedRouteArgs {
const SyncedRouteArgs({
this.navigationScrollController,
this.key,
});
final _i16.ScrollController? navigationScrollController;
final _i16.Key? key;
@override
String toString() {
return 'SyncedRouteArgs{navigationScrollController: $navigationScrollController, key: $key}';
}
}

View file

@ -1,182 +0,0 @@
import 'package:animations/animations.dart';
import 'package:collection/collection.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/library_search/library_search_options.dart';
import 'package:fladder/routes/app_routes.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/screens/dashboard/dashboard_screen.dart';
import 'package:fladder/screens/syncing/synced_screen.dart';
import 'package:fladder/screens/favourites/favourites_screen.dart';
import 'package:fladder/screens/library_search/library_search_screen.dart';
import 'package:fladder/screens/shared/detail_scaffold.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class DashboardRoute extends CustomRoute {
DashboardRoute() : super(name: 'Dashboard', basePath: '/dashboard');
@override
GoRoute get go => GoRoute(
path: basePath,
parentNavigatorKey: AppRoutes.homeShellKey,
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: DashboardScreen(
key: Key(basePath),
navigationScrollController: AppRoutes.scrollController,
),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
});
},
);
}
class FavouritesRoute extends CustomRoute {
FavouritesRoute() : super(name: 'Favorites', basePath: '/favorites');
@override
GoRoute get go => GoRoute(
path: basePath,
parentNavigatorKey: AppRoutes.homeShellKey,
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: FavouritesScreen(
key: Key(basePath),
navigationScrollController: AppRoutes.scrollController,
),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
});
},
);
}
class SyncRoute extends CustomRoute {
SyncRoute() : super(name: 'Sync', basePath: '/sync');
@override
GoRoute get go => GoRoute(
path: basePath,
parentNavigatorKey: AppRoutes.homeShellKey,
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: SyncedScreen(
key: Key(basePath),
navigationScrollController: AppRoutes.scrollController,
),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
});
},
);
}
class DetailsRoute extends CustomRoute {
final String? id;
final GlobalKey<NavigatorState>? navKey;
DetailsRoute({this.id, this.navKey}) : super(name: 'Details', basePath: '/details', arguments: ':itemId');
@override
String get route => "$basePath/$id";
@override
GoRoute get go => GoRoute(
path: path,
parentNavigatorKey: navKey,
pageBuilder: (context, state) {
final String id = state.pathParameters['itemId'] ?? "nothing";
ItemBaseModel? item = state.extra as ItemBaseModel?;
return CustomTransitionPage(
key: state.pageKey,
child: DetailScreen(
key: Key(id),
id: id,
item: item,
),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeThroughTransition(
animation: animation,
secondaryAnimation: secondaryAnimation,
child: child,
);
},
);
},
);
}
const _libraryKey = "libraryId";
const _sortOptionsKey = "sortOptions";
const _sortOrderKey = "sortOrder";
const _favoriteKey = "favorite";
const _folderKey = "folder";
class LibrarySearchRoute extends CustomRoute {
final String? id;
final bool? favorites;
final SortingOptions? sortOptions;
final SortOrder? sortOrder;
final String? folderId;
final GlobalKey<NavigatorState>? navKey;
LibrarySearchRoute({this.id, this.favorites, this.sortOptions, this.sortOrder, this.folderId, this.navKey})
: super(
name: 'LibrarySearch',
basePath: '/library',
queryParameters: {
_libraryKey: id,
_sortOptionsKey: sortOptions?.name,
_sortOrderKey: sortOrder?.name,
_favoriteKey: favorites,
_folderKey: folderId,
},
);
@override
String get route => "$basePath${parseUrlParameters(queryParameters)}";
@override
GoRoute get go => GoRoute(
path: basePath,
parentNavigatorKey: navKey,
pageBuilder: (context, state) {
final String? id = state.uri.queryParameters[_libraryKey];
final bool? favourites = bool.tryParse(state.uri.queryParameters[_favoriteKey] ?? "");
final String? folderId = state.uri.queryParameters[_folderKey];
final SortingOptions? sortingOptions = SortingOptions.values
.firstWhereOrNull((element) => element.name == state.uri.queryParameters[_sortOptionsKey]);
final SortingOrder? sortOrder = SortingOrder.values
.firstWhereOrNull((element) => element.name == state.uri.queryParameters[_sortOrderKey]);
return CustomTransitionPage(
key: state.pageKey,
child: LibrarySearchScreen(
key: Key(id ?? "librarySearch"),
viewModelId: id,
folderId: folderId?.split(','),
sortingOptions: sortingOptions,
sortOrder: sortOrder,
favourites: favourites,
),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
},
);
},
);
}

View file

@ -1,124 +0,0 @@
import 'package:fladder/screens/login/login_screen.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:fladder/routes/app_routes.dart';
import 'package:fladder/screens/login/lock_screen.dart';
import 'package:fladder/screens/splash_screen.dart';
import 'package:fladder/util/adaptive_layout.dart';
extension RouteContextBuilder on BuildContext {
/// Push a location onto the page stack.
Future<T?> routePush<T>(CustomRoute route, {Object? extra}) async {
return push(route.route, extra: extra);
}
/// Replaces the top-most page of the page stack with the given URL location
void routeReplace(CustomRoute route, {Object? extra}) {
replace(route.route, extra: extra);
}
/// Navigate to a location.
void routeGo(CustomRoute route, {Object? extra}) {
go(route.route, extra: extra);
}
/// [Pushed] if nested(single) else [Go]
void routePushOrGo(CustomRoute route, {Object? extra}) {
switch (AdaptiveLayout.of(this).size) {
case ScreenLayout.single:
routePush(route, extra: extra);
break;
case ScreenLayout.dual:
routeGo(route, extra: extra);
break;
}
}
/// [Push] if nested(single) else [Replace]
void routeReplaceOrPush(CustomRoute route, {Object? extra}) {
switch (AdaptiveLayout.of(this).size) {
case ScreenLayout.single:
routePush(route, extra: extra);
break;
case ScreenLayout.dual:
routeReplace(route, extra: extra);
break;
}
}
}
abstract class CustomRoute {
final String name;
final String basePath;
final String? arguments;
final Map<String, dynamic>? queryParameters;
CustomRoute({required this.name, required this.basePath, this.arguments, this.queryParameters})
: assert(basePath.isNotEmpty, 'GoRoute path cannot be empty');
String get route => basePath;
String get path => "$basePath/${arguments ?? queryParameters}";
void navigate() {
AppRoutes.parentKey.currentContext?.routeGo(this);
}
void replaceRoot() {
AppRoutes.parentKey.currentContext?.routeReplace(this);
}
RouteBase get go;
}
class LoginRoute extends CustomRoute {
LoginRoute() : super(name: 'Login', basePath: '/login');
@override
GoRoute get go => GoRoute(
path: basePath,
builder: (context, state) {
return const LoginScreen();
},
);
}
class SplashRoute extends CustomRoute {
SplashRoute() : super(name: 'Splash', basePath: '/splash');
@override
GoRoute get go => GoRoute(
path: basePath,
builder: (context, state) {
return const SplashScreen();
},
);
}
class LockScreenRoute extends CustomRoute {
final bool selfLock;
LockScreenRoute({this.selfLock = false}) : super(name: 'Lock', basePath: '/lock');
@override
GoRoute get go => GoRoute(
parentNavigatorKey: AppRoutes.parentKey,
path: basePath,
builder: (context, state) {
return LockScreen(
selfLock: selfLock,
);
},
);
}
String parseUrlParameters(Map<String, dynamic>? parameters) {
if (parameters == null) return '';
String parameterString = '?';
for (String key in parameters.keys) {
if (parameters[key] != null) parameterString += '$key=${parameters[key]}&';
}
return parameterString.substring(0, parameterString.length - 1);
}

View file

@ -1,103 +0,0 @@
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/screens/settings/client_settings_page.dart';
import 'package:fladder/screens/settings/player_settings_page.dart';
import 'package:fladder/screens/settings/settings_screen.dart';
import 'package:fladder/screens/settings/security_settings_page.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class SettingsRoute extends CustomRoute {
final String? id;
final GlobalKey<NavigatorState>? navKey;
SettingsRoute({this.id, this.navKey}) : super(name: 'Settings', basePath: '/settings');
@override
GoRoute get go => GoRoute(
path: basePath,
parentNavigatorKey: navKey,
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: const SettingsScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
},
);
},
);
}
class ClientSettingsRoute extends CustomRoute {
final String? id;
final GlobalKey<NavigatorState>? navKey;
ClientSettingsRoute({this.id, this.navKey}) : super(name: 'ClientSettings', basePath: '/settings/client');
@override
GoRoute get go => GoRoute(
path: basePath,
parentNavigatorKey: navKey,
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: const ClientSettingsPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
},
);
},
);
}
class SecuritySettingsRoute extends CustomRoute {
final String? id;
final GlobalKey<NavigatorState>? navKey;
SecuritySettingsRoute({this.id, this.navKey}) : super(name: 'SecuritySettings', basePath: '/settings/security');
@override
GoRoute get go => GoRoute(
path: basePath,
parentNavigatorKey: navKey,
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: const SecuritySettingsPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
},
);
},
);
}
class PlayerSettingsRoute extends CustomRoute {
final String? id;
final GlobalKey<NavigatorState>? navKey;
PlayerSettingsRoute({this.id, this.navKey}) : super(name: 'PlayerSettings', basePath: '/settings/player');
@override
GoRoute get go => GoRoute(
path: basePath,
parentNavigatorKey: navKey,
pageBuilder: (context, state) {
return CustomTransitionPage(
key: state.pageKey,
child: const PlayerSettingsPage(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(
opacity: CurveTween(curve: Curves.easeInOutCirc).animate(animation),
child: child,
);
},
);
},
);
}

View file

@ -0,0 +1,86 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/providers/items/item_details_provider.dart';
import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/util/fladder_image.dart';
@RoutePage()
class DetailsScreen extends ConsumerStatefulWidget {
final String id;
final ItemBaseModel? item;
const DetailsScreen({@QueryParam() this.id = '', this.item, super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _DetailsScreenState();
}
class _DetailsScreenState extends ConsumerState<DetailsScreen> {
late Widget currentWidget = const Center(
key: Key("progress-indicator"),
child: CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round),
);
@override
void didUpdateWidget(covariant DetailsScreen oldWidget) {
super.didUpdateWidget(oldWidget);
if (kIsWeb) {
updateWidget();
}
}
@override
void initState() {
super.initState();
updateWidget();
}
Future<void> updateWidget() async {
Future.microtask(() async {
if (widget.item != null) {
setState(() {
currentWidget = widget.item!.detailScreenWidget;
});
} else {
final response = await ref.read(itemDetailsProvider.notifier).fetchDetails(widget.id);
if (context.mounted) {
if (response != null) {
setState(() {
currentWidget = response.detailScreenWidget;
});
} else {
const DashboardRoute().navigate(context);
}
}
}
});
}
@override
Widget build(BuildContext context) {
return Stack(
key: Key(widget.id),
children: [
Hero(
tag: widget.id,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface.withOpacity(1.0),
),
//Small offset to match detailscaffold
child: Transform.translate(
offset: const Offset(0, -5), child: FladderImage(image: widget.item?.getPosters?.primary)),
),
),
AnimatedSwitcher(
duration: const Duration(seconds: 1),
child: currentWidget,
)
],
);
}
}

View file

@ -1,6 +1,11 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/library_search/library_search_options.dart'; import 'package:fladder/models/library_search/library_search_options.dart';
@ -10,8 +15,7 @@ import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/providers/settings/home_settings_provider.dart'; import 'package:fladder/providers/settings/home_settings_provider.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/providers/views_provider.dart'; import 'package:fladder/providers/views_provider.dart';
import 'package:fladder/routes/build_routes/home_routes.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/screens/shared/media/carousel_banner.dart'; import 'package:fladder/screens/shared/media/carousel_banner.dart';
import 'package:fladder/screens/shared/media/poster_row.dart'; import 'package:fladder/screens/shared/media/poster_row.dart';
import 'package:fladder/screens/shared/nested_scaffold.dart'; import 'package:fladder/screens/shared/nested_scaffold.dart';
@ -23,13 +27,10 @@ import 'package:fladder/util/sliver_list_padding.dart';
import 'package:fladder/widgets/shared/pinch_poster_zoom.dart'; import 'package:fladder/widgets/shared/pinch_poster_zoom.dart';
import 'package:fladder/widgets/shared/poster_size_slider.dart'; import 'package:fladder/widgets/shared/poster_size_slider.dart';
import 'package:fladder/widgets/shared/pull_to_refresh.dart'; import 'package:fladder/widgets/shared/pull_to_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@RoutePage()
class DashboardScreen extends ConsumerStatefulWidget { class DashboardScreen extends ConsumerStatefulWidget {
final ScrollController navigationScrollController;
const DashboardScreen({ const DashboardScreen({
required this.navigationScrollController,
super.key, super.key,
}); });
@ -91,8 +92,8 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
child: PinchPosterZoom( child: PinchPosterZoom(
scaleDifference: (difference) => ref.read(clientSettingsProvider.notifier).addPosterSize(difference), scaleDifference: (difference) => ref.read(clientSettingsProvider.notifier).addPosterSize(difference),
child: CustomScrollView( child: CustomScrollView(
controller: AdaptiveLayout.scrollOf(context),
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
controller: widget.navigationScrollController,
slivers: [ slivers: [
if (AdaptiveLayout.of(context).layout == LayoutState.phone) if (AdaptiveLayout.of(context).layout == LayoutState.phone)
NestedSliverAppBar( NestedSliverAppBar(
@ -175,9 +176,9 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
.map((view) => SliverToBoxAdapter( .map((view) => SliverToBoxAdapter(
child: PosterRow( child: PosterRow(
label: context.localized.dashboardRecentlyAdded(view.name), label: context.localized.dashboardRecentlyAdded(view.name),
onLabelClick: () => context.routePushOrGo(LibrarySearchRoute( onLabelClick: () => context.router.push(LibrarySearchRoute(
id: view.id, viewModelId: view.id,
sortOptions: switch (view.collectionType) { sortingOptions: switch (view.collectionType) {
CollectionType.tvshows || CollectionType.tvshows ||
CollectionType.books || CollectionType.books ||
CollectionType.boxsets || CollectionType.boxsets ||
@ -186,7 +187,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
SortingOptions.dateLastContentAdded, SortingOptions.dateLastContentAdded,
_ => SortingOptions.dateAdded, _ => SortingOptions.dateAdded,
}, },
sortOrder: SortOrder.descending, sortOrder: SortingOrder.descending,
)), )),
posters: view.recentlyAdded, posters: view.recentlyAdded,
), ),

View file

@ -1,4 +1,9 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/book_model.dart'; import 'package:fladder/models/book_model.dart';
import 'package:fladder/providers/items/book_details_provider.dart'; import 'package:fladder/providers/items/book_details_provider.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
@ -12,13 +17,11 @@ import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
import 'package:fladder/util/item_base_model/play_item_helpers.dart'; import 'package:fladder/util/item_base_model/play_item_helpers.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/router_extension.dart';
import 'package:fladder/util/widget_extensions.dart'; import 'package:fladder/util/widget_extensions.dart';
import 'package:fladder/widgets/shared/item_actions.dart'; import 'package:fladder/widgets/shared/item_actions.dart';
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart'; import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
import 'package:fladder/widgets/shared/selectable_icon_button.dart'; import 'package:fladder/widgets/shared/selectable_icon_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
class BookDetailScreen extends ConsumerStatefulWidget { class BookDetailScreen extends ConsumerStatefulWidget {
final BookModel item; final BookModel item;
@ -29,7 +32,8 @@ class BookDetailScreen extends ConsumerStatefulWidget {
} }
class _BookDetailScreenState extends ConsumerState<BookDetailScreen> { class _BookDetailScreenState extends ConsumerState<BookDetailScreen> {
late final provider = bookDetailsProvider(widget.item.id); AutoDisposeStateNotifierProvider<BookDetailsProviderNotifier, BookProviderModel> get provider =>
bookDetailsProvider(widget.item.id);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -47,7 +51,7 @@ class _BookDetailScreenState extends ConsumerState<BookDetailScreen> {
}, },
onDeleteSuccesFully: (item) { onDeleteSuccesFully: (item) {
if (context.mounted) { if (context.mounted) {
context.pop(); context.router.popBack();
} }
}, },
), ),

View file

@ -1,27 +1,28 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/screens/details_screens/components/overview_header.dart';
import 'package:fladder/screens/shared/media/components/media_play_button.dart';
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
import 'package:fladder/util/item_base_model/play_item_helpers.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/providers/items/episode_details_provider.dart'; import 'package:fladder/providers/items/episode_details_provider.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/screens/details_screens/components/media_stream_information.dart'; import 'package:fladder/screens/details_screens/components/media_stream_information.dart';
import 'package:fladder/screens/details_screens/components/overview_header.dart';
import 'package:fladder/screens/shared/detail_scaffold.dart'; import 'package:fladder/screens/shared/detail_scaffold.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/screens/shared/media/chapter_row.dart'; import 'package:fladder/screens/shared/media/chapter_row.dart';
import 'package:fladder/screens/shared/media/components/media_header.dart'; import 'package:fladder/screens/shared/media/components/media_header.dart';
import 'package:fladder/screens/shared/media/components/media_play_button.dart';
import 'package:fladder/screens/shared/media/episode_posters.dart'; import 'package:fladder/screens/shared/media/episode_posters.dart';
import 'package:fladder/screens/shared/media/expanding_overview.dart'; import 'package:fladder/screens/shared/media/expanding_overview.dart';
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
import 'package:fladder/util/item_base_model/play_item_helpers.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/router_extension.dart';
import 'package:fladder/util/widget_extensions.dart'; import 'package:fladder/util/widget_extensions.dart';
import 'package:fladder/widgets/shared/selectable_icon_button.dart'; import 'package:fladder/widgets/shared/selectable_icon_button.dart';
import 'package:go_router/go_router.dart';
class EpisodeDetailScreen extends ConsumerStatefulWidget { class EpisodeDetailScreen extends ConsumerStatefulWidget {
final ItemBaseModel item; final ItemBaseModel item;
@ -32,7 +33,8 @@ class EpisodeDetailScreen extends ConsumerStatefulWidget {
} }
class _ItemDetailScreenState extends ConsumerState<EpisodeDetailScreen> { class _ItemDetailScreenState extends ConsumerState<EpisodeDetailScreen> {
late final providerInstance = episodeDetailsProvider(widget.item.id); AutoDisposeStateNotifierProvider<EpisodeDetailsProvider, EpisodeDetailModel> get providerInstance =>
episodeDetailsProvider(widget.item.id);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -52,7 +54,7 @@ class _ItemDetailScreenState extends ConsumerState<EpisodeDetailScreen> {
}, },
onDeleteSuccesFully: (item) { onDeleteSuccesFully: (item) {
if (context.mounted) { if (context.mounted) {
context.pop(); context.router.popBack();
} }
}, },
), ),

View file

@ -1,25 +1,27 @@
import 'package:fladder/util/router_extension.dart';
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/providers/items/movies_details_provider.dart'; import 'package:fladder/providers/items/movies_details_provider.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/screens/details_screens/components/overview_header.dart';
import 'package:fladder/screens/details_screens/components/media_stream_information.dart'; import 'package:fladder/screens/details_screens/components/media_stream_information.dart';
import 'package:fladder/screens/shared/media/components/media_header.dart'; import 'package:fladder/screens/details_screens/components/overview_header.dart';
import 'package:fladder/screens/shared/detail_scaffold.dart'; import 'package:fladder/screens/shared/detail_scaffold.dart';
import 'package:fladder/screens/shared/media/chapter_row.dart'; import 'package:fladder/screens/shared/media/chapter_row.dart';
import 'package:fladder/screens/shared/media/components/media_header.dart';
import 'package:fladder/screens/shared/media/components/media_play_button.dart'; import 'package:fladder/screens/shared/media/components/media_play_button.dart';
import 'package:fladder/screens/shared/media/expanding_overview.dart'; import 'package:fladder/screens/shared/media/expanding_overview.dart';
import 'package:fladder/screens/shared/media/people_row.dart'; import 'package:fladder/screens/shared/media/people_row.dart';
import 'package:fladder/screens/shared/media/poster_row.dart'; import 'package:fladder/screens/shared/media/poster_row.dart';
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart'; import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
import 'package:fladder/util/item_base_model/play_item_helpers.dart'; import 'package:fladder/util/item_base_model/play_item_helpers.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/widget_extensions.dart'; import 'package:fladder/util/widget_extensions.dart';
import 'package:fladder/widgets/shared/selectable_icon_button.dart'; import 'package:fladder/widgets/shared/selectable_icon_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
class MovieDetailScreen extends ConsumerStatefulWidget { class MovieDetailScreen extends ConsumerStatefulWidget {
final ItemBaseModel item; final ItemBaseModel item;
@ -30,7 +32,7 @@ class MovieDetailScreen extends ConsumerStatefulWidget {
} }
class _ItemDetailScreenState extends ConsumerState<MovieDetailScreen> { class _ItemDetailScreenState extends ConsumerState<MovieDetailScreen> {
late final providerInstance = movieDetailsProvider(widget.item.id); MovieDetailsProvider get providerInstance => movieDetailsProvider(widget.item.id);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -49,7 +51,7 @@ class _ItemDetailScreenState extends ConsumerState<MovieDetailScreen> {
}, },
onDeleteSuccesFully: (item) { onDeleteSuccesFully: (item) {
if (context.mounted) { if (context.mounted) {
context.pop(); context.router.popBack();
} }
}, },
), ),

View file

@ -1,5 +1,10 @@
import 'package:flutter/material.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/season_model.dart';
import 'package:fladder/providers/items/season_details_provider.dart'; import 'package:fladder/providers/items/season_details_provider.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/screens/details_screens/components/overview_header.dart'; import 'package:fladder/screens/details_screens/components/overview_header.dart';
@ -16,8 +21,6 @@ import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/util/string_extensions.dart';
import 'package:fladder/util/widget_extensions.dart'; import 'package:fladder/util/widget_extensions.dart';
import 'package:fladder/widgets/shared/selectable_icon_button.dart'; import 'package:fladder/widgets/shared/selectable_icon_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class SeasonDetailScreen extends ConsumerStatefulWidget { class SeasonDetailScreen extends ConsumerStatefulWidget {
final ItemBaseModel item; final ItemBaseModel item;
@ -29,7 +32,8 @@ class SeasonDetailScreen extends ConsumerStatefulWidget {
class _SeasonDetailScreenState extends ConsumerState<SeasonDetailScreen> { class _SeasonDetailScreenState extends ConsumerState<SeasonDetailScreen> {
Set<EpisodeDetailsViewType> viewOptions = {EpisodeDetailsViewType.grid}; Set<EpisodeDetailsViewType> viewOptions = {EpisodeDetailsViewType.grid};
late final providerId = seasonDetailsProvider(widget.item.id); AutoDisposeStateNotifierProvider<SeasonDetailsNotifier, SeasonModel?> get providerId =>
seasonDetailsProvider(widget.item.id);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View file

@ -1,28 +1,30 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/screens/details_screens/components/overview_header.dart';
import 'package:fladder/screens/shared/media/components/media_play_button.dart';
import 'package:fladder/screens/shared/media/components/next_up_episode.dart';
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
import 'package:fladder/util/item_base_model/play_item_helpers.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/series_model.dart';
import 'package:fladder/providers/items/series_details_provider.dart'; import 'package:fladder/providers/items/series_details_provider.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/screens/details_screens/components/overview_header.dart';
import 'package:fladder/screens/shared/detail_scaffold.dart'; import 'package:fladder/screens/shared/detail_scaffold.dart';
import 'package:fladder/screens/shared/media/components/media_header.dart'; import 'package:fladder/screens/shared/media/components/media_header.dart';
import 'package:fladder/screens/shared/media/components/media_play_button.dart';
import 'package:fladder/screens/shared/media/components/next_up_episode.dart';
import 'package:fladder/screens/shared/media/episode_posters.dart'; import 'package:fladder/screens/shared/media/episode_posters.dart';
import 'package:fladder/screens/shared/media/expanding_overview.dart'; import 'package:fladder/screens/shared/media/expanding_overview.dart';
import 'package:fladder/screens/shared/media/people_row.dart'; import 'package:fladder/screens/shared/media/people_row.dart';
import 'package:fladder/screens/shared/media/poster_row.dart'; import 'package:fladder/screens/shared/media/poster_row.dart';
import 'package:fladder/screens/shared/media/season_row.dart'; import 'package:fladder/screens/shared/media/season_row.dart';
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
import 'package:fladder/util/item_base_model/play_item_helpers.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/router_extension.dart';
import 'package:fladder/util/widget_extensions.dart'; import 'package:fladder/util/widget_extensions.dart';
import 'package:fladder/widgets/shared/selectable_icon_button.dart'; import 'package:fladder/widgets/shared/selectable_icon_button.dart';
import 'package:go_router/go_router.dart';
class SeriesDetailScreen extends ConsumerStatefulWidget { class SeriesDetailScreen extends ConsumerStatefulWidget {
final ItemBaseModel item; final ItemBaseModel item;
@ -33,7 +35,8 @@ class SeriesDetailScreen extends ConsumerStatefulWidget {
} }
class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> { class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
late final providerId = seriesDetailsProvider(widget.item.id); AutoDisposeStateNotifierProvider<SeriesDetailViewNotifier, SeriesModel?> get providerId =>
seriesDetailsProvider(widget.item.id);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -51,7 +54,7 @@ class _SeriesDetailScreenState extends ConsumerState<SeriesDetailScreen> {
}, },
onDeleteSuccesFully: (item) { onDeleteSuccesFully: (item) {
if (context.mounted) { if (context.mounted) {
context.pop(); context.router.popBack();
} }
}, },
), ),

View file

@ -1,5 +1,6 @@
import 'package:auto_route/auto_route.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/routes/build_routes/home_routes.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/screens/shared/nested_scaffold.dart'; import 'package:fladder/screens/shared/nested_scaffold.dart';
import 'package:fladder/screens/shared/nested_sliver_appbar.dart'; import 'package:fladder/screens/shared/nested_sliver_appbar.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
@ -14,10 +15,9 @@ import 'package:fladder/screens/shared/media/poster_grid.dart';
import 'package:fladder/util/sliver_list_padding.dart'; import 'package:fladder/util/sliver_list_padding.dart';
import 'package:fladder/widgets/shared/pull_to_refresh.dart'; import 'package:fladder/widgets/shared/pull_to_refresh.dart';
@RoutePage()
class FavouritesScreen extends ConsumerWidget { class FavouritesScreen extends ConsumerWidget {
final ScrollController navigationScrollController; const FavouritesScreen({super.key});
const FavouritesScreen({required this.navigationScrollController, super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@ -30,13 +30,13 @@ class FavouritesScreen extends ConsumerWidget {
scaleDifference: (difference) => ref.read(clientSettingsProvider.notifier).addPosterSize(difference / 2), scaleDifference: (difference) => ref.read(clientSettingsProvider.notifier).addPosterSize(difference / 2),
child: CustomScrollView( child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
controller: navigationScrollController, controller: AdaptiveLayout.scrollOf(context),
slivers: [ slivers: [
if (AdaptiveLayout.of(context).layout == LayoutState.phone) if (AdaptiveLayout.of(context).layout == LayoutState.phone)
NestedSliverAppBar( NestedSliverAppBar(
searchTitle: "${context.localized.search} ${context.localized.favorites.toLowerCase()}...", searchTitle: "${context.localized.search} ${context.localized.favorites.toLowerCase()}...",
parent: context, parent: context,
route: LibrarySearchRoute(favorites: true), route: LibrarySearchRoute(favourites: true),
) )
else else
const DefaultSliverTopBadding(), const DefaultSliverTopBadding(),

View file

@ -1,13 +1,13 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/routes/build_routes/home_routes.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/route_builder.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/util/string_extensions.dart';
import 'package:fladder/widgets/navigation_scaffold/components/adaptive_fab.dart'; import 'package:fladder/widgets/navigation_scaffold/components/adaptive_fab.dart';
import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart'; import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart';
@ -19,11 +19,9 @@ enum HomeTabs {
sync; sync;
} }
class Home extends ConsumerWidget { @RoutePage()
final HomeTabs? currentTab; class HomeScreen extends ConsumerWidget {
final Widget? nestedChild; const HomeScreen({super.key});
final String? location;
const Home({this.currentTab, this.nestedChild, this.location, super.key});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@ -35,13 +33,13 @@ class Home extends ConsumerWidget {
label: context.localized.navigationDashboard, label: context.localized.navigationDashboard,
icon: const Icon(IconsaxOutline.home), icon: const Icon(IconsaxOutline.home),
selectedIcon: const Icon(IconsaxBold.home), selectedIcon: const Icon(IconsaxBold.home),
route: DashboardRoute(), route: const DashboardRoute(),
action: () => context.routeGo(DashboardRoute()), action: () => context.router.navigate(const DashboardRoute()),
floatingActionButton: AdaptiveFab( floatingActionButton: AdaptiveFab(
context: context, context: context,
title: context.localized.search, title: context.localized.search,
key: Key(e.name.capitalize()), key: Key(e.name.capitalize()),
onPressed: () => context.routePushOrGo(LibrarySearchRoute()), onPressed: () => context.router.navigate(LibrarySearchRoute()),
child: const Icon(IconsaxOutline.search_normal_1), child: const Icon(IconsaxOutline.search_normal_1),
), ),
); );
@ -50,15 +48,15 @@ class Home extends ConsumerWidget {
label: context.localized.navigationFavorites, label: context.localized.navigationFavorites,
icon: const Icon(IconsaxOutline.heart), icon: const Icon(IconsaxOutline.heart),
selectedIcon: const Icon(IconsaxBold.heart), selectedIcon: const Icon(IconsaxBold.heart),
route: FavouritesRoute(), route: const FavouritesRoute(),
floatingActionButton: AdaptiveFab( floatingActionButton: AdaptiveFab(
context: context, context: context,
title: context.localized.filter(0), title: context.localized.filter(0),
key: Key(e.name.capitalize()), key: Key(e.name.capitalize()),
onPressed: () => context.routePushOrGo(LibrarySearchRoute(favorites: true)), onPressed: () => context.router.navigate(LibrarySearchRoute(favourites: true)),
child: const Icon(IconsaxOutline.heart_search), child: const Icon(IconsaxOutline.heart_search),
), ),
action: () => context.routeGo(FavouritesRoute()), action: () => context.router.navigate(const FavouritesRoute()),
); );
case HomeTabs.sync: case HomeTabs.sync:
if (canDownload) { if (canDownload) {
@ -66,8 +64,8 @@ class Home extends ConsumerWidget {
label: context.localized.navigationSync, label: context.localized.navigationSync,
icon: const Icon(IconsaxOutline.cloud), icon: const Icon(IconsaxOutline.cloud),
selectedIcon: const Icon(IconsaxBold.cloud), selectedIcon: const Icon(IconsaxBold.cloud),
route: SyncRoute(), route: SyncedRoute(),
action: () => context.routeGo(SyncRoute()), action: () => context.router.navigate(SyncedRoute()),
); );
} }
return null; return null;
@ -75,12 +73,17 @@ class Home extends ConsumerWidget {
return null; return null;
} }
}); });
return HeroControllerScope(
return NavigationScaffold( controller: HeroController(),
currentIndex: currentTab?.index ?? 0, child: AutoRouter(
location: location, builder: (context, child) {
nestedChild: nestedChild, return NavigationScaffold(
destinations: destinations.whereNotNull().toList(), destinations: destinations.whereNotNull().toList(),
currentRouteName: context.router.current.name,
nestedChild: child,
);
},
),
); );
} }
} }

View file

@ -1,6 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -30,6 +32,7 @@ import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/refresh_state.dart'; import 'package:fladder/util/refresh_state.dart';
import 'package:fladder/util/router_extension.dart';
import 'package:fladder/util/sliver_list_padding.dart'; import 'package:fladder/util/sliver_list_padding.dart';
import 'package:fladder/widgets/navigation_scaffold/components/floating_player_bar.dart'; import 'package:fladder/widgets/navigation_scaffold/components/floating_player_bar.dart';
import 'package:fladder/widgets/navigation_scaffold/components/settings_user_icon.dart'; import 'package:fladder/widgets/navigation_scaffold/components/settings_user_icon.dart';
@ -43,6 +46,7 @@ import 'package:fladder/widgets/shared/pull_to_refresh.dart';
import 'package:fladder/widgets/shared/scroll_position.dart'; import 'package:fladder/widgets/shared/scroll_position.dart';
import 'package:fladder/widgets/shared/shapes.dart'; import 'package:fladder/widgets/shared/shapes.dart';
@RoutePage()
class LibrarySearchScreen extends ConsumerStatefulWidget { class LibrarySearchScreen extends ConsumerStatefulWidget {
final String? viewModelId; final String? viewModelId;
final bool? favourites; final bool? favourites;
@ -51,11 +55,11 @@ class LibrarySearchScreen extends ConsumerStatefulWidget {
final SortingOptions? sortingOptions; final SortingOptions? sortingOptions;
final PhotoModel? photoToView; final PhotoModel? photoToView;
const LibrarySearchScreen({ const LibrarySearchScreen({
this.viewModelId, @QueryParam("parentId") this.viewModelId,
this.folderId, @QueryParam("folderId") this.folderId,
this.favourites, @QueryParam("favourites") this.favourites,
this.sortOrder, @QueryParam("sortOrder") this.sortOrder,
this.sortingOptions, @QueryParam("sortOptions") this.sortingOptions,
this.photoToView, this.photoToView,
super.key, super.key,
}); });
@ -65,9 +69,6 @@ class LibrarySearchScreen extends ConsumerStatefulWidget {
} }
class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> { class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
late final Key uniqueKey = Key(widget.folderId?.join(',').toString() ?? widget.viewModelId ?? UniqueKey().toString());
late final providerKey = librarySearchProvider(uniqueKey);
late final libraryProvider = ref.read(providerKey.notifier);
final SearchController searchController = SearchController(); final SearchController searchController = SearchController();
final Debouncer debouncer = Debouncer(const Duration(seconds: 1)); final Debouncer debouncer = Debouncer(const Duration(seconds: 1));
final GlobalKey<RefreshIndicatorState> refreshKey = GlobalKey<RefreshIndicatorState>(); final GlobalKey<RefreshIndicatorState> refreshKey = GlobalKey<RefreshIndicatorState>();
@ -76,9 +77,26 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
bool loadOnStart = false; bool loadOnStart = false;
Key get uniqueKey => Key(widget.folderId?.join(',').toString() ?? widget.viewModelId ?? "EmptySearch");
AutoDisposeStateNotifierProvider<LibrarySearchNotifier, LibrarySearchModel> get providerKey =>
librarySearchProvider(uniqueKey);
LibrarySearchNotifier get libraryProvider => ref.read(librarySearchProvider(uniqueKey).notifier);
@override
void didUpdateWidget(covariant LibrarySearchScreen oldWidget) {
super.didUpdateWidget(oldWidget);
if (kIsWeb && ref.read(librarySearchProvider(uniqueKey)).posters.isEmpty) {
initLibrary();
}
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
initLibrary();
}
void initLibrary() {
searchController.addListener(() { searchController.addListener(() {
debouncer.run(() { debouncer.run(() {
ref.read(providerKey.notifier).setSearch(searchController.text); ref.read(providerKey.notifier).setSearch(searchController.text);
@ -87,7 +105,9 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
Future.microtask( Future.microtask(
() async { () async {
libraryProvider.setDefaultOptions(widget.sortOrder, widget.sortingOptions); if (libraryProvider.mounted) {
libraryProvider.setDefaultOptions(widget.sortOrder, widget.sortingOptions);
}
await refreshKey.currentState?.show(); await refreshKey.currentState?.show();
SystemChrome.setEnabledSystemUIMode( SystemChrome.setEnabledSystemUIMode(
SystemUiMode.edgeToEdge, SystemUiMode.edgeToEdge,
@ -113,10 +133,8 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isEmptySearchScreen = widget.viewModelId == null && widget.favourites == null && widget.folderId == null; final isEmptySearchScreen = widget.viewModelId == null && widget.favourites == null && widget.folderId == null;
final librarySearchResults = ref.watch(providerKey); final librarySearchResults = ref.watch(providerKey);
final libraryProvider = ref.read(providerKey.notifier);
final postersList = librarySearchResults.posters.hideEmptyChildren(librarySearchResults.hideEmtpyShows); final postersList = librarySearchResults.posters.hideEmptyChildren(librarySearchResults.hideEmtpyShows);
final playerState = ref.watch(mediaPlaybackProvider.select((value) => value.state)); final playerState = ref.watch(mediaPlaybackProvider.select((value) => value.state));
final libraryViewType = ref.watch(libraryViewTypeProvider); final libraryViewType = ref.watch(libraryViewTypeProvider);
ref.listen( ref.listen(
@ -130,6 +148,7 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
); );
return PopScope( return PopScope(
key: uniqueKey,
canPop: !librarySearchResults.selecteMode, canPop: !librarySearchResults.selecteMode,
onPopInvokedWithResult: (didPop, result) { onPopInvokedWithResult: (didPop, result) {
if (librarySearchResults.selecteMode) { if (librarySearchResults.selecteMode) {
@ -139,7 +158,8 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
child: Scaffold( child: Scaffold(
extendBody: true, extendBody: true,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
floatingActionButtonLocation: playerState == VideoPlayerState.minimized ? FloatingActionButtonLocation.centerFloat : null, floatingActionButtonLocation:
playerState == VideoPlayerState.minimized ? FloatingActionButtonLocation.centerFloat : null,
floatingActionButton: switch (playerState) { floatingActionButton: switch (playerState) {
VideoPlayerState.minimized => const Padding( VideoPlayerState.minimized => const Padding(
padding: EdgeInsets.symmetric(horizontal: 8), padding: EdgeInsets.symmetric(horizontal: 8),
@ -197,8 +217,9 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
child: MediaQuery.removeViewInsets( child: MediaQuery.removeViewInsets(
context: context, context: context,
child: ClipRRect( child: ClipRRect(
borderRadius: borderRadius: AdaptiveLayout.of(context).layout == LayoutState.desktop
AdaptiveLayout.of(context).layout == LayoutState.desktop ? BorderRadius.circular(15) : BorderRadius.circular(0), ? BorderRadius.circular(15)
: BorderRadius.circular(0),
child: FladderScrollbar( child: FladderScrollbar(
visible: AdaptiveLayout.of(context).inputDevice != InputDevice.pointer, visible: AdaptiveLayout.of(context).inputDevice != InputDevice.pointer,
controller: scrollController, controller: scrollController,
@ -206,7 +227,12 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
refreshKey: refreshKey, refreshKey: refreshKey,
autoFocus: false, autoFocus: false,
contextRefresh: false, contextRefresh: false,
onRefresh: () async => libraryProvider.initRefresh(widget.folderId, widget.viewModelId, widget.favourites), onRefresh: () async {
if (libraryProvider.mounted) {
return libraryProvider.initRefresh(
widget.folderId, widget.viewModelId, widget.favourites);
}
},
refreshOnStart: false, refreshOnStart: false,
child: CustomScrollView( child: CustomScrollView(
physics: const AlwaysScrollableNoImplicitScrollPhysics(), physics: const AlwaysScrollableNoImplicitScrollPhysics(),
@ -215,10 +241,11 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
SliverAppBar( SliverAppBar(
floating: !AdaptiveLayout.of(context).isDesktop, floating: !AdaptiveLayout.of(context).isDesktop,
collapsedHeight: 80, collapsedHeight: 80,
automaticallyImplyLeading: true, automaticallyImplyLeading: false,
pinned: AdaptiveLayout.of(context).isDesktop, pinned: AdaptiveLayout.of(context).isDesktop,
primary: true, primary: true,
elevation: 5, elevation: 5,
leading: context.router.backButton(),
surfaceTintColor: Colors.transparent, surfaceTintColor: Colors.transparent,
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
@ -228,7 +255,8 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
actions: [ actions: [
const SizedBox(width: 4), const SizedBox(width: 4),
Builder(builder: (context) { Builder(builder: (context) {
final isFavorite = librarySearchResults.nestedCurrentItem?.userData.isFavourite == true; final isFavorite =
librarySearchResults.nestedCurrentItem?.userData.isFavourite == true;
final itemActions = librarySearchResults.nestedCurrentItem?.generateActions( final itemActions = librarySearchResults.nestedCurrentItem?.generateActions(
context, context,
ref, ref,
@ -276,7 +304,8 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
(e) => FilledButton.tonal( (e) => FilledButton.tonal(
style: FilledButtonTheme.of(context).style?.copyWith( style: FilledButtonTheme.of(context).style?.copyWith(
padding: const WidgetStatePropertyAll( padding: const WidgetStatePropertyAll(
EdgeInsets.symmetric(horizontal: 12, vertical: 24)), EdgeInsets.symmetric(
horizontal: 12, vertical: 24)),
backgroundColor: WidgetStateProperty.resolveWith( backgroundColor: WidgetStateProperty.resolveWith(
(states) { (states) {
if (e != currentType) { if (e != currentType) {
@ -312,8 +341,8 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
return Card( return Card(
elevation: 0, elevation: 0,
child: Tooltip( child: Tooltip(
message: message: librarySearchResults.nestedCurrentItem?.type.label(context) ??
librarySearchResults.nestedCurrentItem?.type.label(context) ?? context.localized.library(1), context.localized.library(1),
child: InkWell( child: InkWell(
onTapUp: (details) async { onTapUp: (details) async {
if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) { if (AdaptiveLayout.of(context).inputDevice == InputDevice.pointer) {
@ -324,8 +353,9 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
position: RelativeRect.fromLTRB(left, top, 40, 100), position: RelativeRect.fromLTRB(left, top, 40, 100),
items: <PopupMenuEntry>[ items: <PopupMenuEntry>[
PopupMenuItem( PopupMenuItem(
child: Text(librarySearchResults.nestedCurrentItem?.type.label(context) ?? child: Text(
context.localized.library(0))), librarySearchResults.nestedCurrentItem?.type.label(context) ??
context.localized.library(0))),
itemCountWidget.toPopupMenuItem(useIcons: true), itemCountWidget.toPopupMenuItem(useIcons: true),
refreshAction.toPopupMenuItem(useIcons: true), refreshAction.toPopupMenuItem(useIcons: true),
itemViewAction.toPopupMenuItem(useIcons: true), itemViewAction.toPopupMenuItem(useIcons: true),
@ -356,7 +386,8 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
child: Icon( child: Icon(
isFavorite isFavorite
? librarySearchResults.nestedCurrentItem?.type.selectedicon ? librarySearchResults.nestedCurrentItem?.type.selectedicon
: librarySearchResults.nestedCurrentItem?.type.icon ?? IconsaxOutline.document, : librarySearchResults.nestedCurrentItem?.type.icon ??
IconsaxOutline.document,
color: isFavorite ? Theme.of(context).colorScheme.primary : null, color: isFavorite ? Theme.of(context).colorScheme.primary : null,
), ),
), ),
@ -433,7 +464,8 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
if (postersList.isNotEmpty) if (postersList.isNotEmpty)
SliverPadding( SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: MediaQuery.of(context).padding.left, right: MediaQuery.of(context).padding.right), left: MediaQuery.of(context).padding.left,
right: MediaQuery.of(context).padding.right),
sliver: LibraryViews( sliver: LibraryViews(
key: uniqueKey, key: uniqueKey,
items: postersList, items: postersList,
@ -568,7 +600,8 @@ class _LibrarySearchBottomBar extends ConsumerWidget {
}, },
label: Text(context.localized.removeFromCollection), label: Text(context.localized.removeFromCollection),
icon: Container( icon: Container(
decoration: BoxDecoration(color: Theme.of(context).colorScheme.onPrimary, borderRadius: BorderRadius.circular(6)), decoration:
BoxDecoration(color: Theme.of(context).colorScheme.onPrimary, borderRadius: BorderRadius.circular(6)),
child: const Padding( child: const Padding(
padding: EdgeInsets.all(3.0), padding: EdgeInsets.all(3.0),
child: Icon(IconsaxOutline.save_remove, size: 20), child: Icon(IconsaxOutline.save_remove, size: 20),
@ -620,8 +653,8 @@ class _LibrarySearchBottomBar extends ConsumerWidget {
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
elevation: 0, elevation: 0,
borderRadiusGeometry: BorderRadius.circular(6), borderRadiusGeometry: BorderRadius.circular(6),
onTap: () => onTap: () => scrollController.animateTo(0,
scrollController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.easeInOutCubic), duration: const Duration(milliseconds: 500), curve: Curves.easeInOutCubic),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primaryContainer, color: Theme.of(context).colorScheme.primaryContainer,
@ -679,8 +712,9 @@ class _LibrarySearchBottomBar extends ConsumerWidget {
AnimatedFadeSize( AnimatedFadeSize(
child: librarySearchResults.selecteMode child: librarySearchResults.selecteMode
? Container( ? Container(
decoration: decoration: BoxDecoration(
BoxDecoration(color: Theme.of(context).colorScheme.primaryContainer, borderRadius: BorderRadius.circular(16)), color: Theme.of(context).colorScheme.primaryContainer,
borderRadius: BorderRadius.circular(16)),
child: Row( child: Row(
children: [ children: [
Tooltip( Tooltip(

View file

@ -1,4 +1,8 @@
import 'package:flutter/material.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart'; import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/item_shared_models.dart'; import 'package:fladder/models/items/item_shared_models.dart';
@ -12,8 +16,6 @@ import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/map_bool_helper.dart'; import 'package:fladder/util/map_bool_helper.dart';
import 'package:fladder/util/refresh_state.dart'; import 'package:fladder/util/refresh_state.dart';
import 'package:fladder/widgets/shared/scroll_position.dart'; import 'package:fladder/widgets/shared/scroll_position.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class LibraryFilterChips extends ConsumerWidget { class LibraryFilterChips extends ConsumerWidget {
final Key uniqueKey; final Key uniqueKey;
@ -188,12 +190,12 @@ List<Widget> libraryFilterChips(
if (librarySearchResults.types[FladderItemType.series] == true) if (librarySearchResults.types[FladderItemType.series] == true)
FilterChip( FilterChip(
avatar: Icon( avatar: Icon(
librarySearchResults.hideEmtpyShows ? Icons.visibility_rounded : Icons.visibility_off_rounded, librarySearchResults.hideEmtpyShows ? Icons.visibility_off_rounded : Icons.visibility_rounded,
color: Theme.of(context).colorScheme.onSurface, color: Theme.of(context).colorScheme.onSurface,
), ),
selected: librarySearchResults.hideEmtpyShows, selected: librarySearchResults.hideEmtpyShows,
showCheckmark: false, showCheckmark: false,
label: Text(librarySearchResults.hideEmtpyShows ? context.localized.showEmpty : context.localized.hideEmpty), label: Text(context.localized.hideEmpty),
onSelected: libraryProvider.setHideEmpty, onSelected: libraryProvider.setHideEmpty,
), ),
if (librarySearchResults.officialRatings.isNotEmpty) if (librarySearchResults.officialRatings.isNotEmpty)

View file

@ -1,24 +1,24 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:fladder/models/account_model.dart'; import 'package:fladder/models/account_model.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/route_builder.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/screens/login/widgets/login_icon.dart'; import 'package:fladder/screens/login/widgets/login_icon.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/screens/shared/passcode_input.dart'; import 'package:fladder/screens/shared/passcode_input.dart';
import 'package:fladder/util/auth_service.dart'; import 'package:fladder/util/auth_service.dart';
import 'package:fladder/util/localization_helper.dart';
final lockScreenActiveProvider = StateProvider<bool>((ref) => false); final lockScreenActiveProvider = StateProvider<bool>((ref) => false);
@RoutePage()
class LockScreen extends ConsumerStatefulWidget { class LockScreen extends ConsumerStatefulWidget {
final bool selfLock; const LockScreen({super.key});
const LockScreen({this.selfLock = false, super.key});
@override @override
ConsumerState<ConsumerStatefulWidget> createState() => _LockScreenState(); ConsumerState<ConsumerStatefulWidget> createState() => _LockScreenState();
@ -52,10 +52,6 @@ class _LockScreenState extends ConsumerState<LockScreen> with WidgetsBindingObse
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
Future.microtask(() { Future.microtask(() {
ref.read(lockScreenActiveProvider.notifier).update((state) => true); ref.read(lockScreenActiveProvider.notifier).update((state) => true);
final user = ref.read(userProvider);
if (user != null && !widget.selfLock) {
tapLoggedInAccount(user);
}
}); });
hackyFixForBlackNavbar(); hackyFixForBlackNavbar();
} }
@ -63,7 +59,7 @@ class _LockScreenState extends ConsumerState<LockScreen> with WidgetsBindingObse
void handleLogin(AccountModel user) { void handleLogin(AccountModel user) {
ref.read(lockScreenActiveProvider.notifier).update((state) => false); ref.read(lockScreenActiveProvider.notifier).update((state) => false);
poppingLockScreen = true; poppingLockScreen = true;
context.pop(); context.router.popForced();
} }
void tapLoggedInAccount(AccountModel user) async { void tapLoggedInAccount(AccountModel user) async {
@ -131,7 +127,7 @@ class _LockScreenState extends ConsumerState<LockScreen> with WidgetsBindingObse
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () { onPressed: () {
ref.read(lockScreenActiveProvider.notifier).update((state) => false); ref.read(lockScreenActiveProvider.notifier).update((state) => false);
context.routeGo(LoginRoute()); context.router.push(const LoginRoute());
}, },
icon: const Icon(Icons.login_rounded), icon: const Icon(Icons.login_rounded),
label: Text(context.localized.login), label: Text(context.localized.login),

View file

@ -1,34 +1,34 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer'; import 'dart:developer';
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/screens/login/lock_screen.dart';
import 'package:fladder/screens/login/widgets/discover_servers_widget.dart';
import 'package:fladder/screens/shared/fladder_logo.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:fladder/models/account_model.dart'; import 'package:fladder/models/account_model.dart';
import 'package:fladder/providers/auth_provider.dart'; import 'package:fladder/providers/auth_provider.dart';
import 'package:fladder/providers/shared_provider.dart'; import 'package:fladder/providers/shared_provider.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/home_routes.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/routes/build_routes/route_builder.dart'; import 'package:fladder/screens/login/lock_screen.dart';
import 'package:fladder/screens/login/login_edit_user.dart'; import 'package:fladder/screens/login/login_edit_user.dart';
import 'package:fladder/screens/login/login_user_grid.dart'; import 'package:fladder/screens/login/login_user_grid.dart';
import 'package:fladder/screens/login/widgets/discover_servers_widget.dart';
import 'package:fladder/screens/shared/animated_fade_size.dart'; import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/screens/shared/fladder_logo.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/screens/shared/outlined_text_field.dart'; import 'package:fladder/screens/shared/outlined_text_field.dart';
import 'package:fladder/screens/shared/passcode_input.dart'; import 'package:fladder/screens/shared/passcode_input.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/auth_service.dart'; import 'package:fladder/util/auth_service.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart';
import 'package:fladder/widgets/navigation_scaffold/components/fladder_appbar.dart'; import 'package:fladder/widgets/navigation_scaffold/components/fladder_appbar.dart';
@RoutePage()
class LoginScreen extends ConsumerStatefulWidget { class LoginScreen extends ConsumerStatefulWidget {
const LoginScreen({super.key}); const LoginScreen({super.key});
@ -164,7 +164,7 @@ class _LoginPageState extends ConsumerState<LoginScreen> {
serverTextController.text = value; serverTextController.text = value;
startAddingNewUser(); startAddingNewUser();
}); });
context.pop(); Navigator.of(context).pop();
}, },
), ),
); );
@ -211,7 +211,7 @@ class _LoginPageState extends ConsumerState<LoginScreen> {
void loggedInGoToHome() { void loggedInGoToHome() {
ref.read(lockScreenActiveProvider.notifier).update((state) => false); ref.read(lockScreenActiveProvider.notifier).update((state) => false);
if (context.mounted) { if (context.mounted) {
context.routeGo(DashboardRoute()); context.router.navigate(const DashboardRoute());
} }
} }

View file

@ -1,6 +1,10 @@
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/providers/items/identify_provider.dart'; import 'package:fladder/providers/items/identify_provider.dart';
import 'package:fladder/screens/shared/adaptive_dialog.dart'; import 'package:fladder/screens/shared/adaptive_dialog.dart';
@ -9,8 +13,6 @@ import 'package:fladder/screens/shared/focused_outlined_text_field.dart';
import 'package:fladder/screens/shared/media/external_urls.dart'; import 'package:fladder/screens/shared/media/external_urls.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/util/string_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Future<void> showIdentifyScreen(BuildContext context, ItemBaseModel item) async { Future<void> showIdentifyScreen(BuildContext context, ItemBaseModel item) async {
return showDialogAdaptive( return showDialogAdaptive(
@ -30,7 +32,7 @@ class IdentifyScreen extends ConsumerStatefulWidget {
} }
class _IdentifyScreenState extends ConsumerState<IdentifyScreen> with TickerProviderStateMixin { class _IdentifyScreenState extends ConsumerState<IdentifyScreen> with TickerProviderStateMixin {
late AutoDisposeStateNotifierProvider<IdentifyNotifier, IdentifyModel> provider = identifyProvider(widget.item.id); AutoDisposeStateNotifierProvider<IdentifyNotifier, IdentifyModel> get provider => identifyProvider(widget.item.id);
late final TabController tabController = TabController(length: 2, vsync: this); late final TabController tabController = TabController(length: 2, vsync: this);
TextEditingController? currentController; TextEditingController? currentController;

View file

@ -1,13 +1,15 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/information_model.dart'; import 'package:fladder/models/information_model.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/providers/items/information_provider.dart'; import 'package:fladder/providers/items/information_provider.dart';
import 'package:fladder/screens/shared/fladder_snackbar.dart'; import 'package:fladder/screens/shared/fladder_snackbar.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/widgets/shared/clickable_text.dart'; import 'package:fladder/widgets/shared/clickable_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter/services.dart';
Future<void> showInfoScreen(BuildContext context, ItemBaseModel item) async { Future<void> showInfoScreen(BuildContext context, ItemBaseModel item) async {
return showDialog( return showDialog(
@ -27,7 +29,7 @@ class ItemInfoScreen extends ConsumerStatefulWidget {
} }
class ItemInfoScreenState extends ConsumerState<ItemInfoScreen> { class ItemInfoScreenState extends ConsumerState<ItemInfoScreen> {
late AutoDisposeStateNotifierProvider<InformationNotifier, InformationProviderModel> provider = AutoDisposeStateNotifierProvider<InformationNotifier, InformationProviderModel> get provider =>
informationProvider(widget.item.id); informationProvider(widget.item.id);
@override @override

View file

@ -1,11 +1,11 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:fladder/models/settings/home_settings_model.dart'; import 'package:fladder/models/settings/home_settings_model.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart';
@ -13,7 +13,7 @@ import 'package:fladder/providers/settings/home_settings_provider.dart';
import 'package:fladder/providers/shared_provider.dart'; import 'package:fladder/providers/shared_provider.dart';
import 'package:fladder/providers/sync_provider.dart'; import 'package:fladder/providers/sync_provider.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/route_builder.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/screens/settings/settings_list_tile.dart'; import 'package:fladder/screens/settings/settings_list_tile.dart';
import 'package:fladder/screens/settings/settings_scaffold.dart'; import 'package:fladder/screens/settings/settings_scaffold.dart';
import 'package:fladder/screens/settings/widgets/settings_label_divider.dart'; import 'package:fladder/screens/settings/widgets/settings_label_divider.dart';
@ -30,6 +30,7 @@ import 'package:fladder/util/theme_mode_extension.dart';
import 'package:fladder/widgets/shared/enum_selection.dart'; import 'package:fladder/widgets/shared/enum_selection.dart';
import 'package:fladder/widgets/shared/fladder_slider.dart'; import 'package:fladder/widgets/shared/fladder_slider.dart';
@RoutePage()
class ClientSettingsPage extends ConsumerStatefulWidget { class ClientSettingsPage extends ConsumerStatefulWidget {
const ClientSettingsPage({super.key}); const ClientSettingsPage({super.key});
@ -38,16 +39,17 @@ class ClientSettingsPage extends ConsumerStatefulWidget {
} }
class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> { class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
late final nextUpDaysEditor = late final nextUpDaysEditor = TextEditingController(
TextEditingController(text: ref.read(clientSettingsProvider.select((value) => value.nextUpDateCutoff?.inDays ?? 14)).toString()); text: ref.read(clientSettingsProvider.select((value) => value.nextUpDateCutoff?.inDays ?? 14)).toString());
late final libraryPageSizeController = late final libraryPageSizeController = TextEditingController(
TextEditingController(text: ref.read(clientSettingsProvider.select((value) => value.libraryPageSize))?.toString() ?? ""); text: ref.read(clientSettingsProvider.select((value) => value.libraryPageSize))?.toString() ?? "");
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final clientSettings = ref.watch(clientSettingsProvider); final clientSettings = ref.watch(clientSettingsProvider);
final showBackground = AdaptiveLayout.of(context).layout != LayoutState.phone && AdaptiveLayout.of(context).size != ScreenLayout.single; final showBackground = AdaptiveLayout.of(context).layout != LayoutState.phone &&
AdaptiveLayout.of(context).size != ScreenLayout.single;
final currentFolder = ref.watch(syncProvider.notifier).savePath; final currentFolder = ref.watch(syncProvider.notifier).savePath;
Locale currentLocale = WidgetsBinding.instance.platformDispatcher.locale; Locale currentLocale = WidgetsBinding.instance.platformDispatcher.locale;
@ -72,8 +74,8 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
actions: [ actions: [
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
String? selectedDirectory = await FilePicker.platform String? selectedDirectory = await FilePicker.platform.getDirectoryPath(
.getDirectoryPath(dialogTitle: context.localized.pathEditSelect, initialDirectory: currentFolder); dialogTitle: context.localized.pathEditSelect, initialDirectory: currentFolder);
if (selectedDirectory != null) { if (selectedDirectory != null) {
ref.read(clientSettingsProvider.notifier).setSyncPath(selectedDirectory); ref.read(clientSettingsProvider.notifier).setSyncPath(selectedDirectory);
} }
@ -85,8 +87,8 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
), ),
) )
: () async { : () async {
String? selectedDirectory = await FilePicker.platform String? selectedDirectory = await FilePicker.platform.getDirectoryPath(
.getDirectoryPath(dialogTitle: context.localized.pathEditSelect, initialDirectory: currentFolder); dialogTitle: context.localized.pathEditSelect, initialDirectory: currentFolder);
if (selectedDirectory != null) { if (selectedDirectory != null) {
ref.read(clientSettingsProvider.notifier).setSyncPath(selectedDirectory); ref.read(clientSettingsProvider.notifier).setSyncPath(selectedDirectory);
} }
@ -131,10 +133,10 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
(context) async { (context) async {
await ref.read(syncProvider.notifier).clear(); await ref.read(syncProvider.notifier).clear();
setState(() {}); setState(() {});
context.pop(); Navigator.of(context).pop();
}, },
context.localized.clear, context.localized.clear,
(context) => context.pop(), (context) => Navigator.of(context).pop(),
context.localized.cancel, context.localized.cancel,
); );
}, },
@ -155,9 +157,9 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
initialValue: clientSettings.timeOut ?? const Duration(), initialValue: clientSettings.timeOut ?? const Duration(),
); );
ref ref.read(clientSettingsProvider.notifier).setTimeOut(timePicker != null
.read(clientSettingsProvider.notifier) ? Duration(minutes: timePicker.inMinutes, seconds: timePicker.inSeconds % 60)
.setTimeOut(timePicker != null ? Duration(minutes: timePicker.inMinutes, seconds: timePicker.inSeconds % 60) : null); : null);
}, },
), ),
const Divider(), const Divider(),
@ -176,7 +178,9 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
(entry) => PopupMenuItem( (entry) => PopupMenuItem(
value: entry, value: entry,
child: Text(entry.label(context)), child: Text(entry.label(context)),
onTap: () => ref.read(homeSettingsProvider.notifier).update((context) => context.copyWith(carouselSettings: entry)), onTap: () => ref
.read(homeSettingsProvider.notifier)
.update((context) => context.copyWith(carouselSettings: entry)),
), ),
) )
.toList(), .toList(),
@ -196,7 +200,8 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
(entry) => PopupMenuItem( (entry) => PopupMenuItem(
value: entry, value: entry,
child: Text(entry.label(context)), child: Text(entry.label(context)),
onTap: () => ref.read(homeSettingsProvider.notifier).update((context) => context.copyWith(nextUp: entry)), onTap: () =>
ref.read(homeSettingsProvider.notifier).update((context) => context.copyWith(nextUp: entry)),
), ),
) )
.toList(), .toList(),
@ -223,7 +228,9 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
fontWeight: currentLocale.languageCode == entry.languageCode ? FontWeight.bold : null, fontWeight: currentLocale.languageCode == entry.languageCode ? FontWeight.bold : null,
), ),
), ),
onTap: () => ref.read(clientSettingsProvider.notifier).update((state) => state.copyWith(selectedLocale: entry)), onTap: () => ref
.read(clientSettingsProvider.notifier)
.update((state) => state.copyWith(selectedLocale: entry)),
), ),
) )
]; ];
@ -233,7 +240,8 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
SettingsListTile( SettingsListTile(
label: Text(context.localized.settingsBlurredPlaceholderTitle), label: Text(context.localized.settingsBlurredPlaceholderTitle),
subLabel: Text(context.localized.settingsBlurredPlaceholderDesc), subLabel: Text(context.localized.settingsBlurredPlaceholderDesc),
onTap: () => ref.read(clientSettingsProvider.notifier).setBlurPlaceholders(!clientSettings.blurPlaceHolders), onTap: () =>
ref.read(clientSettingsProvider.notifier).setBlurPlaceholders(!clientSettings.blurPlaceHolders),
trailing: Switch( trailing: Switch(
value: clientSettings.blurPlaceHolders, value: clientSettings.blurPlaceHolders,
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setBlurPlaceholders(value), onChanged: (value) => ref.read(clientSettingsProvider.notifier).setBlurPlaceholders(value),
@ -242,7 +250,8 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
SettingsListTile( SettingsListTile(
label: Text(context.localized.settingsBlurEpisodesTitle), label: Text(context.localized.settingsBlurEpisodesTitle),
subLabel: Text(context.localized.settingsBlurEpisodesDesc), subLabel: Text(context.localized.settingsBlurEpisodesDesc),
onTap: () => ref.read(clientSettingsProvider.notifier).setBlurEpisodes(!clientSettings.blurUpcomingEpisodes), onTap: () =>
ref.read(clientSettingsProvider.notifier).setBlurEpisodes(!clientSettings.blurUpcomingEpisodes),
trailing: Switch( trailing: Switch(
value: clientSettings.blurUpcomingEpisodes, value: clientSettings.blurUpcomingEpisodes,
onChanged: (value) => ref.read(clientSettingsProvider.notifier).setBlurEpisodes(value), onChanged: (value) => ref.read(clientSettingsProvider.notifier).setBlurEpisodes(value),
@ -286,8 +295,9 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
)), )),
), ),
SettingsListTile( SettingsListTile(
label: Text( label: Text(AdaptiveLayout.of(context).isDesktop
AdaptiveLayout.of(context).isDesktop ? context.localized.settingsShowScaleSlider : context.localized.settingsPosterPinch), ? context.localized.settingsShowScaleSlider
: context.localized.settingsPosterPinch),
onTap: () => ref.read(clientSettingsProvider.notifier).update( onTap: () => ref.read(clientSettingsProvider.notifier).update(
(current) => current.copyWith(pinchPosterZoom: !current.pinchPosterZoom), (current) => current.copyWith(pinchPosterZoom: !current.pinchPosterZoom),
), ),
@ -314,7 +324,9 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
max: 1.5, max: 1.5,
value: clientSettings.posterSize, value: clientSettings.posterSize,
divisions: 20, divisions: 20,
onChanged: (value) => ref.read(clientSettingsProvider.notifier).update((current) => current.copyWith(posterSize: value)), onChanged: (value) => ref
.read(clientSettingsProvider.notifier)
.update((current) => current.copyWith(posterSize: value)),
), ),
), ),
const Divider(), const Divider(),
@ -439,14 +451,14 @@ class _ClientSettingsPageState extends ConsumerState<ClientSettingsPage> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
FilledButton( FilledButton(
onPressed: () => context.pop(), onPressed: () => Navigator.of(context).pop(),
child: Text(context.localized.cancel), child: Text(context.localized.cancel),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
await ref.read(sharedPreferencesProvider).clear(); await ref.read(sharedPreferencesProvider).clear();
context.routeGo(LoginRoute()); context.router.push(const LoginRoute());
}, },
child: Text(context.localized.clear), child: Text(context.localized.clear),
) )

View file

@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:fladder/providers/settings/video_player_settings_provider.dart'; import 'package:fladder/providers/settings/video_player_settings_provider.dart';
import 'package:fladder/screens/settings/settings_list_tile.dart'; import 'package:fladder/screens/settings/settings_list_tile.dart';
import 'package:fladder/screens/settings/settings_scaffold.dart'; import 'package:fladder/screens/settings/settings_scaffold.dart';
@ -14,6 +15,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'dart:io' show Platform; import 'dart:io' show Platform;
@RoutePage()
class PlayerSettingsPage extends ConsumerStatefulWidget { class PlayerSettingsPage extends ConsumerStatefulWidget {
const PlayerSettingsPage({super.key}); const PlayerSettingsPage({super.key});

View file

@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/screens/settings/settings_list_tile.dart'; import 'package:fladder/screens/settings/settings_list_tile.dart';
import 'package:fladder/screens/settings/settings_scaffold.dart'; import 'package:fladder/screens/settings/settings_scaffold.dart';
@ -8,6 +9,7 @@ import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@RoutePage()
class SecuritySettingsPage extends ConsumerStatefulWidget { class SecuritySettingsPage extends ConsumerStatefulWidget {
const SecuritySettingsPage({super.key}); const SecuritySettingsPage({super.key});

View file

@ -1,8 +1,12 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/screens/shared/user_icon.dart'; import 'package:fladder/screens/shared/user_icon.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
import 'package:flutter/material.dart'; import 'package:fladder/util/router_extension.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class SettingsScaffold extends ConsumerWidget { class SettingsScaffold extends ConsumerWidget {
final String label; final String label;
@ -25,7 +29,7 @@ class SettingsScaffold extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final padding = MediaQuery.of(context).padding; final padding = MediaQuery.of(context).padding;
return Scaffold( return Scaffold(
backgroundColor: AdaptiveLayout.of(context).isDesktop ? Colors.transparent : null, backgroundColor: AdaptiveLayout.of(context).size == ScreenLayout.dual ? Colors.transparent : null,
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: floatingActionButton, floatingActionButton: floatingActionButton,
body: Column( body: Column(
@ -36,8 +40,8 @@ class SettingsScaffold extends ConsumerWidget {
slivers: [ slivers: [
if (AdaptiveLayout.of(context).size == ScreenLayout.single) if (AdaptiveLayout.of(context).size == ScreenLayout.single)
SliverAppBar.large( SliverAppBar.large(
titleSpacing: 20,
backgroundColor: Theme.of(context).scaffoldBackgroundColor, backgroundColor: Theme.of(context).scaffoldBackgroundColor,
leading: context.router.backButton(),
flexibleSpace: FlexibleSpaceBar( flexibleSpace: FlexibleSpaceBar(
titlePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16) titlePadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16)
.add(EdgeInsets.only(left: padding.left, right: padding.right)), .add(EdgeInsets.only(left: padding.left, right: padding.right)),
@ -54,10 +58,10 @@ class SettingsScaffold extends ConsumerWidget {
)) ))
], ],
), ),
expandedTitleScale: 2, expandedTitleScale: 1.2,
), ),
expandedHeight: 175, expandedHeight: 100,
collapsedHeight: 100, collapsedHeight: 80,
pinned: false, pinned: false,
floating: true, floating: true,
) )

View file

@ -1,27 +1,27 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/providers/auth_provider.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/route_builder.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/routes/build_routes/settings_routes.dart';
import 'package:fladder/screens/settings/quick_connect_window.dart'; import 'package:fladder/screens/settings/quick_connect_window.dart';
import 'package:fladder/screens/settings/settings_list_tile.dart'; import 'package:fladder/screens/settings/settings_list_tile.dart';
import 'package:fladder/screens/settings/settings_scaffold.dart'; import 'package:fladder/screens/settings/settings_scaffold.dart';
import 'package:fladder/screens/shared/animated_fade_size.dart'; import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/screens/shared/fladder_icon.dart'; import 'package:fladder/screens/shared/fladder_icon.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/application_info.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/router_extension.dart';
import 'package:fladder/util/theme_extensions.dart'; import 'package:fladder/util/theme_extensions.dart';
import 'package:fladder/widgets/shared/hide_on_scroll.dart'; import 'package:fladder/widgets/shared/hide_on_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/providers/auth_provider.dart';
import 'package:fladder/util/application_info.dart';
import 'package:go_router/go_router.dart';
@RoutePage()
class SettingsScreen extends ConsumerStatefulWidget { class SettingsScreen extends ConsumerStatefulWidget {
final Widget? child; const SettingsScreen({super.key});
final String? location;
const SettingsScreen({this.child, this.location, super.key});
@override @override
ConsumerState<ConsumerStatefulWidget> createState() => _SettingsScreenState(); ConsumerState<ConsumerStatefulWidget> createState() => _SettingsScreenState();
@ -29,27 +29,30 @@ class SettingsScreen extends ConsumerStatefulWidget {
class _SettingsScreenState extends ConsumerState<SettingsScreen> { class _SettingsScreenState extends ConsumerState<SettingsScreen> {
final scrollController = ScrollController(); final scrollController = ScrollController();
late final singlePane = widget.child == null;
final minVerticalPadding = 20.0; final minVerticalPadding = 20.0;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (singlePane) { if (AdaptiveLayout.of(context).size == ScreenLayout.single) {
return Card( return Card(
elevation: 0, elevation: 0,
child: _leftPane(context), child: _leftPane(context),
); );
} else { } else {
return Row( return AutoRouter(
mainAxisSize: MainAxisSize.max, builder: (context, content) {
crossAxisAlignment: CrossAxisAlignment.stretch, return Row(
children: [ mainAxisSize: MainAxisSize.max,
Expanded(flex: 1, child: _leftPane(context)), crossAxisAlignment: CrossAxisAlignment.stretch,
Expanded( children: [
flex: 2, Expanded(flex: 1, child: _leftPane(context)),
child: widget.child ?? Container(), Expanded(
), flex: 2,
], child: content,
),
],
);
},
); );
} }
} }
@ -68,152 +71,167 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
} }
} }
bool containsRoute(CustomRoute route) => widget.location == route.route;
Widget _leftPane(BuildContext context) { Widget _leftPane(BuildContext context) {
final quickConnectAvailable = ref.watch(userProvider.select((value) => value?.serverConfiguration?.quickConnectAvailable ?? false)); void navigateTo(PageRouteInfo route) {
return SettingsScaffold( AdaptiveLayout.of(context).size == ScreenLayout.single
label: context.localized.settings, ? context.router.navigate(route)
scrollController: scrollController, : context.router.replace(route);
showUserIcon: true, }
items: [
if (context.canPop() && AdaptiveLayout.of(context).isDesktop) bool containsRoute(PageRouteInfo route) {
Align( return context.router.current.name == route.routeName;
alignment: Alignment.centerLeft, }
child: Padding(
padding: const EdgeInsets.all(16.0), final quickConnectAvailable =
child: IconButton.filledTonal( ref.watch(userProvider.select((value) => value?.serverConfiguration?.quickConnectAvailable ?? false));
style: IconButton.styleFrom( return Container(
backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0.8), color: context.colors.surface,
), child: SettingsScaffold(
onPressed: () { label: context.localized.settings,
context.pop(); scrollController: scrollController,
}, showUserIcon: true,
icon: Padding( items: [
padding: EdgeInsets.all(AdaptiveLayout.of(context).inputDevice == InputDevice.pointer ? 0 : 4), if (context.router.canNavigateBack && AdaptiveLayout.of(context).size == ScreenLayout.dual)
child: const Icon(IconsaxOutline.arrow_left_2), Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: IconButton.filledTonal(
style: IconButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.surface.withOpacity(0.8),
),
onPressed: () => context.router.popBack(),
icon: Padding(
padding: EdgeInsets.all(AdaptiveLayout.of(context).inputDevice == InputDevice.pointer ? 0 : 4),
child: const Icon(IconsaxOutline.arrow_left_2),
),
), ),
), ),
), ),
),
SettingsListTile(
label: Text(context.localized.settingsClientTitle),
subLabel: Text(context.localized.settingsClientDesc),
selected: containsRoute(ClientSettingsRoute()),
icon: deviceIcon,
onTap: () => context.routeReplaceOrPush(ClientSettingsRoute()),
),
if (quickConnectAvailable)
SettingsListTile( SettingsListTile(
label: Text(context.localized.settingsQuickConnectTitle), label: Text(context.localized.settingsClientTitle),
icon: IconsaxOutline.password_check, subLabel: Text(context.localized.settingsClientDesc),
onTap: () => openQuickConnectDialog(context), selected: containsRoute(const ClientSettingsRoute()),
icon: deviceIcon,
onTap: () => navigateTo(const ClientSettingsRoute()),
), ),
SettingsListTile( if (quickConnectAvailable)
label: Text(context.localized.settingsProfileTitle), SettingsListTile(
subLabel: Text(context.localized.settingsProfileDesc), label: Text(context.localized.settingsQuickConnectTitle),
selected: containsRoute(SecuritySettingsRoute()), icon: IconsaxOutline.password_check,
icon: IconsaxOutline.security_user, onTap: () => openQuickConnectDialog(context),
onTap: () => context.routeReplaceOrPush(SecuritySettingsRoute()), ),
), SettingsListTile(
SettingsListTile( label: Text(context.localized.settingsProfileTitle),
label: Text(context.localized.settingsPlayerTitle), subLabel: Text(context.localized.settingsProfileDesc),
subLabel: Text(context.localized.settingsPlayerDesc), selected: containsRoute(const SecuritySettingsRoute()),
selected: containsRoute(PlayerSettingsRoute()), icon: IconsaxOutline.security_user,
icon: IconsaxOutline.video_play, onTap: () => navigateTo(const SecuritySettingsRoute()),
onTap: () => context.routeReplaceOrPush(PlayerSettingsRoute()),
),
SettingsListTile(
label: Text(context.localized.about),
subLabel: const Text("Fladder"),
suffix: Opacity(
opacity: 1,
child: FladderIconOutlined(
size: 24,
color: context.colors.onSurfaceVariant,
)),
onTap: () => showAboutDialog(
context: context,
applicationIcon: const FladderIcon(size: 85),
applicationVersion: ref.watch(applicationInfoProvider).versionAndPlatform,
applicationLegalese: "Donut Factory",
), ),
), SettingsListTile(
], label: Text(context.localized.settingsPlayerTitle),
floatingActionButton: HideOnScroll( subLabel: Text(context.localized.settingsPlayerDesc),
controller: scrollController, selected: containsRoute(const PlayerSettingsRoute()),
visibleBuilder: (visible) { icon: IconsaxOutline.video_play,
return AnimatedFadeSize( onTap: () => navigateTo(const PlayerSettingsRoute()),
child: visible ),
? Padding( SettingsListTile(
padding: EdgeInsets.symmetric(horizontal: MediaQuery.paddingOf(context).horizontal), label: Text(context.localized.about),
child: Padding( subLabel: const Text("Fladder"),
padding: const EdgeInsets.symmetric(horizontal: 16), suffix: Opacity(
child: Row( opacity: 1,
mainAxisAlignment: MainAxisAlignment.end, child: FladderIconOutlined(
children: [ size: 24,
const Spacer(), color: context.colors.onSurfaceVariant,
FloatingActionButton( )),
key: Key(context.localized.switchUser), onTap: () => showAboutDialog(
tooltip: context.localized.switchUser, context: context,
onPressed: () { applicationIcon: const FladderIcon(size: 85),
ref.read(userProvider.notifier).logoutUser(); applicationVersion: ref.watch(applicationInfoProvider).versionAndPlatform,
context.routeGo(LoginRoute()); applicationLegalese: "Donut Factory",
}, ),
child: const Icon( ),
IconsaxOutline.arrow_swap_horizontal, ],
floatingActionButton: HideOnScroll(
controller: scrollController,
visibleBuilder: (visible) {
return AnimatedFadeSize(
child: visible
? Padding(
padding: EdgeInsets.symmetric(horizontal: MediaQuery.paddingOf(context).horizontal),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
const Spacer(),
FloatingActionButton(
key: Key(context.localized.switchUser),
tooltip: context.localized.switchUser,
onPressed: () async {
await ref.read(userProvider.notifier).logoutUser();
context.router.navigate(const LoginRoute());
},
child: const Icon(
IconsaxOutline.arrow_swap_horizontal,
),
), ),
), const SizedBox(width: 16),
const SizedBox(width: 16), FloatingActionButton(
FloatingActionButton( heroTag: context.localized.logout,
key: Key(context.localized.logout), key: Key(context.localized.logout),
tooltip: context.localized.logout, tooltip: context.localized.logout,
backgroundColor: Theme.of(context).colorScheme.errorContainer, backgroundColor: Theme.of(context).colorScheme.errorContainer,
onPressed: () { onPressed: () {
final user = ref.read(userProvider); final user = ref.read(userProvider);
showDialog( showDialog(
context: context, context: context,
builder: (context) => AlertDialog( builder: (context) => AlertDialog(
title: Text(context.localized.logoutUserPopupTitle(user?.name ?? "")), title: Text(context.localized.logoutUserPopupTitle(user?.name ?? "")),
scrollable: true, scrollable: true,
content: Text( content: Text(
context.localized.logoutUserPopupContent(user?.name ?? "", user?.server ?? ""), context.localized.logoutUserPopupContent(user?.name ?? "", user?.server ?? ""),
),
actions: [
ElevatedButton(
onPressed: () => Navigator.pop(context),
child: Text(context.localized.cancel),
), ),
ElevatedButton( actions: [
style: ElevatedButton.styleFrom().copyWith( ElevatedButton(
foregroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer), onPressed: () => Navigator.pop(context),
backgroundColor: WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer), child: Text(context.localized.cancel),
), ),
onPressed: () async { ElevatedButton(
await ref.read(authProvider.notifier).logOutUser(); style: ElevatedButton.styleFrom().copyWith(
if (context.mounted) context.routeGo(SplashRoute()); foregroundColor:
}, WidgetStatePropertyAll(Theme.of(context).colorScheme.onErrorContainer),
child: Text(context.localized.logout), backgroundColor:
), WidgetStatePropertyAll(Theme.of(context).colorScheme.errorContainer),
], ),
), onPressed: () async {
); await ref.read(authProvider.notifier).logOutUser();
}, if (context.mounted) {
child: Icon( context.router.navigate(const LoginRoute());
IconsaxOutline.logout, }
color: Theme.of(context).colorScheme.onErrorContainer, },
child: Text(context.localized.logout),
),
],
),
);
},
child: Icon(
IconsaxOutline.logout,
color: Theme.of(context).colorScheme.onErrorContainer,
),
), ),
), ],
], ),
), ),
)
: Container(
height: 0,
key: UniqueKey(),
), ),
) );
: Container( },
height: 0, ),
key: UniqueKey(),
),
);
},
), ),
); );
} }

View file

@ -1,88 +1,26 @@
import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/widgets/navigation_scaffold/components/settings_user_icon.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/images_models.dart'; import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/media_playback_model.dart'; import 'package:fladder/models/media_playback_model.dart';
import 'package:fladder/providers/items/item_details_provider.dart';
import 'package:fladder/providers/video_player_provider.dart'; import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/routes/build_routes/home_routes.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/theme.dart'; import 'package:fladder/theme.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/fladder_image.dart'; import 'package:fladder/util/fladder_image.dart';
import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/refresh_state.dart'; import 'package:fladder/util/refresh_state.dart';
import 'package:fladder/util/router_extension.dart';
import 'package:fladder/widgets/navigation_scaffold/components/floating_player_bar.dart'; import 'package:fladder/widgets/navigation_scaffold/components/floating_player_bar.dart';
import 'package:fladder/widgets/navigation_scaffold/components/settings_user_icon.dart';
import 'package:fladder/widgets/shared/item_actions.dart'; import 'package:fladder/widgets/shared/item_actions.dart';
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart'; import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
import 'package:fladder/widgets/shared/pull_to_refresh.dart'; import 'package:fladder/widgets/shared/pull_to_refresh.dart';
class DetailScreen extends ConsumerStatefulWidget {
final String id;
final ItemBaseModel? item;
const DetailScreen({required this.id, this.item, super.key});
@override
ConsumerState<ConsumerStatefulWidget> createState() => _DetailScreenState();
}
class _DetailScreenState extends ConsumerState<DetailScreen> {
late Widget currentWidget = const Center(
key: Key("progress-indicator"),
child: CircularProgressIndicator.adaptive(strokeCap: StrokeCap.round),
);
@override
void initState() {
super.initState();
Future.microtask(() async {
if (widget.item != null) {
setState(() {
currentWidget = widget.item!.detailScreenWidget;
});
} else {
final response = await ref.read(itemDetailsProvider.notifier).fetchDetails(widget.id);
if (context.mounted) {
if (response != null) {
setState(() {
currentWidget = response.detailScreenWidget;
});
} else {
context.routeGo(DashboardRoute());
}
}
}
});
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Hero(
tag: widget.id,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface.withOpacity(1.0),
),
//Small offset to match detailscaffold
child: Transform.translate(
offset: const Offset(0, -5), child: FladderImage(image: widget.item?.getPosters?.primary)),
),
),
AnimatedSwitcher(
duration: const Duration(seconds: 1),
child: currentWidget,
)
],
);
}
}
class DetailScaffold extends ConsumerStatefulWidget { class DetailScaffold extends ConsumerStatefulWidget {
final String label; final String label;
final ItemBaseModel? item; final ItemBaseModel? item;
@ -212,13 +150,7 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
style: IconButton.styleFrom( style: IconButton.styleFrom(
backgroundColor: backGroundColor, backgroundColor: backGroundColor,
), ),
onPressed: () { onPressed: () => context.router.popBack(),
if (context.canPop()) {
context.pop();
} else {
context.replace(DashboardRoute().route);
}
},
icon: Padding( icon: Padding(
padding: padding:
EdgeInsets.all(AdaptiveLayout.of(context).inputDevice == InputDevice.pointer ? 0 : 4), EdgeInsets.all(AdaptiveLayout.of(context).inputDevice == InputDevice.pointer ? 0 : 4),
@ -275,13 +207,13 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
icon: const Icon(IconsaxOutline.refresh), icon: const Icon(IconsaxOutline.refresh),
), ),
), ),
) ),
else if (AdaptiveLayout.of(context).size == ScreenLayout.single)
const SizedBox(height: 30, width: 30, child: SettingsUserIcon()), const SizedBox(height: 30, width: 30, child: SettingsUserIcon()),
Tooltip( Tooltip(
message: context.localized.home, message: context.localized.home,
child: IconButton( child: IconButton(
onPressed: () => context.routeGo(DashboardRoute()), onPressed: () => context.router.navigate(const DashboardRoute()),
icon: const Icon(IconsaxOutline.home), icon: const Icon(IconsaxOutline.home),
), ),
), ),

View file

@ -1,13 +1,12 @@
import 'package:animations/animations.dart'; import 'package:animations/animations.dart';
import 'package:auto_route/auto_route.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/route_builder.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/routes/build_routes/settings_routes.dart';
import 'package:fladder/screens/search/search_screen.dart'; import 'package:fladder/screens/search/search_screen.dart';
import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/util/string_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
class FloatingSearchBar extends ConsumerStatefulWidget { class FloatingSearchBar extends ConsumerStatefulWidget {
final List<Widget> trailing; final List<Widget> trailing;
@ -58,9 +57,9 @@ class _FloatingSearchBarState extends ConsumerState<FloatingSearchBar> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (context.canPop()) if (context.router.canPop())
IconButton( IconButton(
onPressed: () => context.pop(), onPressed: () => context.router.maybePop(),
icon: const Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@ -77,7 +76,7 @@ class _FloatingSearchBarState extends ConsumerState<FloatingSearchBar> {
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
context.routeGo(SecuritySettingsRoute()); context.router.push(const SecuritySettingsRoute());
}, },
icon: ClipRRect( icon: ClipRRect(
borderRadius: BorderRadius.circular(200), borderRadius: BorderRadius.circular(200),

View file

@ -1,5 +1,5 @@
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/widgets/navigation_scaffold/components/settings_user_icon.dart'; import 'package:fladder/widgets/navigation_scaffold/components/settings_user_icon.dart';
@ -10,7 +10,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
class NestedSliverAppBar extends ConsumerWidget { class NestedSliverAppBar extends ConsumerWidget {
final BuildContext parent; final BuildContext parent;
final String? searchTitle; final String? searchTitle;
final CustomRoute? route; final PageRouteInfo? route;
const NestedSliverAppBar({required this.parent, this.route, this.searchTitle, super.key}); const NestedSliverAppBar({required this.parent, this.route, this.searchTitle, super.key});
@override @override
@ -49,7 +49,7 @@ class NestedSliverAppBar extends ConsumerWidget {
child: InkWell( child: InkWell(
onTap: route != null onTap: route != null
? () { ? () {
context.routePushOrGo(route!); route?.push(context);
} }
: null, : null,
child: Padding( child: Padding(
@ -62,7 +62,8 @@ class NestedSliverAppBar extends ConsumerWidget {
const Icon(IconsaxOutline.search_normal), const Icon(IconsaxOutline.search_normal),
const SizedBox(width: 16), const SizedBox(width: 16),
Transform.translate( Transform.translate(
offset: const Offset(0, 2.5), child: Text(searchTitle ?? "${context.localized.search}...")), offset: const Offset(0, 2.5),
child: Text(searchTitle ?? "${context.localized.search}...")),
], ],
), ),
), ),

View file

@ -1,14 +1,18 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/account_model.dart'; import 'package:fladder/models/account_model.dart';
import 'package:fladder/providers/shared_provider.dart'; import 'package:fladder/providers/shared_provider.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/home_routes.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/screens/shared/fladder_logo.dart'; import 'package:fladder/screens/shared/fladder_logo.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@RoutePage()
class SplashScreen extends ConsumerStatefulWidget { class SplashScreen extends ConsumerStatefulWidget {
const SplashScreen({super.key}); final Function(bool loggedIn)? loggedIn;
const SplashScreen({this.loggedIn, super.key});
@override @override
ConsumerState<ConsumerStatefulWidget> createState() => _SplashScreenState(); ConsumerState<ConsumerStatefulWidget> createState() => _SplashScreenState();
@ -18,23 +22,23 @@ class _SplashScreenState extends ConsumerState<SplashScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
Future.microtask(() async { WidgetsBinding.instance.addPostFrameCallback((value) async {
await Future.delayed(const Duration(milliseconds: 500)); await Future.delayed(const Duration(milliseconds: 500));
final AccountModel? lastUsedAccount = ref.read(sharedUtilityProvider).getActiveAccount(); final AccountModel? lastUsedAccount = ref.read(sharedUtilityProvider).getActiveAccount();
ref.read(userProvider.notifier).updateUser(lastUsedAccount); ref.read(userProvider.notifier).updateUser(lastUsedAccount);
if (context.mounted) { if (context.mounted) {
if (lastUsedAccount == null) { if (lastUsedAccount == null) {
context.routeGo(LoginRoute()); callBackOrNavigate(false);
} else { } else {
switch (lastUsedAccount.authMethod) { switch (lastUsedAccount.authMethod) {
case Authentication.autoLogin: case Authentication.autoLogin:
context.routeGo(DashboardRoute()); callBackOrNavigate(true);
break; break;
case Authentication.biometrics: case Authentication.biometrics:
case Authentication.none: case Authentication.none:
case Authentication.passcode: case Authentication.passcode:
context.routeReplace(LoginRoute()); callBackOrNavigate(false);
break; break;
} }
} }
@ -42,6 +46,19 @@ class _SplashScreenState extends ConsumerState<SplashScreen> {
}); });
} }
void callBackOrNavigate(bool loggedIn) {
if (widget.loggedIn == null) {
if (loggedIn) {
context.router.replace(const DashboardRoute());
} else {
context.router.replace(const LoginRoute());
}
} else {
widget.loggedIn?.call(loggedIn);
context.router.maybePop(loggedIn);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return const Scaffold( return const Scaffold(

View file

@ -1,7 +1,8 @@
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:fladder/providers/sync_provider.dart'; import 'package:fladder/providers/sync_provider.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/routes/build_routes/home_routes.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/screens/shared/nested_scaffold.dart'; import 'package:fladder/screens/shared/nested_scaffold.dart';
import 'package:fladder/screens/shared/nested_sliver_appbar.dart'; import 'package:fladder/screens/shared/nested_sliver_appbar.dart';
import 'package:fladder/screens/syncing/sync_list_item.dart'; import 'package:fladder/screens/syncing/sync_list_item.dart';
@ -14,10 +15,11 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/util/sliver_list_padding.dart'; import 'package:fladder/util/sliver_list_padding.dart';
@RoutePage()
class SyncedScreen extends ConsumerStatefulWidget { class SyncedScreen extends ConsumerStatefulWidget {
final ScrollController navigationScrollController; final ScrollController? navigationScrollController;
const SyncedScreen({required this.navigationScrollController, super.key}); const SyncedScreen({this.navigationScrollController, super.key});
@override @override
ConsumerState<ConsumerStatefulWidget> createState() => _SyncedScreenState(); ConsumerState<ConsumerStatefulWidget> createState() => _SyncedScreenState();

View file

@ -1,11 +1,10 @@
import 'package:fladder/util/poster_defaults.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:fladder/routes/app_routes.dart'; import 'package:fladder/routes/auto_router.dart';
import 'package:fladder/util/poster_defaults.dart';
enum LayoutState { enum LayoutState {
phone, phone,
@ -65,8 +64,9 @@ class AdaptiveLayout extends InheritedWidget {
final InputDevice inputDevice; final InputDevice inputDevice;
final TargetPlatform platform; final TargetPlatform platform;
final bool isDesktop; final bool isDesktop;
final GoRouter router; final AutoRouter router;
final PosterDefaults posterDefaults; final PosterDefaults posterDefaults;
final ScrollController controller;
const AdaptiveLayout({ const AdaptiveLayout({
super.key, super.key,
@ -77,6 +77,7 @@ class AdaptiveLayout extends InheritedWidget {
required this.isDesktop, required this.isDesktop,
required this.router, required this.router,
required this.posterDefaults, required this.posterDefaults,
required this.controller,
required super.child, required super.child,
}); });
@ -94,7 +95,7 @@ class AdaptiveLayout extends InheritedWidget {
return result!.posterDefaults; return result!.posterDefaults;
} }
static GoRouter routerOf(BuildContext context) { static AutoRouter routerOf(BuildContext context) {
final AdaptiveLayout? result = maybeOf(context); final AdaptiveLayout? result = maybeOf(context);
return result!.router; return result!.router;
} }
@ -104,6 +105,11 @@ class AdaptiveLayout extends InheritedWidget {
return result!; return result!;
} }
static ScrollController scrollOf(BuildContext context) {
final AdaptiveLayout? result = maybeOf(context);
return result!.controller;
}
@override @override
bool updateShouldNotify(AdaptiveLayout oldWidget) { bool updateShouldNotify(AdaptiveLayout oldWidget) {
return layout != oldWidget.layout || return layout != oldWidget.layout ||
@ -128,8 +134,9 @@ class AdaptiveLayoutBuilder extends ConsumerStatefulWidget {
class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> { class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
late LayoutState layout = widget.fallBack; late LayoutState layout = widget.fallBack;
late ScreenLayout size = ScreenLayout.single; late ScreenLayout size = ScreenLayout.single;
late GoRouter router = AppRoutes.routes(ref: ref, screenLayout: size); late AutoRouter router = AutoRouter(layout: size, ref: ref);
late TargetPlatform currentPlatform = defaultTargetPlatform; late TargetPlatform currentPlatform = defaultTargetPlatform;
late ScrollController controller = ScrollController();
@override @override
void didChangeDependencies() { void didChangeDependencies() {
@ -169,7 +176,7 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
} }
if (size != newSize) { if (size != newSize) {
size = newSize; size = newSize;
router = AppRoutes.routes(ref: ref, screenLayout: size); router = AutoRouter(layout: size, ref: ref);
} }
} }
@ -177,6 +184,7 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AdaptiveLayout( return AdaptiveLayout(
layout: layout, layout: layout,
controller: controller,
size: size, size: size,
inputDevice: (isDesktop || kIsWeb) ? InputDevice.pointer : InputDevice.touch, inputDevice: (isDesktop || kIsWeb) ? InputDevice.pointer : InputDevice.touch,
platform: currentPlatform, platform: currentPlatform,
@ -191,3 +199,10 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
); );
} }
} }
double? get topPadding {
return switch (defaultTargetPlatform) {
TargetPlatform.linux || TargetPlatform.windows || TargetPlatform.macOS => 35,
_ => null
};
}

View file

@ -0,0 +1,30 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
extension RouterExtension on StackRouter {
Future<bool> popBack() async {
if (kIsWeb) {
back();
return canNavigateBack;
} else {
return maybePop();
}
}
Widget? backButton() {
if (kIsWeb && canNavigateBack) {
return IconButton(
onPressed: back,
icon: const BackButtonIcon(),
);
} else if (canPop()) {
return IconButton(
onPressed: maybePop,
icon: const BackButtonIcon(),
);
}
return null;
}
}

View file

@ -1,4 +1,4 @@
import 'package:fladder/routes/build_routes/route_builder.dart'; import 'package:auto_route/auto_route.dart';
import 'package:fladder/widgets/navigation_scaffold/components/adaptive_fab.dart'; import 'package:fladder/widgets/navigation_scaffold/components/adaptive_fab.dart';
import 'package:fladder/widgets/navigation_scaffold/components/navigation_button.dart'; import 'package:fladder/widgets/navigation_scaffold/components/navigation_button.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -7,7 +7,7 @@ class DestinationModel {
final String label; final String label;
final Widget? icon; final Widget? icon;
final Widget? selectedIcon; final Widget? selectedIcon;
final CustomRoute? route; final PageRouteInfo? route;
final Function()? action; final Function()? action;
final String? tooltip; final String? tooltip;
final Badge? badge; final Badge? badge;

View file

@ -1,9 +1,9 @@
import 'package:auto_route/auto_route.dart';
import 'package:fladder/screens/shared/default_titlebar.dart'; import 'package:fladder/screens/shared/default_titlebar.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
bool get _isDesktop { bool get _isDesktop {
if (kIsWeb) return false; if (kIsWeb) return false;
@ -29,7 +29,7 @@ class FladderAppbar extends StatelessWidget implements PreferredSize {
height: height, height: height,
child: Row( child: Row(
children: [ children: [
if (automaticallyImplyLeading && context.canPop()) const BackButton(), if (automaticallyImplyLeading && context.router.canPop()) const BackButton(),
Expanded( Expanded(
child: DefaultTitleBar( child: DefaultTitleBar(
label: label, label: label,

View file

@ -5,7 +5,7 @@ import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/providers/views_provider.dart'; import 'package:fladder/providers/views_provider.dart';
import 'package:fladder/routes/build_routes/settings_routes.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/screens/shared/animated_fade_size.dart'; import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/widgets/navigation_scaffold/components/adaptive_fab.dart'; import 'package:fladder/widgets/navigation_scaffold/components/adaptive_fab.dart';
@ -110,7 +110,10 @@ class _NavigationBodyState extends ConsumerState<NavigationBody> {
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
), ),
}, },
if (AdaptiveLayout.of(context).platform == TargetPlatform.macOS) const SizedBox(height: 32) else const SizedBox(height: 16), if (AdaptiveLayout.of(context).platform == TargetPlatform.macOS)
const SizedBox(height: 32)
else
const SizedBox(height: 16),
IconButton( IconButton(
onPressed: () { onPressed: () {
if (AdaptiveLayout.layoutOf(context) != LayoutState.desktop) { if (AdaptiveLayout.layoutOf(context) != LayoutState.desktop) {
@ -152,7 +155,7 @@ class _NavigationBodyState extends ConsumerState<NavigationBody> {
height: 48, height: 48,
child: AnimatedSwitcher( child: AnimatedSwitcher(
duration: const Duration(milliseconds: 250), duration: const Duration(milliseconds: 250),
child: widget.currentLocation.contains(SettingsRoute().route) child: widget.currentLocation.contains(const SettingsRoute().routeName)
? Card( ? Card(
color: Theme.of(context).colorScheme.primaryContainer, color: Theme.of(context).colorScheme.primaryContainer,
child: const Padding( child: const Padding(

View file

@ -1,14 +1,13 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/collection_types.dart'; import 'package:fladder/models/collection_types.dart';
import 'package:fladder/models/view_model.dart'; import 'package:fladder/models/view_model.dart';
import 'package:fladder/routes/build_routes/home_routes.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/routes/build_routes/route_builder.dart';
import 'package:fladder/routes/build_routes/settings_routes.dart';
import 'package:fladder/screens/metadata/refresh_metadata.dart'; import 'package:fladder/screens/metadata/refresh_metadata.dart';
import 'package:fladder/screens/shared/animated_fade_size.dart'; import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
@ -74,7 +73,7 @@ class NestedNavigationDrawer extends ConsumerWidget {
), ),
...destinations.map((destination) => DrawerListButton( ...destinations.map((destination) => DrawerListButton(
label: destination.label, label: destination.label,
selected: destination.route?.route == currentLocation, selected: context.router.current.name == destination.route?.routeName,
selectedIcon: destination.selectedIcon!, selectedIcon: destination.selectedIcon!,
icon: destination.icon!, icon: destination.icon!,
onPressed: () { onPressed: () {
@ -93,7 +92,7 @@ class NestedNavigationDrawer extends ConsumerWidget {
), ),
...views.map((library) => DrawerListButton( ...views.map((library) => DrawerListButton(
label: library.name, label: library.name,
selected: currentLocation.contains(library.id), selected: checkLibrary(context, library.id),
actions: [ actions: [
ItemActionButton( ItemActionButton(
label: Text(context.localized.scanLibrary), label: Text(context.localized.scanLibrary),
@ -102,7 +101,7 @@ class NestedNavigationDrawer extends ConsumerWidget {
), ),
], ],
onPressed: () { onPressed: () {
context.routePushOrGo(LibrarySearchRoute(id: library.id)); context.router.push(LibrarySearchRoute(viewModelId: library.id));
Scaffold.of(context).closeDrawer(); Scaffold.of(context).closeDrawer();
}, },
selectedIcon: Icon(library.collectionType.icon), selectedIcon: Icon(library.collectionType.icon),
@ -115,15 +114,15 @@ class NestedNavigationDrawer extends ConsumerWidget {
child: DrawerListButton( child: DrawerListButton(
label: context.localized.settings, label: context.localized.settings,
selectedIcon: const Icon(IconsaxBold.setting_3), selectedIcon: const Icon(IconsaxBold.setting_3),
selected: currentLocation.contains(SettingsRoute().basePath), selected: currentLocation.contains(const SettingsRoute().routeName),
icon: const SizedBox(width: 35, height: 35, child: SettingsUserIcon()), icon: const SizedBox(width: 35, height: 35, child: SettingsUserIcon()),
onPressed: () { onPressed: () {
switch (AdaptiveLayout.of(context).size) { switch (AdaptiveLayout.of(context).size) {
case ScreenLayout.single: case ScreenLayout.single:
context.routePush(SettingsRoute()); const SettingsRoute().push(context);
break; break;
case ScreenLayout.dual: case ScreenLayout.dual:
context.routeGo(ClientSettingsRoute()); context.router.push(const ClientSettingsRoute());
break; break;
} }
Scaffold.of(context).closeDrawer(); Scaffold.of(context).closeDrawer();
@ -135,14 +134,14 @@ class NestedNavigationDrawer extends ConsumerWidget {
label: context.localized.settings, label: context.localized.settings,
selectedIcon: const Icon(IconsaxBold.setting_2), selectedIcon: const Icon(IconsaxBold.setting_2),
icon: const Icon(IconsaxOutline.setting_2), icon: const Icon(IconsaxOutline.setting_2),
selected: currentLocation.contains(SettingsRoute().basePath), selected: currentLocation.contains(const SettingsRoute().routeName),
onPressed: () { onPressed: () {
switch (AdaptiveLayout.of(context).size) { switch (AdaptiveLayout.of(context).size) {
case ScreenLayout.single: case ScreenLayout.single:
context.routePush(SettingsRoute()); const SettingsRoute().push(context);
break; break;
case ScreenLayout.dual: case ScreenLayout.dual:
context.routeGo(ClientSettingsRoute()); context.router.push(const ClientSettingsRoute());
break; break;
} }
Scaffold.of(context).closeDrawer(); Scaffold.of(context).closeDrawer();
@ -152,4 +151,13 @@ class NestedNavigationDrawer extends ConsumerWidget {
], ],
); );
} }
bool checkLibrary(BuildContext context, String id) {
try {
return context.router.current.name == LibrarySearchRoute().routeName &&
(context.routeData.queryParams.isNotEmpty && context.routeData.queryParams.getString('parentId') == id);
} catch (e) {
return false;
}
}
} }

View file

@ -1,8 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:fladder/providers/user_provider.dart'; import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/routes/build_routes/route_builder.dart'; import 'package:fladder/routes/auto_router.gr.dart';
import 'package:fladder/routes/build_routes/settings_routes.dart';
import 'package:fladder/screens/shared/user_icon.dart'; import 'package:fladder/screens/shared/user_icon.dart';
import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
@ -19,11 +18,8 @@ class SettingsUserIcon extends ConsumerWidget {
child: UserIcon( child: UserIcon(
user: users, user: users,
cornerRadius: 200, cornerRadius: 200,
onLongPress: () => context.routePush(LockScreenRoute()), onLongPress: () => context.router.push(const LockRoute()),
onTap: () => switch (AdaptiveLayout.of(context).size) { onTap: () => context.router.navigate(const SettingsRoute()),
ScreenLayout.single => context.routePush(SettingsRoute()),
ScreenLayout.dual => context.routePush(ClientSettingsRoute()),
},
), ),
); );
} }

View file

@ -1,27 +1,26 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/media_playback_model.dart'; import 'package:fladder/models/media_playback_model.dart';
import 'package:fladder/providers/video_player_provider.dart'; import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/providers/views_provider.dart'; import 'package:fladder/providers/views_provider.dart';
import 'package:fladder/routes/app_routes.dart';
import 'package:fladder/screens/shared/nested_bottom_appbar.dart'; import 'package:fladder/screens/shared/nested_bottom_appbar.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/widgets/navigation_scaffold/components/floating_player_bar.dart';
import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart'; import 'package:fladder/widgets/navigation_scaffold/components/destination_model.dart';
import 'package:fladder/widgets/navigation_scaffold/components/fladder_appbar.dart'; import 'package:fladder/widgets/navigation_scaffold/components/fladder_appbar.dart';
import 'package:fladder/widgets/navigation_scaffold/components/floating_player_bar.dart';
import 'package:fladder/widgets/navigation_scaffold/components/navigation_body.dart'; import 'package:fladder/widgets/navigation_scaffold/components/navigation_body.dart';
import 'package:fladder/widgets/navigation_scaffold/components/navigation_drawer.dart'; import 'package:fladder/widgets/navigation_scaffold/components/navigation_drawer.dart';
import 'package:fladder/widgets/shared/hide_on_scroll.dart'; import 'package:fladder/widgets/shared/hide_on_scroll.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class NavigationScaffold extends ConsumerStatefulWidget { class NavigationScaffold extends ConsumerStatefulWidget {
final int? currentIndex; final String? currentRouteName;
final String? location;
final Widget? nestedChild; final Widget? nestedChild;
final List<DestinationModel> destinations; final List<DestinationModel> destinations;
final GlobalKey<NavigatorState>? nestedNavigatorKey; final GlobalKey<NavigatorState>? nestedNavigatorKey;
const NavigationScaffold({ const NavigationScaffold({
this.currentIndex, this.currentRouteName,
this.location,
this.nestedChild, this.nestedChild,
required this.destinations, required this.destinations,
this.nestedNavigatorKey, this.nestedNavigatorKey,
@ -35,8 +34,9 @@ class NavigationScaffold extends ConsumerStatefulWidget {
class _NavigationScaffoldState extends ConsumerState<NavigationScaffold> { class _NavigationScaffoldState extends ConsumerState<NavigationScaffold> {
final GlobalKey<ScaffoldState> _key = GlobalKey(); final GlobalKey<ScaffoldState> _key = GlobalKey();
int get currentIndex => widget.destinations.indexWhere((element) => element.route?.route == widget.location); int get currentIndex =>
String get currentLocation => widget.location ?? "Nothing"; widget.destinations.indexWhere((element) => element.route?.routeName == widget.currentRouteName);
String get currentLocation => widget.currentRouteName ?? "Nothing";
@override @override
void initState() { void initState() {
@ -50,6 +50,7 @@ class _NavigationScaffoldState extends ConsumerState<NavigationScaffold> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final playerState = ref.watch(mediaPlaybackProvider.select((value) => value.state)); final playerState = ref.watch(mediaPlaybackProvider.select((value) => value.state));
final views = ref.watch(viewsProvider.select((value) => value.views)); final views = ref.watch(viewsProvider.select((value) => value.views));
return PopScope( return PopScope(
canPop: currentIndex == 0, canPop: currentIndex == 0,
onPopInvokedWithResult: (didPop, result) { onPopInvokedWithResult: (didPop, result) {
@ -70,21 +71,21 @@ class _NavigationScaffoldState extends ConsumerState<NavigationScaffold> {
padding: EdgeInsets.symmetric(horizontal: 8), padding: EdgeInsets.symmetric(horizontal: 8),
child: FloatingPlayerBar(), child: FloatingPlayerBar(),
), ),
_ => widget.destinations.elementAtOrNull(currentIndex)?.floatingActionButton?.normal, _ => currentIndex != -1
? widget.destinations.elementAtOrNull(currentIndex)?.floatingActionButton?.normal
: null,
} }
: null, : null,
drawer: NestedNavigationDrawer( drawer: NestedNavigationDrawer(
actionButton: null, actionButton: null,
toggleExpanded: (value) { toggleExpanded: (value) => _key.currentState?.closeDrawer(),
_key.currentState?.closeDrawer();
},
views: views, views: views,
destinations: widget.destinations, destinations: widget.destinations,
currentLocation: currentLocation, currentLocation: currentLocation,
), ),
bottomNavigationBar: AdaptiveLayout.of(context).layout == LayoutState.phone bottomNavigationBar: AdaptiveLayout.of(context).layout == LayoutState.phone
? HideOnScroll( ? HideOnScroll(
controller: AppRoutes.scrollController, controller: AdaptiveLayout.scrollOf(context),
child: NestedBottomAppBar( child: NestedBottomAppBar(
child: Transform.translate( child: Transform.translate(
offset: const Offset(0, 8), offset: const Offset(0, 8),
@ -93,8 +94,8 @@ class _NavigationScaffoldState extends ConsumerState<NavigationScaffold> {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: widget.destinations children: widget.destinations
.map( .map(
(destination) => (destination) => destination.toNavigationButton(
destination.toNavigationButton(widget.location == destination.route?.route, false), widget.currentRouteName == destination.route?.routeName, false),
) )
.toList(), .toList(),
), ),

View file

@ -4,6 +4,7 @@ import 'dart:ui' as ui;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -34,12 +35,12 @@ class _TrickplayImageState extends ConsumerState<TrickplayImage> {
@override @override
void didUpdateWidget(covariant TrickplayImage oldWidget) { void didUpdateWidget(covariant TrickplayImage oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.position?.inMilliseconds != widget.position?.inMilliseconds) { if (oldWidget.position?.inMilliseconds != widget.position?.inMilliseconds) {
time = widget.position ?? Duration.zero; time = widget.position ?? Duration.zero;
model = widget.trickplay; model = widget.trickplay;
loadImage(); loadImage();
} }
super.didUpdateWidget(oldWidget);
} }
@override @override

View file

@ -1,20 +1,22 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:audio_service/audio_service.dart'; import 'package:audio_service/audio_service.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:smtc_windows/smtc_windows.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:fladder/models/item_base_model.dart'; import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart'; import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/providers/settings/video_player_settings_provider.dart'; import 'package:fladder/providers/settings/video_player_settings_provider.dart';
import 'package:fladder/providers/video_player_provider.dart'; import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/wrappers/media_control_base.dart'; import 'package:fladder/wrappers/media_control_base.dart';
import 'package:fladder/wrappers/media_wrapper_interface.dart'; import 'package:fladder/wrappers/media_wrapper_interface.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:smtc_windows/smtc_windows.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
class MediaControlsWrapper extends MediaPlayback implements MediaControlBase { class MediaControlsWrapper extends MediaPlayback implements MediaControlBase {
MediaControlsWrapper({required this.ref}); MediaControlsWrapper({required this.ref});

View file

@ -5,26 +5,31 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "61.0.0" version: "72.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.2"
analyzer: analyzer:
dependency: transitive dependency: transitive
description: description:
name: analyzer name: analyzer
sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.13.0" version: "6.7.0"
analyzer_plugin: analyzer_plugin:
dependency: transitive dependency: transitive
description: description:
name: analyzer_plugin name: analyzer_plugin
sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d sha256: "9661b30b13a685efaee9f02e5d01ed9f2b423bd889d28a304d02d704aee69161"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.2" version: "0.11.3"
animations: animations:
dependency: "direct main" dependency: "direct main"
description: description:
@ -97,6 +102,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.19" version: "0.1.19"
auto_route:
dependency: "direct main"
description:
name: auto_route
sha256: b83e8ce46da7228cdd019b5a11205454847f0a971bca59a7529b98df9876889b
url: "https://pub.dev"
source: hosted
version: "9.2.2"
auto_route_generator:
dependency: "direct dev"
description:
name: auto_route_generator
sha256: c9086eb07271e51b44071ad5cff34e889f3156710b964a308c2ab590769e79e6
url: "https://pub.dev"
source: hosted
version: "9.0.0"
automatic_animated_list: automatic_animated_list:
dependency: "direct main" dependency: "direct main"
description: description:
@ -317,10 +338,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: cross_file name: cross_file
sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.3.4+1" version: "0.3.4+2"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
@ -381,10 +402,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: dart_style name: dart_style
sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.3.7"
dbus: dbus:
dependency: transitive dependency: transitive
description: description:
@ -445,10 +466,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: extended_image_library name: extended_image_library
sha256: c9caee8fe9b6547bd41c960c4f2d1ef8e34321804de6a1777f1d614a24247ad6 sha256: "9a94ec9314aa206cfa35f16145c3cd6e2c924badcc670eaaca8a3a8063a68cd7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.4" version: "4.0.5"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -773,14 +794,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: cdae1b9c8bd7efadcef6112e81c903662ef2ce105cbd220a04bbb7c3425b5554
url: "https://pub.dev"
source: hosted
version: "14.2.0"
google_fonts: google_fonts:
dependency: "direct main" dependency: "direct main"
description: description:
@ -929,10 +942,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: just_audio_web name: just_audio_web
sha256: "0edb481ad4aa1ff38f8c40f1a3576013c3420bf6669b686fe661627d49bc606c" sha256: "9a98035b8b24b40749507687520ec5ab404e291d2b0937823ff45d92cb18d448"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.11" version: "0.4.13"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -1013,6 +1026,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
macros:
dependency: transitive
description:
name: macros
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
url: "https://pub.dev"
source: hosted
version: "0.1.2-main.4"
markdown: markdown:
dependency: transitive dependency: transitive
description: description:
@ -1161,18 +1182,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: package_info_plus name: package_info_plus
sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.0.0" version: "8.0.2"
package_info_plus_platform_interface: package_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: package_info_plus_platform_interface name: package_info_plus_platform_interface
sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.0" version: "3.0.1"
page_transition: page_transition:
dependency: "direct main" dependency: "direct main"
description: description:
@ -1345,10 +1366,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pointer_interceptor_web name: pointer_interceptor_web
sha256: a6237528b46c411d8d55cdfad8fcb3269fc4cbb26060b14bff94879165887d1e sha256: "7a7087782110f8c1827170660b09f8aa893e0e9a61431dbbe2ac3fc482e8c044"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.10.2" version: "0.10.2+1"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -1593,10 +1614,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_web name: shared_preferences_web
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.2.1"
shared_preferences_windows: shared_preferences_windows:
dependency: transitive dependency: transitive
description: description:
@ -1910,10 +1931,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.1" version: "2.3.3"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
@ -2030,10 +2051,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: wakelock_plus name: wakelock_plus
sha256: "14758533319a462ffb5aa3b7ddb198e59b29ac3b02da14173a1715d65d4e6e68" sha256: bf4ee6f17a2fa373ed3753ad0e602b7603f8c75af006d5b9bdade263928c0484
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.5" version: "1.2.8"
wakelock_plus_platform_interface: wakelock_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -2062,18 +2083,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: web name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "1.1.0"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.5" version: "2.4.0"
webview_flutter: webview_flutter:
dependency: transitive dependency: transitive
description: description:

View file

@ -91,7 +91,7 @@ dependencies:
flutter_widget_from_html: ^0.14.11 flutter_widget_from_html: ^0.14.11
# Navigation # Navigation
go_router: ^14.2.0 auto_route: ^9.2.2
url_launcher: ^6.1.10 url_launcher: ^6.1.10
flutter_custom_tabs: ^1.0.4 flutter_custom_tabs: ^1.0.4
@ -147,6 +147,7 @@ dev_dependencies:
swagger_dart_code_generator: ^2.15.2 swagger_dart_code_generator: ^2.15.2
riverpod_generator: ^2.4.0 riverpod_generator: ^2.4.0
dart_mappable_builder: ^4.2.3 dart_mappable_builder: ^4.2.3
auto_route_generator: ^9.0.0
flutter: flutter:
# The following line ensures that the Material Icons font is # The following line ensures that the Material Icons font is