mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 13:38:13 -08:00
chore: Lots of bug fixes and navigation improvements
This commit is contained in:
parent
9bb5e81812
commit
92d5391b93
35 changed files with 513 additions and 455 deletions
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||
|
|
@ -5,6 +7,7 @@ import 'package:iconsax_plus/iconsax_plus.dart';
|
|||
import 'package:fladder/jellyfin/jellyfin_open_api.enums.swagger.dart';
|
||||
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/library_filter_model.dart';
|
||||
|
||||
extension CollectionTypeExtension on CollectionType {
|
||||
IconData get iconOutlined {
|
||||
|
|
@ -30,11 +33,6 @@ extension CollectionTypeExtension on CollectionType {
|
|||
}
|
||||
}
|
||||
|
||||
bool get searchRecursive => switch (this) {
|
||||
CollectionType.homevideos || CollectionType.photos => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
IconData getIconType(bool outlined) {
|
||||
switch (this) {
|
||||
case CollectionType.music:
|
||||
|
|
@ -58,6 +56,16 @@ extension CollectionTypeExtension on CollectionType {
|
|||
}
|
||||
}
|
||||
|
||||
LibraryFilterModel get defaultFilters {
|
||||
log(name);
|
||||
return switch (this) {
|
||||
CollectionType.homevideos || CollectionType.photos => const LibraryFilterModel(recursive: false),
|
||||
_ => const LibraryFilterModel(
|
||||
recursive: true,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
double? get aspectRatio => switch (this) {
|
||||
CollectionType.music ||
|
||||
CollectionType.homevideos ||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,14 @@ import 'package:fladder/models/items/season_model.dart';
|
|||
import 'package:fladder/models/items/series_model.dart';
|
||||
import 'package:fladder/models/library_search/library_search_options.dart';
|
||||
import 'package:fladder/models/playlist_model.dart';
|
||||
import 'package:fladder/providers/api_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/screens/details_screens/book_detail_screen.dart';
|
||||
import 'package:fladder/screens/details_screens/details_screens.dart';
|
||||
import 'package:fladder/screens/details_screens/episode_detail_screen.dart';
|
||||
import 'package:fladder/screens/details_screens/season_detail_screen.dart';
|
||||
import 'package:fladder/screens/library_search/library_search_screen.dart';
|
||||
import 'package:fladder/screens/photo_viewer/photo_viewer_screen.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
import 'package:fladder/util/string_extensions.dart';
|
||||
|
||||
|
|
@ -140,15 +142,14 @@ class ItemBaseModel with ItemBaseModelMappable {
|
|||
case SeasonModel _:
|
||||
return SeasonDetailScreen(item: this);
|
||||
case FolderModel _:
|
||||
case PhotoAlbumModel _:
|
||||
case BoxSetModel _:
|
||||
case PlaylistModel _:
|
||||
case PhotoAlbumModel _:
|
||||
return LibrarySearchScreen(folderId: [id]);
|
||||
case PhotoModel _:
|
||||
final photo = this as PhotoModel;
|
||||
return LibrarySearchScreen(
|
||||
folderId: [photo.albumId ?? photo.parentId ?? ""],
|
||||
photoToView: photo,
|
||||
return PhotoViewerScreen(
|
||||
items: [photo],
|
||||
);
|
||||
case BookModel book:
|
||||
return BookDetailScreen(item: book);
|
||||
|
|
@ -163,7 +164,37 @@ class ItemBaseModel with ItemBaseModelMappable {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> navigateTo(BuildContext context) async => context.router.push(DetailsRoute(id: id, item: this));
|
||||
Future<void> navigateTo(BuildContext context, {WidgetRef? ref}) async {
|
||||
switch (this) {
|
||||
case FolderModel _:
|
||||
case BoxSetModel _:
|
||||
case PlaylistModel _:
|
||||
context.router.push(LibrarySearchRoute(folderId: [id], recursive: true));
|
||||
break;
|
||||
case PhotoAlbumModel _:
|
||||
context.router.push(LibrarySearchRoute(folderId: [id], recursive: false));
|
||||
break;
|
||||
case PhotoModel _:
|
||||
final photo = this as PhotoModel;
|
||||
context.router.push(
|
||||
PhotoViewerRoute(
|
||||
items: [photo],
|
||||
loadingItems: ref?.read(jellyApiProvider).itemsGetAlbumPhotos(albumId: photo.albumId),
|
||||
selected: photo.id,
|
||||
),
|
||||
);
|
||||
break;
|
||||
case BookModel _:
|
||||
case MovieModel _:
|
||||
case EpisodeModel _:
|
||||
case SeriesModel _:
|
||||
case SeasonModel _:
|
||||
case PersonModel _:
|
||||
default:
|
||||
context.router.push(DetailsRoute(id: id, item: this));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
factory ItemBaseModel.fromBaseDto(dto.BaseItemDto item, Ref ref) {
|
||||
return switch (item.type) {
|
||||
|
|
|
|||
|
|
@ -50,9 +50,9 @@ abstract class LibraryFilterModel with _$LibraryFilterModel {
|
|||
Map<FladderItemType, bool> types,
|
||||
@Default(SortingOptions.sortName) SortingOptions sortingOption,
|
||||
@Default(SortingOrder.ascending) SortingOrder sortOrder,
|
||||
@Default(false) bool favourites,
|
||||
@Default(false) bool? favourites,
|
||||
@Default(true) bool hideEmptyShows,
|
||||
@Default(true) bool recursive,
|
||||
@Default(true) bool? recursive,
|
||||
@Default(GroupBy.none) GroupBy groupBy,
|
||||
}) = _LibraryFilterModel;
|
||||
|
||||
|
|
@ -64,8 +64,8 @@ abstract class LibraryFilterModel with _$LibraryFilterModel {
|
|||
officialRatings.hasEnabled ||
|
||||
hideEmptyShows ||
|
||||
itemFilters.hasEnabled ||
|
||||
!recursive ||
|
||||
favourites;
|
||||
recursive == false ||
|
||||
favourites == true;
|
||||
}
|
||||
|
||||
LibraryFilterModel loadModel(LibraryFilterModel model) {
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ mixin _$LibraryFilterModel implements DiagnosticableTreeMixin {
|
|||
Map<FladderItemType, bool> get types;
|
||||
SortingOptions get sortingOption;
|
||||
SortingOrder get sortOrder;
|
||||
bool get favourites;
|
||||
bool? get favourites;
|
||||
bool get hideEmptyShows;
|
||||
bool get recursive;
|
||||
bool? get recursive;
|
||||
GroupBy get groupBy;
|
||||
|
||||
/// Create a copy of LibraryFilterModel
|
||||
|
|
@ -81,9 +81,9 @@ abstract mixin class $LibraryFilterModelCopyWith<$Res> {
|
|||
Map<FladderItemType, bool> types,
|
||||
SortingOptions sortingOption,
|
||||
SortingOrder sortOrder,
|
||||
bool favourites,
|
||||
bool? favourites,
|
||||
bool hideEmptyShows,
|
||||
bool recursive,
|
||||
bool? recursive,
|
||||
GroupBy groupBy});
|
||||
}
|
||||
|
||||
|
|
@ -109,9 +109,9 @@ class _$LibraryFilterModelCopyWithImpl<$Res>
|
|||
Object? types = null,
|
||||
Object? sortingOption = null,
|
||||
Object? sortOrder = null,
|
||||
Object? favourites = null,
|
||||
Object? favourites = freezed,
|
||||
Object? hideEmptyShows = null,
|
||||
Object? recursive = null,
|
||||
Object? recursive = freezed,
|
||||
Object? groupBy = null,
|
||||
}) {
|
||||
return _then(_self.copyWith(
|
||||
|
|
@ -151,18 +151,18 @@ class _$LibraryFilterModelCopyWithImpl<$Res>
|
|||
? _self.sortOrder
|
||||
: sortOrder // ignore: cast_nullable_to_non_nullable
|
||||
as SortingOrder,
|
||||
favourites: null == favourites
|
||||
favourites: freezed == favourites
|
||||
? _self.favourites
|
||||
: favourites // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
as bool?,
|
||||
hideEmptyShows: null == hideEmptyShows
|
||||
? _self.hideEmptyShows
|
||||
: hideEmptyShows // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
recursive: null == recursive
|
||||
recursive: freezed == recursive
|
||||
? _self.recursive
|
||||
: recursive // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
as bool?,
|
||||
groupBy: null == groupBy
|
||||
? _self.groupBy
|
||||
: groupBy // ignore: cast_nullable_to_non_nullable
|
||||
|
|
@ -274,9 +274,9 @@ extension LibraryFilterModelPatterns on LibraryFilterModel {
|
|||
Map<FladderItemType, bool> types,
|
||||
SortingOptions sortingOption,
|
||||
SortingOrder sortOrder,
|
||||
bool favourites,
|
||||
bool? favourites,
|
||||
bool hideEmptyShows,
|
||||
bool recursive,
|
||||
bool? recursive,
|
||||
GroupBy groupBy)?
|
||||
$default, {
|
||||
required TResult orElse(),
|
||||
|
|
@ -328,9 +328,9 @@ extension LibraryFilterModelPatterns on LibraryFilterModel {
|
|||
Map<FladderItemType, bool> types,
|
||||
SortingOptions sortingOption,
|
||||
SortingOrder sortOrder,
|
||||
bool favourites,
|
||||
bool? favourites,
|
||||
bool hideEmptyShows,
|
||||
bool recursive,
|
||||
bool? recursive,
|
||||
GroupBy groupBy)
|
||||
$default,
|
||||
) {
|
||||
|
|
@ -380,9 +380,9 @@ extension LibraryFilterModelPatterns on LibraryFilterModel {
|
|||
Map<FladderItemType, bool> types,
|
||||
SortingOptions sortingOption,
|
||||
SortingOrder sortOrder,
|
||||
bool favourites,
|
||||
bool? favourites,
|
||||
bool hideEmptyShows,
|
||||
bool recursive,
|
||||
bool? recursive,
|
||||
GroupBy groupBy)?
|
||||
$default,
|
||||
) {
|
||||
|
|
@ -529,13 +529,13 @@ class _LibraryFilterModel extends LibraryFilterModel
|
|||
final SortingOrder sortOrder;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool favourites;
|
||||
final bool? favourites;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool hideEmptyShows;
|
||||
@override
|
||||
@JsonKey()
|
||||
final bool recursive;
|
||||
final bool? recursive;
|
||||
@override
|
||||
@JsonKey()
|
||||
final GroupBy groupBy;
|
||||
|
|
@ -598,9 +598,9 @@ abstract mixin class _$LibraryFilterModelCopyWith<$Res>
|
|||
Map<FladderItemType, bool> types,
|
||||
SortingOptions sortingOption,
|
||||
SortingOrder sortOrder,
|
||||
bool favourites,
|
||||
bool? favourites,
|
||||
bool hideEmptyShows,
|
||||
bool recursive,
|
||||
bool? recursive,
|
||||
GroupBy groupBy});
|
||||
}
|
||||
|
||||
|
|
@ -626,9 +626,9 @@ class __$LibraryFilterModelCopyWithImpl<$Res>
|
|||
Object? types = null,
|
||||
Object? sortingOption = null,
|
||||
Object? sortOrder = null,
|
||||
Object? favourites = null,
|
||||
Object? favourites = freezed,
|
||||
Object? hideEmptyShows = null,
|
||||
Object? recursive = null,
|
||||
Object? recursive = freezed,
|
||||
Object? groupBy = null,
|
||||
}) {
|
||||
return _then(_LibraryFilterModel(
|
||||
|
|
@ -668,18 +668,18 @@ class __$LibraryFilterModelCopyWithImpl<$Res>
|
|||
? _self.sortOrder
|
||||
: sortOrder // ignore: cast_nullable_to_non_nullable
|
||||
as SortingOrder,
|
||||
favourites: null == favourites
|
||||
favourites: freezed == favourites
|
||||
? _self.favourites
|
||||
: favourites // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
as bool?,
|
||||
hideEmptyShows: null == hideEmptyShows
|
||||
? _self.hideEmptyShows
|
||||
: hideEmptyShows // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
recursive: null == recursive
|
||||
recursive: freezed == recursive
|
||||
? _self.recursive
|
||||
: recursive // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
as bool?,
|
||||
groupBy: null == groupBy
|
||||
? _self.groupBy
|
||||
: groupBy // ignore: cast_nullable_to_non_nullable
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'library_screen_provider.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$libraryScreenHash() => r'ff8b8514461c3e5da1aaf0933d6d49b014c3c05c';
|
||||
String _$libraryScreenHash() => r'792c4e47e5cd03635f42a4da4e24698c7584bbdb';
|
||||
|
||||
/// See also [LibraryScreen].
|
||||
@ProviderFor(LibraryScreen)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
|||
await loadViews(viewModelId, filters);
|
||||
}
|
||||
}
|
||||
|
||||
await loadFilters();
|
||||
|
||||
if (!wasInitialized) {
|
||||
|
|
@ -78,6 +79,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
|||
filters: state.filters.copyWith(
|
||||
types: state.filters.types.replaceMap(filters.types, enabledOnly: true),
|
||||
genres: state.filters.genres.replaceMap(filters.genres, enabledOnly: true),
|
||||
recursive: filters.recursive,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -158,7 +160,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
|||
} else if (state.views.hasEnabled) {
|
||||
await handleViewLoading();
|
||||
} else {
|
||||
if (state.searchQuery.isEmpty && !state.filters.favourites) {
|
||||
if (state.searchQuery.isEmpty && state.filters.favourites == false) {
|
||||
state = state.copyWith(posters: []);
|
||||
} else {
|
||||
final response = await _loadLibrary(recursive: true);
|
||||
|
|
@ -236,7 +238,6 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
|||
genres: {for (var element in genres) element.name: false}.replaceMap(tempFilters.genres),
|
||||
studios: {for (var element in studios) element: false}.replaceMap(tempFilters.studios),
|
||||
tags: {for (var element in tags) element: false}.replaceMap(tempFilters.tags),
|
||||
recursive: state.views.included.firstOrNull?.collectionType.searchRecursive ?? true,
|
||||
),
|
||||
);
|
||||
state = tempState;
|
||||
|
|
@ -295,7 +296,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
|||
}.toList(),
|
||||
filters: [
|
||||
...state.filters.itemFilters.included,
|
||||
if (state.filters.favourites) ItemFilter.isfavorite,
|
||||
if (state.filters.favourites == true) ItemFilter.isfavorite,
|
||||
],
|
||||
includeItemTypes: state.filters.types.included.map((e) => e.dtoKind).toList(),
|
||||
);
|
||||
|
|
@ -353,9 +354,9 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
|||
}
|
||||
|
||||
void toggleFavourite() =>
|
||||
state = state.copyWith(filters: state.filters.copyWith(favourites: !state.filters.favourites));
|
||||
state = state.copyWith(filters: state.filters.copyWith(favourites: state.filters.favourites == false));
|
||||
void toggleRecursive() =>
|
||||
state = state.copyWith(filters: state.filters.copyWith(recursive: !state.filters.recursive));
|
||||
state = state.copyWith(filters: state.filters.copyWith(recursive: state.filters.recursive == false));
|
||||
void toggleType(FladderItemType type) =>
|
||||
state = state.copyWith(filters: state.filters.copyWith(types: state.filters.types.toggleKey(type)));
|
||||
void toggleView(ViewModel view) => state = state.copyWith(views: state.views.toggleKey(view));
|
||||
|
|
@ -569,7 +570,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
|||
} else if (state.views.hasEnabled) {
|
||||
await handleViewLoading();
|
||||
} else {
|
||||
if (state.searchQuery.isEmpty && !state.filters.favourites) {
|
||||
if (state.searchQuery.isEmpty && state.filters.favourites == false) {
|
||||
itemsToPlay = [];
|
||||
} else {
|
||||
final response = await _loadLibrary(recursive: true, shuffle: shuffle);
|
||||
|
|
@ -612,7 +613,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
|||
List<PhotoModel> albumItems = [];
|
||||
|
||||
if (!state.filters.types.included.containsAny([FladderItemType.video, FladderItemType.photo]) &&
|
||||
state.filters.recursive) {
|
||||
state.filters.recursive == true) {
|
||||
for (var album in itemsToPlay.where(
|
||||
(element) => element is PhotoAlbumModel || element is FolderModel,
|
||||
)) {
|
||||
|
|
@ -634,7 +635,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
|||
}.toList(),
|
||||
filters: [
|
||||
...state.filters.itemFilters.included,
|
||||
if (state.filters.favourites) ItemFilter.isfavorite,
|
||||
if (state.filters.favourites == true) ItemFilter.isfavorite,
|
||||
],
|
||||
sortBy: shuffle ? [ItemSortBy.random] : null,
|
||||
);
|
||||
|
|
@ -665,7 +666,7 @@ class LibrarySearchNotifier extends StateNotifier<LibrarySearchModel> {
|
|||
allItems = await showLoadingOverlay(context, callBack: fetchGallery(shuffle: shuffle));
|
||||
if (allItems.isNotEmpty) {
|
||||
final newItemList = shuffle ? allItems.shuffled() : allItems;
|
||||
await context.navigateTo(PhotoViewerRoute(
|
||||
await context.pushRoute(PhotoViewerRoute(
|
||||
items: newItemList,
|
||||
selected: selected?.id,
|
||||
));
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import 'package:fladder/models/item_base_model.dart';
|
|||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/media_segments_model.dart';
|
||||
import 'package:fladder/models/items/photos_model.dart';
|
||||
import 'package:fladder/models/items/trick_play_model.dart';
|
||||
import 'package:fladder/providers/auth_provider.dart';
|
||||
import 'package:fladder/providers/image_provider.dart';
|
||||
|
|
@ -344,6 +345,20 @@ class JellyService {
|
|||
);
|
||||
}
|
||||
|
||||
Future<List<PhotoModel>> itemsGetAlbumPhotos({
|
||||
String? albumId,
|
||||
}) async {
|
||||
final response = await itemsGet(
|
||||
parentId: albumId,
|
||||
enableUserData: true,
|
||||
fields: [
|
||||
ItemFields.parentid,
|
||||
ItemFields.datecreated,
|
||||
],
|
||||
);
|
||||
return response.body?.items.whereType<PhotoModel>().toList() ?? [];
|
||||
}
|
||||
|
||||
Future<Response<List<ItemBaseModel>>> personsGet({
|
||||
String? searchTerm,
|
||||
int? limit,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ part of 'background_download_provider.dart';
|
|||
// **************************************************************************
|
||||
|
||||
String _$backgroundDownloaderHash() =>
|
||||
r'9d866549ed7632e855ba30de2765368960889cff';
|
||||
r'2571c078d018150bf93c6f35735f58f16cbca3dd';
|
||||
|
||||
/// See also [BackgroundDownloader].
|
||||
@ProviderFor(BackgroundDownloader)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
|
|
@ -21,14 +19,7 @@ class AutoRouter extends RootStackRouter {
|
|||
List<AutoRouteGuard> get guards => [...super.guards, AuthGuard(ref: ref)];
|
||||
|
||||
@override
|
||||
RouteType get defaultRouteType => kIsWeb ||
|
||||
{
|
||||
TargetPlatform.windows,
|
||||
TargetPlatform.linux,
|
||||
TargetPlatform.macOS,
|
||||
}.contains(defaultTargetPlatform)
|
||||
? const RouteType.cupertino()
|
||||
: const RouteType.adaptive();
|
||||
RouteType get defaultRouteType => const RouteType.adaptive();
|
||||
|
||||
@override
|
||||
List<AutoRoute> get routes => [
|
||||
|
|
@ -40,26 +31,43 @@ class AutoRouter extends RootStackRouter {
|
|||
_homeRoute.copyWith(
|
||||
children: [
|
||||
...homeRoutes,
|
||||
AutoRoute(page: DetailsRoute.page, path: 'details', usesPathAsKey: true),
|
||||
AutoRoute(page: LibrarySearchRoute.page, path: 'library', usesPathAsKey: true),
|
||||
CustomRoute(
|
||||
...detailsRoutes,
|
||||
AutoRoute(
|
||||
page: SettingsRoute.page,
|
||||
path: settingsPageRoute,
|
||||
children: _settingsChildren,
|
||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||
),
|
||||
],
|
||||
),
|
||||
AutoRoute(page: LockRoute.page, path: '/locked'),
|
||||
AutoRoute(page: PhotoViewerRoute.page, path: "/album"),
|
||||
];
|
||||
}
|
||||
|
||||
final AutoRoute _homeRoute = AutoRoute(page: HomeRoute.page, path: '/');
|
||||
final List<AutoRoute> homeRoutes = [
|
||||
_dashboardRoute,
|
||||
_favouritesRoute,
|
||||
_syncedRoute,
|
||||
_librariesRoute,
|
||||
AutoRoute(
|
||||
page: DashboardRoute.page,
|
||||
initial: true,
|
||||
path: 'dashboard',
|
||||
),
|
||||
AutoRoute(
|
||||
page: FavouritesRoute.page,
|
||||
path: 'favourites',
|
||||
),
|
||||
AutoRoute(
|
||||
page: SyncedRoute.page,
|
||||
path: 'synced',
|
||||
),
|
||||
AutoRoute(
|
||||
page: LibraryRoute.page,
|
||||
path: 'libraries',
|
||||
),
|
||||
];
|
||||
|
||||
final List<AutoRoute> detailsRoutes = [
|
||||
AutoRoute(page: DetailsRoute.page, path: 'details'),
|
||||
AutoRoute(page: PhotoViewerRoute.page, path: "album"),
|
||||
AutoRoute(page: LibrarySearchRoute.page, path: 'library'),
|
||||
];
|
||||
|
||||
final List<AutoRoute> _defaultRoutes = [
|
||||
|
|
@ -67,40 +75,12 @@ final List<AutoRoute> _defaultRoutes = [
|
|||
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,
|
||||
maintainState: false,
|
||||
path: 'dashboard',
|
||||
);
|
||||
final AutoRoute _favouritesRoute = CustomRoute(
|
||||
page: FavouritesRoute.page,
|
||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||
maintainState: false,
|
||||
path: 'favourites',
|
||||
);
|
||||
final AutoRoute _syncedRoute = CustomRoute(
|
||||
page: SyncedRoute.page,
|
||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||
maintainState: false,
|
||||
path: 'synced',
|
||||
);
|
||||
|
||||
final AutoRoute _librariesRoute = CustomRoute(
|
||||
page: LibraryRoute.page,
|
||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||
maintainState: false,
|
||||
path: 'libraries',
|
||||
);
|
||||
|
||||
final List<AutoRoute> _settingsChildren = [
|
||||
CustomRoute(page: SettingsSelectionRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'list'),
|
||||
CustomRoute(page: ClientSettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'client'),
|
||||
CustomRoute(page: SecuritySettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'security'),
|
||||
CustomRoute(page: PlayerSettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'player'),
|
||||
CustomRoute(page: AboutSettingsRoute.page, transitionsBuilder: TransitionsBuilders.fadeIn, path: 'about'),
|
||||
AutoRoute(page: SettingsSelectionRoute.page, path: 'list'),
|
||||
AutoRoute(page: ClientSettingsRoute.page, path: 'client'),
|
||||
AutoRoute(page: SecuritySettingsRoute.page, path: 'security'),
|
||||
AutoRoute(page: PlayerSettingsRoute.page, path: 'player'),
|
||||
AutoRoute(page: AboutSettingsRoute.page, path: 'about'),
|
||||
];
|
||||
|
||||
class LockScreenGuard extends AutoRouteGuard {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@
|
|||
import 'dart:async' as _i24;
|
||||
|
||||
import 'package:auto_route/auto_route.dart' as _i18;
|
||||
import 'package:collection/collection.dart' as _i23;
|
||||
import 'package:collection/collection.dart' as _i22;
|
||||
import 'package:fladder/models/item_base_model.dart' as _i19;
|
||||
import 'package:fladder/models/items/photos_model.dart' as _i22;
|
||||
import 'package:fladder/models/items/photos_model.dart' as _i23;
|
||||
import 'package:fladder/models/library_search/library_search_options.dart'
|
||||
as _i21;
|
||||
import 'package:fladder/routes/nested_details_screen.dart' as _i4;
|
||||
|
|
@ -200,8 +200,7 @@ class LibrarySearchRoute extends _i18.PageRouteInfo<LibrarySearchRouteArgs> {
|
|||
_i21.SortingOptions? sortingOptions,
|
||||
Map<_i19.FladderItemType, bool>? types,
|
||||
Map<String, bool>? genres,
|
||||
bool recursive = true,
|
||||
_i22.PhotoModel? photoToView,
|
||||
bool? recursive,
|
||||
_i20.Key? key,
|
||||
List<_i18.PageRouteInfo>? children,
|
||||
}) : super(
|
||||
|
|
@ -215,7 +214,6 @@ class LibrarySearchRoute extends _i18.PageRouteInfo<LibrarySearchRouteArgs> {
|
|||
types: types,
|
||||
genres: genres,
|
||||
recursive: recursive,
|
||||
photoToView: photoToView,
|
||||
key: key,
|
||||
),
|
||||
rawQueryParams: {
|
||||
|
|
@ -246,7 +244,7 @@ class LibrarySearchRoute extends _i18.PageRouteInfo<LibrarySearchRouteArgs> {
|
|||
sortingOptions: queryParams.get('sortOptions'),
|
||||
types: queryParams.get('itemTypes'),
|
||||
genres: queryParams.get('genres'),
|
||||
recursive: queryParams.getBool('recursive', true),
|
||||
recursive: queryParams.optBool('recursive'),
|
||||
),
|
||||
);
|
||||
return _i8.LibrarySearchScreen(
|
||||
|
|
@ -258,7 +256,6 @@ class LibrarySearchRoute extends _i18.PageRouteInfo<LibrarySearchRouteArgs> {
|
|||
types: args.types,
|
||||
genres: args.genres,
|
||||
recursive: args.recursive,
|
||||
photoToView: args.photoToView,
|
||||
key: args.key,
|
||||
);
|
||||
},
|
||||
|
|
@ -274,8 +271,7 @@ class LibrarySearchRouteArgs {
|
|||
this.sortingOptions,
|
||||
this.types,
|
||||
this.genres,
|
||||
this.recursive = true,
|
||||
this.photoToView,
|
||||
this.recursive,
|
||||
this.key,
|
||||
});
|
||||
|
||||
|
|
@ -293,15 +289,13 @@ class LibrarySearchRouteArgs {
|
|||
|
||||
final Map<String, bool>? genres;
|
||||
|
||||
final bool recursive;
|
||||
|
||||
final _i22.PhotoModel? photoToView;
|
||||
final bool? recursive;
|
||||
|
||||
final _i20.Key? key;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'LibrarySearchRouteArgs{viewModelId: $viewModelId, folderId: $folderId, favourites: $favourites, sortOrder: $sortOrder, sortingOptions: $sortingOptions, types: $types, genres: $genres, recursive: $recursive, photoToView: $photoToView, key: $key}';
|
||||
return 'LibrarySearchRouteArgs{viewModelId: $viewModelId, folderId: $folderId, favourites: $favourites, sortOrder: $sortOrder, sortingOptions: $sortingOptions, types: $types, genres: $genres, recursive: $recursive, key: $key}';
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -309,28 +303,26 @@ class LibrarySearchRouteArgs {
|
|||
if (identical(this, other)) return true;
|
||||
if (other is! LibrarySearchRouteArgs) return false;
|
||||
return viewModelId == other.viewModelId &&
|
||||
const _i23.ListEquality().equals(folderId, other.folderId) &&
|
||||
const _i22.ListEquality().equals(folderId, other.folderId) &&
|
||||
favourites == other.favourites &&
|
||||
sortOrder == other.sortOrder &&
|
||||
sortingOptions == other.sortingOptions &&
|
||||
const _i23.MapEquality().equals(types, other.types) &&
|
||||
const _i23.MapEquality().equals(genres, other.genres) &&
|
||||
const _i22.MapEquality().equals(types, other.types) &&
|
||||
const _i22.MapEquality().equals(genres, other.genres) &&
|
||||
recursive == other.recursive &&
|
||||
photoToView == other.photoToView &&
|
||||
key == other.key;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
viewModelId.hashCode ^
|
||||
const _i23.ListEquality().hash(folderId) ^
|
||||
const _i22.ListEquality().hash(folderId) ^
|
||||
favourites.hashCode ^
|
||||
sortOrder.hashCode ^
|
||||
sortingOptions.hashCode ^
|
||||
const _i23.MapEquality().hash(types) ^
|
||||
const _i23.MapEquality().hash(genres) ^
|
||||
const _i22.MapEquality().hash(types) ^
|
||||
const _i22.MapEquality().hash(genres) ^
|
||||
recursive.hashCode ^
|
||||
photoToView.hashCode ^
|
||||
key.hashCode;
|
||||
}
|
||||
|
||||
|
|
@ -370,9 +362,9 @@ class LoginRoute extends _i18.PageRouteInfo<void> {
|
|||
/// [_i11.PhotoViewerScreen]
|
||||
class PhotoViewerRoute extends _i18.PageRouteInfo<PhotoViewerRouteArgs> {
|
||||
PhotoViewerRoute({
|
||||
List<_i22.PhotoModel>? items,
|
||||
List<_i23.PhotoModel>? items,
|
||||
String? selected,
|
||||
_i24.Future<List<_i22.PhotoModel>>? loadingItems,
|
||||
_i24.Future<List<_i23.PhotoModel>>? loadingItems,
|
||||
_i25.Key? key,
|
||||
List<_i18.PageRouteInfo>? children,
|
||||
}) : super(
|
||||
|
|
@ -415,11 +407,11 @@ class PhotoViewerRouteArgs {
|
|||
this.key,
|
||||
});
|
||||
|
||||
final List<_i22.PhotoModel>? items;
|
||||
final List<_i23.PhotoModel>? items;
|
||||
|
||||
final String? selected;
|
||||
|
||||
final _i24.Future<List<_i22.PhotoModel>>? loadingItems;
|
||||
final _i24.Future<List<_i23.PhotoModel>>? loadingItems;
|
||||
|
||||
final _i25.Key? key;
|
||||
|
||||
|
|
@ -432,7 +424,7 @@ class PhotoViewerRouteArgs {
|
|||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is! PhotoViewerRouteArgs) return false;
|
||||
return const _i23.ListEquality().equals(items, other.items) &&
|
||||
return const _i22.ListEquality().equals(items, other.items) &&
|
||||
selected == other.selected &&
|
||||
loadingItems == other.loadingItems &&
|
||||
key == other.key;
|
||||
|
|
@ -440,7 +432,7 @@ class PhotoViewerRouteArgs {
|
|||
|
||||
@override
|
||||
int get hashCode =>
|
||||
const _i23.ListEquality().hash(items) ^
|
||||
const _i22.ListEquality().hash(items) ^
|
||||
selected.hashCode ^
|
||||
loadingItems.hashCode ^
|
||||
key.hashCode;
|
||||
|
|
@ -561,56 +553,16 @@ class SplashRouteArgs {
|
|||
|
||||
/// generated route for
|
||||
/// [_i17.SyncedScreen]
|
||||
class SyncedRoute extends _i18.PageRouteInfo<SyncedRouteArgs> {
|
||||
SyncedRoute({
|
||||
_i25.ScrollController? navigationScrollController,
|
||||
_i20.Key? key,
|
||||
List<_i18.PageRouteInfo>? children,
|
||||
}) : super(
|
||||
SyncedRoute.name,
|
||||
args: SyncedRouteArgs(
|
||||
navigationScrollController: navigationScrollController,
|
||||
key: key,
|
||||
),
|
||||
initialChildren: children,
|
||||
);
|
||||
class SyncedRoute extends _i18.PageRouteInfo<void> {
|
||||
const SyncedRoute({List<_i18.PageRouteInfo>? children})
|
||||
: super(SyncedRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'SyncedRoute';
|
||||
|
||||
static _i18.PageInfo page = _i18.PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<SyncedRouteArgs>(
|
||||
orElse: () => const SyncedRouteArgs(),
|
||||
);
|
||||
return _i17.SyncedScreen(
|
||||
navigationScrollController: args.navigationScrollController,
|
||||
key: args.key,
|
||||
);
|
||||
return const _i17.SyncedScreen();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class SyncedRouteArgs {
|
||||
const SyncedRouteArgs({this.navigationScrollController, this.key});
|
||||
|
||||
final _i25.ScrollController? navigationScrollController;
|
||||
|
||||
final _i20.Key? key;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SyncedRouteArgs{navigationScrollController: $navigationScrollController, key: $key}';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
if (other is! SyncedRouteArgs) return false;
|
||||
return navigationScrollController == other.navigationScrollController &&
|
||||
key == other.key;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => navigationScrollController.hashCode ^ key.hashCode;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import 'package:fladder/providers/user_provider.dart';
|
|||
import 'package:fladder/providers/views_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/screens/dashboard/home_banner_widget.dart';
|
||||
import 'package:fladder/screens/home_screen.dart';
|
||||
import 'package:fladder/screens/shared/media/poster_row.dart';
|
||||
import 'package:fladder/screens/shared/nested_scaffold.dart';
|
||||
import 'package:fladder/screens/shared/nested_sliver_appbar.dart';
|
||||
|
|
@ -97,7 +98,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
|||
child: PinchPosterZoom(
|
||||
scaleDifference: (difference) => ref.read(clientSettingsProvider.notifier).addPosterSize(difference),
|
||||
child: CustomScrollView(
|
||||
controller: AdaptiveLayout.scrollOf(context),
|
||||
controller: AdaptiveLayout.scrollOf(context, HomeTabs.dashboard),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
const DefaultSliverTopBadding(),
|
||||
|
|
@ -196,6 +197,7 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
|
|||
_ => SortingOptions.dateAdded,
|
||||
},
|
||||
sortOrder: SortingOrder.descending,
|
||||
recursive: true,
|
||||
),
|
||||
),
|
||||
posters: view.recentlyAdded,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class FolderDetailScreen extends ConsumerWidget {
|
|||
switch (item) {
|
||||
case PhotoModel photoModel:
|
||||
final photoItems = details?.items.whereType<PhotoModel>().toList();
|
||||
await context.navigateTo(PhotoViewerRoute(
|
||||
await context.pushRoute(PhotoViewerRoute(
|
||||
items: photoItems,
|
||||
selected: photoModel.id,
|
||||
));
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:fladder/models/library_filter_model.dart';
|
|||
import 'package:fladder/providers/favourites_provider.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/screens/home_screen.dart';
|
||||
import 'package:fladder/screens/shared/media/poster_row.dart';
|
||||
import 'package:fladder/screens/shared/nested_scaffold.dart';
|
||||
import 'package:fladder/screens/shared/nested_sliver_appbar.dart';
|
||||
|
|
@ -35,7 +36,7 @@ class FavouritesScreen extends ConsumerWidget {
|
|||
scaleDifference: (difference) => ref.read(clientSettingsProvider.notifier).addPosterSize(difference / 2),
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: AdaptiveLayout.scrollOf(context),
|
||||
controller: AdaptiveLayout.scrollOf(context, HomeTabs.favorites),
|
||||
slivers: [
|
||||
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.phone)
|
||||
NestedSliverAppBar(
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ enum HomeTabs {
|
|||
HomeTabs.dashboard => context.router.navigate(const DashboardRoute()),
|
||||
HomeTabs.library => context.router.navigate(const LibraryRoute()),
|
||||
HomeTabs.favorites => context.router.navigate(const FavouritesRoute()),
|
||||
HomeTabs.sync => context.router.navigate(SyncedRoute()),
|
||||
HomeTabs.sync => context.router.navigate(const SyncedRoute()),
|
||||
};
|
||||
|
||||
String label(BuildContext context) => switch (this) {
|
||||
|
|
@ -101,7 +101,7 @@ class HomeScreen extends ConsumerWidget {
|
|||
label: context.localized.navigationSync,
|
||||
icon: Icon(e.icon),
|
||||
selectedIcon: Icon(e.selectedIcon),
|
||||
route: SyncedRoute(),
|
||||
route: const SyncedRoute(),
|
||||
action: () => e.navigate(context),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import 'package:fladder/models/recommended_model.dart';
|
|||
import 'package:fladder/models/view_model.dart';
|
||||
import 'package:fladder/providers/library_screen_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/screens/home_screen.dart';
|
||||
import 'package:fladder/screens/metadata/refresh_metadata.dart';
|
||||
import 'package:fladder/screens/shared/flat_button.dart';
|
||||
import 'package:fladder/screens/shared/media/poster_row.dart';
|
||||
|
|
@ -62,7 +63,7 @@ class _LibraryScreenState extends ConsumerState<LibraryScreen> with SingleTicker
|
|||
onRefresh: () => ref.read(libraryScreenProvider.notifier).fetchAllLibraries(),
|
||||
child: SizedBox.expand(
|
||||
child: CustomScrollView(
|
||||
controller: AdaptiveLayout.scrollOf(context),
|
||||
controller: AdaptiveLayout.scrollOf(context, HomeTabs.library),
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
const DefaultSliverTopBadding(),
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:iconsax_plus/iconsax_plus.dart';
|
|||
|
||||
import 'package:fladder/models/boxset_model.dart';
|
||||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/photos_model.dart';
|
||||
import 'package:fladder/models/library_filter_model.dart';
|
||||
import 'package:fladder/models/library_search/library_search_model.dart';
|
||||
import 'package:fladder/models/library_search/library_search_options.dart';
|
||||
|
|
@ -55,8 +54,7 @@ class LibrarySearchScreen extends ConsumerStatefulWidget {
|
|||
final SortingOptions? sortingOptions;
|
||||
final Map<FladderItemType, bool>? types;
|
||||
final Map<String, bool>? genres;
|
||||
final bool recursive;
|
||||
final PhotoModel? photoToView;
|
||||
final bool? recursive;
|
||||
const LibrarySearchScreen({
|
||||
@QueryParam("parentId") this.viewModelId,
|
||||
@QueryParam("folderId") this.folderId,
|
||||
|
|
@ -65,8 +63,7 @@ class LibrarySearchScreen extends ConsumerStatefulWidget {
|
|||
@QueryParam("sortOptions") this.sortingOptions,
|
||||
@QueryParam("itemTypes") this.types,
|
||||
@QueryParam("genres") this.genres,
|
||||
@QueryParam("recursive") this.recursive = true,
|
||||
this.photoToView,
|
||||
@QueryParam("recursive") this.recursive,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -109,10 +106,6 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
|
|||
SystemUiMode.edgeToEdge,
|
||||
overlays: [],
|
||||
);
|
||||
|
||||
if (context.mounted && widget.photoToView != null) {
|
||||
libraryProvider.viewGallery(context, selected: widget.photoToView);
|
||||
}
|
||||
scrollController.addListener(() {
|
||||
scrollPosition();
|
||||
});
|
||||
|
|
@ -227,7 +220,7 @@ class _LibrarySearchScreenState extends ConsumerState<LibrarySearchScreen> {
|
|||
widget.folderId,
|
||||
widget.viewModelId,
|
||||
defaultFilter.copyWith(
|
||||
favourites: widget.favourites ?? defaultFilter.favourites,
|
||||
favourites: widget.favourites,
|
||||
sortOrder: widget.sortOrder ?? defaultFilter.sortOrder,
|
||||
sortingOption: widget.sortingOptions ?? defaultFilter.sortingOption,
|
||||
types: widget.types ?? {},
|
||||
|
|
|
|||
|
|
@ -59,8 +59,8 @@ class _LibraryFilterChipsState extends ConsumerState<LibraryFilterChips> {
|
|||
onClear: () => libraryProvider.setTypes(librarySearchResults.filters.types.setAll(false)),
|
||||
),
|
||||
ExpressiveButton(
|
||||
isSelected: favourites,
|
||||
icon: favourites ? const Icon(IconsaxPlusBold.heart) : null,
|
||||
isSelected: favourites == true,
|
||||
icon: favourites == true ? const Icon(IconsaxPlusBold.heart) : null,
|
||||
label: Text(context.localized.favorites),
|
||||
onPressed: () {
|
||||
libraryProvider.toggleFavourite();
|
||||
|
|
@ -68,8 +68,8 @@ class _LibraryFilterChipsState extends ConsumerState<LibraryFilterChips> {
|
|||
},
|
||||
),
|
||||
ExpressiveButton(
|
||||
isSelected: recursive,
|
||||
icon: recursive ? const Icon(IconsaxPlusBold.tick_circle) : null,
|
||||
isSelected: recursive == true,
|
||||
icon: recursive == true ? const Icon(IconsaxPlusBold.tick_circle) : null,
|
||||
label: Text(context.localized.recursive),
|
||||
onPressed: () {
|
||||
libraryProvider.toggleRecursive();
|
||||
|
|
|
|||
|
|
@ -61,18 +61,23 @@ class ItemInfoScreenState extends ConsumerState<ItemInfoScreen> {
|
|||
Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
spacing: 6,
|
||||
spacing: 12,
|
||||
children: [
|
||||
Text(
|
||||
widget.item.name,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.item.name,
|
||||
softWrap: false,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
onPressed: () => context.copyToClipboard(info.model.toString()),
|
||||
icon: const Icon(Icons.copy_all_rounded)),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
|
@ -23,6 +24,7 @@ import 'package:fladder/util/throttler.dart';
|
|||
import 'package:fladder/widgets/full_screen_helpers/full_screen_wrapper.dart';
|
||||
import 'package:fladder/widgets/shared/elevated_icon.dart';
|
||||
import 'package:fladder/widgets/shared/progress_floating_button.dart';
|
||||
import 'package:fladder/widgets/shared/selectable_icon_button.dart';
|
||||
|
||||
class PhotoViewerControls extends ConsumerStatefulWidget {
|
||||
final EdgeInsets padding;
|
||||
|
|
@ -309,13 +311,19 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
|||
children: [
|
||||
ElevatedIconButton(
|
||||
onPressed: widget.openOptions,
|
||||
icon: IconsaxPlusLinear.more_2,
|
||||
icon: IconsaxPlusLinear.more_square,
|
||||
),
|
||||
const Spacer(),
|
||||
ElevatedIconButton(
|
||||
SelectableIconButton(
|
||||
onPressed: markAsFavourite,
|
||||
color: widget.photo.userData.isFavourite ? Colors.red : null,
|
||||
selected: false,
|
||||
icon: widget.photo.userData.isFavourite ? IconsaxPlusBold.heart : IconsaxPlusLinear.heart,
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary
|
||||
.harmonizeWith(Colors.red)
|
||||
.withValues(alpha: 0.25),
|
||||
iconColor: widget.photo.userData.isFavourite ? Colors.red : null,
|
||||
),
|
||||
ProgressFloatingButton(
|
||||
controller: timerController,
|
||||
|
|
@ -341,8 +349,11 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
|||
);
|
||||
}
|
||||
|
||||
void markAsFavourite() {
|
||||
ref.read(userProvider.notifier).setAsFavorite(!widget.photo.userData.isFavourite, widget.photo.id);
|
||||
Future<void> markAsFavourite() async {
|
||||
final response =
|
||||
await ref.read(userProvider.notifier).setAsFavorite(!widget.photo.userData.isFavourite, widget.photo.id);
|
||||
|
||||
if (response?.isSuccessful == false) return;
|
||||
|
||||
widget.onPhotoChanged(widget.photo
|
||||
.copyWith(userData: widget.photo.userData.copyWith(isFavourite: !widget.photo.userData.isFavourite)));
|
||||
|
|
|
|||
|
|
@ -90,7 +90,13 @@ class _PhotoViewerScreenState extends ConsumerState<PhotoViewerScreen> with Widg
|
|||
|
||||
if (context.mounted) {
|
||||
setState(() {
|
||||
photos = {...photos, ...newItems}.toList();
|
||||
if (photos.length == 1 && newItems.contains(photos.first)) {
|
||||
photos = newItems;
|
||||
currentPage = photos.indexWhere((value) => value.id == widget.selected).clamp(0, photos.length - 1);
|
||||
controller.jumpToPage(currentPage);
|
||||
} else {
|
||||
photos = {...photos, ...newItems}.toList();
|
||||
}
|
||||
loadingItems = false;
|
||||
});
|
||||
}
|
||||
|
|
@ -165,14 +171,14 @@ class _PhotoViewerScreenState extends ConsumerState<PhotoViewerScreen> with Widg
|
|||
? Center(
|
||||
child: Text(context.localized.noItemsToShow),
|
||||
)
|
||||
: buildViewer(),
|
||||
: buildViewer(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildViewer() {
|
||||
Widget buildViewer(BuildContext context) {
|
||||
final currentPhoto = photos[currentPage];
|
||||
final imageHash = currentPhoto.images?.primary?.hash;
|
||||
return Stack(
|
||||
|
|
@ -498,8 +504,8 @@ class _PhotoViewerScreenState extends ConsumerState<PhotoViewerScreen> with Widg
|
|||
},
|
||||
);
|
||||
|
||||
void markAsFavourite(PhotoModel photo, {bool? value}) {
|
||||
ref.read(userProvider.notifier).setAsFavorite(value ?? !photo.userData.isFavourite, photo.id);
|
||||
Future<void> markAsFavourite(PhotoModel photo, {bool? value}) async {
|
||||
await ref.read(userProvider.notifier).setAsFavorite(value ?? !photo.userData.isFavourite, photo.id);
|
||||
|
||||
setState(() {
|
||||
int index = photos.indexOf(photo);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ class ItemLogo extends StatelessWidget {
|
|||
child: Text(
|
||||
item.parentBaseModel.name,
|
||||
textAlign: TextAlign.start,
|
||||
maxLines: 3,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.fade,
|
||||
style: textStyle ??
|
||||
Theme.of(context).textTheme.headlineLarge?.copyWith(
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class _PosterImageState extends ConsumerState<PosterImage> {
|
|||
}
|
||||
|
||||
Future<void> navigateToDetails() async {
|
||||
await widget.poster.navigateTo(context);
|
||||
await widget.poster.navigateTo(context, ref: ref);
|
||||
}
|
||||
|
||||
final posterRadius = FladderTheme.smallShape.borderRadius;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class _PosterRowState extends ConsumerState<PosterRow> {
|
|||
contentPadding: widget.contentPadding,
|
||||
label: widget.label,
|
||||
onLabelClick: widget.onLabelClick,
|
||||
dominantRatio: dominantRatio,
|
||||
items: widget.posters,
|
||||
itemBuilder: (context, index) {
|
||||
final poster = widget.posters[index];
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:iconsax_plus/iconsax_plus.dart';
|
|||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
import 'package:fladder/screens/home_screen.dart';
|
||||
import 'package:fladder/screens/shared/nested_scaffold.dart';
|
||||
import 'package:fladder/screens/shared/nested_sliver_appbar.dart';
|
||||
import 'package:fladder/screens/syncing/sync_list_item.dart';
|
||||
|
|
@ -20,9 +21,7 @@ import 'package:fladder/widgets/shared/pull_to_refresh.dart';
|
|||
|
||||
@RoutePage()
|
||||
class SyncedScreen extends ConsumerStatefulWidget {
|
||||
final ScrollController? navigationScrollController;
|
||||
|
||||
const SyncedScreen({this.navigationScrollController, super.key});
|
||||
const SyncedScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<ConsumerStatefulWidget> createState() => _SyncedScreenState();
|
||||
|
|
@ -43,7 +42,7 @@ class _SyncedScreenState extends ConsumerState<SyncedScreen> {
|
|||
scaleDifference: (difference) => ref.read(clientSettingsProvider.notifier).addPosterSize(difference / 2),
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: widget.navigationScrollController,
|
||||
controller: AdaptiveLayout.scrollOf(context, HomeTabs.sync),
|
||||
slivers: [
|
||||
if (AdaptiveLayout.viewSizeOf(context) == ViewSize.phone)
|
||||
NestedSliverAppBar(
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
|
||||
import 'package:fladder/models/settings/home_settings_model.dart';
|
||||
import 'package:fladder/providers/settings/home_settings_provider.dart';
|
||||
import 'package:fladder/screens/home_screen.dart';
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout_model.dart';
|
||||
import 'package:fladder/util/debug_banner.dart';
|
||||
import 'package:fladder/util/localization_helper.dart';
|
||||
|
|
@ -81,9 +82,9 @@ class AdaptiveLayout extends InheritedWidget {
|
|||
return result!.data.posterDefaults;
|
||||
}
|
||||
|
||||
static ScrollController scrollOf(BuildContext context) {
|
||||
static ScrollController scrollOf(BuildContext context, HomeTabs tab) {
|
||||
final AdaptiveLayout? result = maybeOf(context);
|
||||
return result!.data.controller;
|
||||
return result?.data.controller[tab] ?? ScrollController();
|
||||
}
|
||||
|
||||
static EdgeInsets adaptivePadding(BuildContext context, {double horizontalPadding = 16}) {
|
||||
|
|
@ -130,7 +131,10 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
|
|||
late ViewSize viewSize = ViewSize.tablet;
|
||||
late LayoutMode layoutMode = LayoutMode.single;
|
||||
late TargetPlatform currentPlatform = defaultTargetPlatform;
|
||||
late ScrollController controller = ScrollController();
|
||||
|
||||
final Map<HomeTabs, ScrollController> scrollControllers = {
|
||||
for (var item in HomeTabs.values) item: ScrollController(),
|
||||
};
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
|
|
@ -177,11 +181,7 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
|
|||
final selectedLayoutMode = selectAvailableOrSmaller<LayoutMode>(layoutMode, acceptedLayouts, LayoutMode.values);
|
||||
final input = (isDesktop || kIsWeb) ? InputDevice.pointer : InputDevice.touch;
|
||||
|
||||
final posterDefaults = switch (selectedViewSize) {
|
||||
ViewSize.phone => const PosterDefaults(size: 300, ratio: 0.55),
|
||||
ViewSize.tablet => const PosterDefaults(size: 350, ratio: 0.55),
|
||||
ViewSize.desktop => const PosterDefaults(size: 400, ratio: 0.55),
|
||||
};
|
||||
final posterDefaults = const PosterDefaults(size: 350, ratio: 0.55);
|
||||
|
||||
final currentLayout = widget.adaptiveLayout ??
|
||||
AdaptiveLayoutModel(
|
||||
|
|
@ -191,7 +191,7 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
|
|||
platform: currentPlatform,
|
||||
isDesktop: isDesktop,
|
||||
sideBarWidth: 0,
|
||||
controller: controller,
|
||||
controller: scrollControllers,
|
||||
posterDefaults: posterDefaults,
|
||||
);
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
|
|||
inputDevice: input,
|
||||
platform: currentPlatform,
|
||||
isDesktop: isDesktop,
|
||||
controller: controller,
|
||||
controller: scrollControllers,
|
||||
posterDefaults: posterDefaults,
|
||||
),
|
||||
child: Builder(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:fladder/screens/home_screen.dart';
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||
import 'package:fladder/util/poster_defaults.dart';
|
||||
|
||||
|
|
@ -46,7 +47,7 @@ class AdaptiveLayoutModel {
|
|||
final TargetPlatform platform;
|
||||
final bool isDesktop;
|
||||
final PosterDefaults posterDefaults;
|
||||
final ScrollController controller;
|
||||
final Map<HomeTabs, ScrollController> controller;
|
||||
final double sideBarWidth;
|
||||
|
||||
const AdaptiveLayoutModel({
|
||||
|
|
@ -67,7 +68,7 @@ class AdaptiveLayoutModel {
|
|||
TargetPlatform? platform,
|
||||
bool? isDesktop,
|
||||
PosterDefaults? posterDefaults,
|
||||
ScrollController? controller,
|
||||
Map<HomeTabs, ScrollController>? controller,
|
||||
double? sideBarWidth,
|
||||
}) {
|
||||
return AdaptiveLayoutModel(
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class FladderImage extends ConsumerWidget {
|
|||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final useBluredPlaceHolder = ref.watch(clientSettingsProvider.select((value) => value.blurPlaceHolders));
|
||||
final newImage = image;
|
||||
final imageProvider = image?.imageProvider;
|
||||
if (newImage == null) {
|
||||
return placeHolder ?? Container();
|
||||
} else {
|
||||
|
|
@ -44,13 +45,15 @@ class FladderImage extends ConsumerWidget {
|
|||
fit: stackFit,
|
||||
children: [
|
||||
if (!disableBlur && useBluredPlaceHolder && newImage.hash.isNotEmpty || blurOnly)
|
||||
BlurHash(
|
||||
hash: newImage.hash,
|
||||
optimizationMode: BlurHashOptimizationMode.approximation,
|
||||
color: Colors.transparent,
|
||||
imageFit: blurFit ?? fit,
|
||||
Image(
|
||||
image: BlurHashImage(
|
||||
newImage.hash,
|
||||
decodingHeight: 24,
|
||||
decodingWidth: 24,
|
||||
),
|
||||
fit: blurFit ?? fit,
|
||||
),
|
||||
if (!blurOnly)
|
||||
if (!blurOnly && imageProvider != null)
|
||||
FadeInImage(
|
||||
placeholder: MemoryImage(kTransparentImage),
|
||||
fit: fit,
|
||||
|
|
@ -58,7 +61,7 @@ class FladderImage extends ConsumerWidget {
|
|||
excludeFromSemantics: true,
|
||||
alignment: alignment ?? Alignment.center,
|
||||
imageErrorBuilder: imageErrorBuilder,
|
||||
image: newImage.imageProvider,
|
||||
image: imageProvider,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import 'package:fladder/models/book_model.dart';
|
|||
import 'package:fladder/models/item_base_model.dart';
|
||||
import 'package:fladder/models/items/episode_model.dart';
|
||||
import 'package:fladder/models/items/item_shared_models.dart';
|
||||
import 'package:fladder/models/items/photos_model.dart';
|
||||
import 'package:fladder/providers/sync_provider.dart';
|
||||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/screens/collections/add_to_collection.dart';
|
||||
|
|
@ -142,7 +141,7 @@ extension ItemBaseModelExtensions on ItemBaseModel {
|
|||
else if (!exclude.contains(ItemActions.showAlbum) && galleryItem)
|
||||
ItemActionButton(
|
||||
icon: Icon(FladderItemType.photoAlbum.icon),
|
||||
action: () => (this as PhotoModel).navigateToAlbum(context),
|
||||
action: () => parentBaseModel.navigateTo(context),
|
||||
label: Text(context.localized.showAlbum),
|
||||
),
|
||||
if (!exclude.contains(ItemActions.playFromStart))
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ extension PhotoAlbumExtension on PhotoAlbumModel? {
|
|||
return;
|
||||
}
|
||||
|
||||
await context.navigateTo(PhotoViewerRoute(
|
||||
await context.pushRoute(PhotoViewerRoute(
|
||||
items: photos.toList(),
|
||||
));
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import 'package:iconsax_plus/iconsax_plus.dart';
|
|||
import 'package:overflow_view/overflow_view.dart';
|
||||
|
||||
import 'package:fladder/models/collection_types.dart';
|
||||
import 'package:fladder/models/library_filter_model.dart';
|
||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||
import 'package:fladder/providers/views_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.gr.dart';
|
||||
|
|
@ -24,6 +25,10 @@ import 'package:fladder/widgets/shared/custom_tooltip.dart';
|
|||
import 'package:fladder/widgets/shared/item_actions.dart';
|
||||
import 'package:fladder/widgets/shared/modal_bottom_sheet.dart';
|
||||
|
||||
const _fullScreenRoutes = {
|
||||
PhotoViewerRoute.name,
|
||||
};
|
||||
|
||||
class SideNavigationBar extends ConsumerStatefulWidget {
|
||||
final int currentIndex;
|
||||
final List<DestinationModel> destinations;
|
||||
|
|
@ -60,6 +65,8 @@ class _SideNavigationBarState extends ConsumerState<SideNavigationBar> {
|
|||
final shouldExpand = fullyExpanded;
|
||||
final isDesktop = AdaptiveLayout.of(context).isDesktop;
|
||||
|
||||
final fullScreenChildRoute = _fullScreenRoutes.contains(context.router.current.name);
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
AdaptiveLayoutBuilder(
|
||||
|
|
@ -69,231 +76,244 @@ class _SideNavigationBarState extends ConsumerState<SideNavigationBar> {
|
|||
),
|
||||
child: (context) => widget.child,
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.surface.withValues(alpha: shouldExpand ? 0.95 : 0.85),
|
||||
width: shouldExpand ? expandedWidth : collapsedWidth,
|
||||
child: MouseRegion(
|
||||
child: Padding(
|
||||
key: const Key('navigation_rail'),
|
||||
padding: padding.copyWith(right: 0, top: isDesktop ? padding.top : null),
|
||||
child: Column(
|
||||
spacing: 2,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (expandedSideBar) ...[
|
||||
Expanded(child: Text(context.localized.navigation)),
|
||||
],
|
||||
Opacity(
|
||||
opacity: largeBar && expandedSideBar ? 0.65 : 1.0,
|
||||
child: IconButton(
|
||||
onPressed: !largeBar
|
||||
? () => widget.scaffoldKey.currentState?.openDrawer()
|
||||
: () => setState(() => expandedSideBar = !expandedSideBar),
|
||||
icon: Icon(
|
||||
largeBar && expandedSideBar ? IconsaxPlusLinear.sidebar_left : IconsaxPlusLinear.menu,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (largeBar) ...[
|
||||
AnimatedFadeSize(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
child: shouldExpand ? actionButton(context).extended : actionButton(context).normal,
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: !largeBar ? MainAxisAlignment.center : MainAxisAlignment.start,
|
||||
children: [
|
||||
...widget.destinations.mapIndexed(
|
||||
(index, destination) => CustomTooltip(
|
||||
tooltipContent: expandedSideBar
|
||||
? null
|
||||
: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
destination.label,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
position: TooltipPosition.right,
|
||||
child: destination.toNavigationButton(
|
||||
widget.currentIndex == index,
|
||||
true,
|
||||
shouldExpand,
|
||||
),
|
||||
),
|
||||
IgnorePointer(
|
||||
ignoring: fullScreenChildRoute,
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
opacity: !fullScreenChildRoute ? 1 : 0,
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surface.withValues(alpha: shouldExpand ? 0.95 : 0.85),
|
||||
width: shouldExpand ? expandedWidth : collapsedWidth,
|
||||
child: MouseRegion(
|
||||
child: Padding(
|
||||
key: const Key('navigation_rail'),
|
||||
padding: padding.copyWith(right: 0, top: isDesktop ? padding.top : null),
|
||||
child: Column(
|
||||
spacing: 2,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (expandedSideBar) ...[
|
||||
Expanded(child: Text(context.localized.navigation)),
|
||||
],
|
||||
Opacity(
|
||||
opacity: largeBar && expandedSideBar ? 0.65 : 1.0,
|
||||
child: IconButton(
|
||||
onPressed: !largeBar
|
||||
? () => widget.scaffoldKey.currentState?.openDrawer()
|
||||
: () => setState(() => expandedSideBar = !expandedSideBar),
|
||||
icon: Icon(
|
||||
largeBar && expandedSideBar ? IconsaxPlusLinear.sidebar_left : IconsaxPlusLinear.menu,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (views.isNotEmpty && largeBar) ...[
|
||||
const Divider(
|
||||
indent: 32,
|
||||
endIndent: 32,
|
||||
),
|
||||
Flexible(
|
||||
child: OverflowView.flexible(
|
||||
direction: Axis.vertical,
|
||||
spacing: 4,
|
||||
children: views.map(
|
||||
(view) {
|
||||
final selected = context.router.currentUrl.contains(view.id);
|
||||
final actions = [
|
||||
ItemActionButton(
|
||||
label: Text(context.localized.scanLibrary),
|
||||
icon: const Icon(IconsaxPlusLinear.refresh),
|
||||
action: () => showRefreshPopup(context, view.id, view.name),
|
||||
)
|
||||
];
|
||||
return CustomTooltip(
|
||||
tooltipContent: expandedSideBar
|
||||
? null
|
||||
: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
view.name,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (largeBar) ...[
|
||||
AnimatedFadeSize(
|
||||
duration: const Duration(milliseconds: 250),
|
||||
child: shouldExpand ? actionButton(context).extended : actionButton(context).normal,
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: !largeBar ? MainAxisAlignment.center : MainAxisAlignment.start,
|
||||
children: [
|
||||
...widget.destinations.mapIndexed(
|
||||
(index, destination) => CustomTooltip(
|
||||
tooltipContent: expandedSideBar
|
||||
? null
|
||||
: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
destination.label,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
position: TooltipPosition.right,
|
||||
child: view.toNavigationButton(
|
||||
selected,
|
||||
true,
|
||||
shouldExpand,
|
||||
() => context.pushRoute(LibrarySearchRoute(viewModelId: view.id)),
|
||||
onLongPress: () => showBottomSheetPill(
|
||||
context: context,
|
||||
content: (context, scrollController) => ListView(
|
||||
shrinkWrap: true,
|
||||
controller: scrollController,
|
||||
children: actions.listTileItems(context, useIcons: true),
|
||||
),
|
||||
),
|
||||
customIcon: usePostersForLibrary
|
||||
? ClipRRect(
|
||||
borderRadius: FladderTheme.smallShape.borderRadius,
|
||||
child: SizedBox.square(
|
||||
dimension: 50,
|
||||
child: FladderImage(
|
||||
image: view.imageData?.primary,
|
||||
placeHolder: Card(
|
||||
child: Icon(
|
||||
selected
|
||||
? view.collectionType.icon
|
||||
: view.collectionType.iconOutlined,
|
||||
),
|
||||
position: TooltipPosition.right,
|
||||
child: destination.toNavigationButton(
|
||||
widget.currentIndex == index,
|
||||
true,
|
||||
shouldExpand,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (views.isNotEmpty && largeBar) ...[
|
||||
const Divider(
|
||||
indent: 32,
|
||||
endIndent: 32,
|
||||
),
|
||||
Flexible(
|
||||
child: OverflowView.flexible(
|
||||
direction: Axis.vertical,
|
||||
spacing: 4,
|
||||
children: views.map(
|
||||
(view) {
|
||||
final selected = context.router.currentUrl.contains(view.id);
|
||||
final actions = [
|
||||
ItemActionButton(
|
||||
label: Text(context.localized.scanLibrary),
|
||||
icon: const Icon(IconsaxPlusLinear.refresh),
|
||||
action: () => showRefreshPopup(context, view.id, view.name),
|
||||
)
|
||||
];
|
||||
return CustomTooltip(
|
||||
tooltipContent: expandedSideBar
|
||||
? null
|
||||
: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
view.name,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
trailing: actions,
|
||||
),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
builder: (context, remaining) {
|
||||
return CustomTooltip(
|
||||
tooltipContent: expandedSideBar
|
||||
? null
|
||||
: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
context.localized.moreOptions,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
position: TooltipPosition.right,
|
||||
child: view.toNavigationButton(
|
||||
selected,
|
||||
true,
|
||||
shouldExpand,
|
||||
() => context.pushRoute(
|
||||
LibrarySearchRoute(
|
||||
viewModelId: view.id,
|
||||
).withFilter(view.collectionType.defaultFilters),
|
||||
),
|
||||
onLongPress: () => showBottomSheetPill(
|
||||
context: context,
|
||||
content: (context, scrollController) => ListView(
|
||||
shrinkWrap: true,
|
||||
controller: scrollController,
|
||||
children: actions.listTileItems(context, useIcons: true),
|
||||
),
|
||||
),
|
||||
customIcon: usePostersForLibrary
|
||||
? ClipRRect(
|
||||
borderRadius: FladderTheme.smallShape.borderRadius,
|
||||
child: SizedBox.square(
|
||||
dimension: 50,
|
||||
child: FladderImage(
|
||||
image: view.imageData?.primary,
|
||||
placeHolder: Card(
|
||||
child: Icon(
|
||||
selected
|
||||
? view.collectionType.icon
|
||||
: view.collectionType.iconOutlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
trailing: actions,
|
||||
),
|
||||
position: TooltipPosition.right,
|
||||
child: PopupMenuButton(
|
||||
iconColor: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.45),
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: "",
|
||||
icon: NavigationButton(
|
||||
label: context.localized.other,
|
||||
selectedIcon: const Icon(IconsaxPlusLinear.arrow_square_down),
|
||||
icon: const Icon(IconsaxPlusLinear.arrow_square_down),
|
||||
expanded: shouldExpand,
|
||||
customIcon: usePostersForLibrary
|
||||
? ClipRRect(
|
||||
borderRadius: FladderTheme.smallShape.borderRadius,
|
||||
child: const SizedBox.square(
|
||||
dimension: 50,
|
||||
child: Card(
|
||||
child: Icon(IconsaxPlusLinear.arrow_square_down),
|
||||
);
|
||||
},
|
||||
).toList(),
|
||||
builder: (context, remaining) {
|
||||
return CustomTooltip(
|
||||
tooltipContent: expandedSideBar
|
||||
? null
|
||||
: Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
context.localized.moreOptions,
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
horizontal: true,
|
||||
),
|
||||
itemBuilder: (context) => views
|
||||
.sublist(views.length - remaining)
|
||||
.map(
|
||||
(e) => PopupMenuItem(
|
||||
onTap: () => context.pushRoute(LibrarySearchRoute(viewModelId: e.id)),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
usePostersForLibrary
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: ClipRRect(
|
||||
borderRadius: FladderTheme.smallShape.borderRadius,
|
||||
child: SizedBox.square(
|
||||
dimension: 45,
|
||||
child: FladderImage(
|
||||
image: e.imageData?.primary,
|
||||
placeHolder: Card(
|
||||
child: Icon(
|
||||
e.collectionType.iconOutlined,
|
||||
),
|
||||
position: TooltipPosition.right,
|
||||
child: PopupMenuButton(
|
||||
iconColor: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.45),
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: "",
|
||||
icon: NavigationButton(
|
||||
label: context.localized.other,
|
||||
selectedIcon: const Icon(IconsaxPlusLinear.arrow_square_down),
|
||||
icon: const Icon(IconsaxPlusLinear.arrow_square_down),
|
||||
expanded: shouldExpand,
|
||||
customIcon: usePostersForLibrary
|
||||
? ClipRRect(
|
||||
borderRadius: FladderTheme.smallShape.borderRadius,
|
||||
child: const SizedBox.square(
|
||||
dimension: 50,
|
||||
child: Card(
|
||||
child: Icon(IconsaxPlusLinear.arrow_square_down),
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
horizontal: true,
|
||||
),
|
||||
itemBuilder: (context) => views
|
||||
.sublist(views.length - remaining)
|
||||
.map(
|
||||
(e) => PopupMenuItem(
|
||||
onTap: () => context.pushRoute(LibrarySearchRoute(
|
||||
viewModelId: e.id,
|
||||
).withFilter(e.collectionType.defaultFilters)),
|
||||
child: Row(
|
||||
spacing: 8,
|
||||
children: [
|
||||
usePostersForLibrary
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4),
|
||||
child: ClipRRect(
|
||||
borderRadius: FladderTheme.smallShape.borderRadius,
|
||||
child: SizedBox.square(
|
||||
dimension: 45,
|
||||
child: FladderImage(
|
||||
image: e.imageData?.primary,
|
||||
placeHolder: Card(
|
||||
child: Icon(
|
||||
e.collectionType.iconOutlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: Icon(e.collectionType.iconOutlined),
|
||||
Text(e.name),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
)
|
||||
: Icon(e.collectionType.iconOutlined),
|
||||
Text(e.name),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
NavigationButton(
|
||||
label: context.localized.settings,
|
||||
selected: widget.currentLocation.contains(const SettingsRoute().routeName),
|
||||
selectedIcon: const Icon(IconsaxPlusBold.setting_3),
|
||||
horizontal: true,
|
||||
expanded: shouldExpand,
|
||||
icon: const SettingsUserIcon(),
|
||||
onPressed: () {
|
||||
if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.single) {
|
||||
context.router.push(const SettingsRoute());
|
||||
} else {
|
||||
context.router.push(const ClientSettingsRoute());
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
NavigationButton(
|
||||
label: context.localized.settings,
|
||||
selected: widget.currentLocation.contains(const SettingsRoute().routeName),
|
||||
selectedIcon: const Icon(IconsaxPlusBold.setting_3),
|
||||
horizontal: true,
|
||||
expanded: shouldExpand,
|
||||
icon: const SettingsUserIcon(),
|
||||
onPressed: () {
|
||||
if (AdaptiveLayout.layoutModeOf(context) == LayoutMode.single) {
|
||||
context.router.push(const SettingsRoute());
|
||||
} else {
|
||||
context.router.push(const ClientSettingsRoute());
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import 'package:fladder/providers/connectivity_provider.dart';
|
|||
import 'package:fladder/providers/video_player_provider.dart';
|
||||
import 'package:fladder/providers/views_provider.dart';
|
||||
import 'package:fladder/routes/auto_router.dart';
|
||||
import 'package:fladder/screens/home_screen.dart';
|
||||
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
||||
import 'package:fladder/screens/shared/nested_bottom_appbar.dart';
|
||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||
|
|
@ -77,6 +78,9 @@ class _NavigationScaffoldState extends ConsumerState<NavigationScaffold> {
|
|||
final calculatedBottomViewPadding =
|
||||
showPlayerBar ? floatingPlayerHeight(context) + bottomViewPadding : bottomViewPadding;
|
||||
|
||||
final currentTab =
|
||||
HomeTabs.values.elementAtOrNull(currentIndex.clamp(0, HomeTabs.values.length - 1)) ?? HomeTabs.dashboard;
|
||||
|
||||
return PopScope(
|
||||
canPop: currentIndex == 0,
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
|
|
@ -124,7 +128,7 @@ class _NavigationScaffoldState extends ConsumerState<NavigationScaffold> {
|
|||
hiddenHeight: calculatedBottomViewPadding,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
child: HideOnScroll(
|
||||
controller: AdaptiveLayout.scrollOf(context),
|
||||
controller: AdaptiveLayout.scrollOf(context, currentTab),
|
||||
forceHide: !homeRoutes.any((element) => element.name.contains(currentLocation)),
|
||||
child: NestedBottomAppBar(
|
||||
child: SizedBox(
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class HideOnScroll extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _HideOnScrollState extends ConsumerState<HideOnScroll> {
|
||||
late final ScrollController scrollController = widget.controller ?? ScrollController();
|
||||
late ScrollController scrollController = widget.controller ?? ScrollController();
|
||||
bool isVisible = true;
|
||||
|
||||
@override
|
||||
|
|
@ -47,6 +47,16 @@ class _HideOnScrollState extends ConsumerState<HideOnScroll> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant HideOnScroll oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.controller != widget.controller) {
|
||||
scrollController.removeListener(_onScroll);
|
||||
scrollController = widget.controller ?? ScrollController();
|
||||
scrollController.addListener(_onScroll);
|
||||
}
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (!widget.canHide) {
|
||||
if (!isVisible) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
|
@ -19,6 +21,7 @@ class HorizontalList<T> extends ConsumerStatefulWidget {
|
|||
final Widget Function(BuildContext context, int index) itemBuilder;
|
||||
final bool scrollToEnd;
|
||||
final EdgeInsets contentPadding;
|
||||
final double? dominantRatio;
|
||||
final double? height;
|
||||
final bool shrinkWrap;
|
||||
const HorizontalList({
|
||||
|
|
@ -33,6 +36,7 @@ class HorizontalList<T> extends ConsumerStatefulWidget {
|
|||
this.contentPadding = const EdgeInsets.symmetric(horizontal: 16),
|
||||
this.subtext,
|
||||
this.shrinkWrap = false,
|
||||
this.dominantRatio,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -201,9 +205,11 @@ class _HorizontalListState extends ConsumerState<HorizontalList> {
|
|||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
height: (widget.height ??
|
||||
AdaptiveLayout.poster(context).size *
|
||||
ref.watch(clientSettingsProvider.select((value) => value.posterSize))),
|
||||
height: widget.height ??
|
||||
((AdaptiveLayout.poster(context).size *
|
||||
ref.watch(clientSettingsProvider.select((value) => value.posterSize))) /
|
||||
pow((widget.dominantRatio ?? 1.0), 0.55)) *
|
||||
0.72,
|
||||
child: ListView.separated(
|
||||
controller: _scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ class _ProgressFloatingButtonState extends ConsumerState<ProgressFloatingButton>
|
|||
}
|
||||
: null,
|
||||
child: FloatingActionButton(
|
||||
heroTag: null,
|
||||
heroTag: "Progress_Floating_Button",
|
||||
onPressed: isActive ? timer.cancel : timer.play,
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
|
|
|
|||
|
|
@ -14,12 +14,16 @@ class SelectableIconButton extends ConsumerStatefulWidget {
|
|||
final IconData icon;
|
||||
final IconData? selectedIcon;
|
||||
final bool selected;
|
||||
final Color? backgroundColor;
|
||||
final Color? iconColor;
|
||||
const SelectableIconButton({
|
||||
required this.onPressed,
|
||||
required this.selected,
|
||||
required this.icon,
|
||||
this.selectedIcon,
|
||||
this.label,
|
||||
this.backgroundColor,
|
||||
this.iconColor,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
|
@ -37,9 +41,14 @@ class _SelectableIconButtonState extends ConsumerState<SelectableIconButton> {
|
|||
message: widget.label ?? "",
|
||||
child: ElevatedButton(
|
||||
style: ButtonStyle(
|
||||
backgroundColor: widget.selected ? WidgetStatePropertyAll(Theme.of(context).colorScheme.primary) : null,
|
||||
iconColor: widget.selected ? WidgetStatePropertyAll(Theme.of(context).colorScheme.onPrimary) : null,
|
||||
foregroundColor: widget.selected ? WidgetStatePropertyAll(Theme.of(context).colorScheme.onPrimary) : null,
|
||||
elevation: WidgetStatePropertyAll(
|
||||
widget.backgroundColor != null ? (widget.backgroundColor!.a < 1 ? 0 : null) : null),
|
||||
backgroundColor: WidgetStatePropertyAll(
|
||||
widget.backgroundColor ?? (widget.selected ? Theme.of(context).colorScheme.primary : null)),
|
||||
iconColor: WidgetStatePropertyAll(
|
||||
widget.iconColor ?? (widget.selected ? Theme.of(context).colorScheme.onPrimary : null)),
|
||||
foregroundColor: WidgetStatePropertyAll(
|
||||
widget.iconColor ?? (widget.selected ? Theme.of(context).colorScheme.onPrimary : null)),
|
||||
padding: const WidgetStatePropertyAll(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: loading
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue