chore: Lots of bug fixes and navigation improvements

This commit is contained in:
PartyDonut 2025-09-01 20:21:36 +02:00
parent 9bb5e81812
commit 92d5391b93
35 changed files with 513 additions and 455 deletions

View file

@ -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 ||

View file

@ -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) {

View file

@ -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) {

View file

@ -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

View file

@ -6,7 +6,7 @@ part of 'library_screen_provider.dart';
// RiverpodGenerator
// **************************************************************************
String _$libraryScreenHash() => r'ff8b8514461c3e5da1aaf0933d6d49b014c3c05c';
String _$libraryScreenHash() => r'792c4e47e5cd03635f42a4da4e24698c7584bbdb';
/// See also [LibraryScreen].
@ProviderFor(LibraryScreen)

View file

@ -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,
));

View file

@ -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,

View file

@ -7,7 +7,7 @@ part of 'background_download_provider.dart';
// **************************************************************************
String _$backgroundDownloaderHash() =>
r'9d866549ed7632e855ba30de2765368960889cff';
r'2571c078d018150bf93c6f35735f58f16cbca3dd';
/// See also [BackgroundDownloader].
@ProviderFor(BackgroundDownloader)

View file

@ -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 {

View file

@ -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;
}

View file

@ -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,

View file

@ -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,
));

View file

@ -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(

View file

@ -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),
);
}

View file

@ -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(),

View file

@ -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 ?? {},

View file

@ -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();

View file

@ -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(
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)),

View file

@ -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)));

View file

@ -90,7 +90,13 @@ class _PhotoViewerScreenState extends ConsumerState<PhotoViewerScreen> with Widg
if (context.mounted) {
setState(() {
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);

View file

@ -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(

View file

@ -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;

View file

@ -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];

View file

@ -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(

View file

@ -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(

View file

@ -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(

View file

@ -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,
),
if (!blurOnly)
fit: blurFit ?? fit,
),
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,
)
],
);

View file

@ -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))

View file

@ -171,7 +171,7 @@ extension PhotoAlbumExtension on PhotoAlbumModel? {
return;
}
await context.navigateTo(PhotoViewerRoute(
await context.pushRoute(PhotoViewerRoute(
items: photos.toList(),
));

View file

@ -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,7 +76,12 @@ class _SideNavigationBarState extends ConsumerState<SideNavigationBar> {
),
child: (context) => widget.child,
),
Container(
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(
@ -168,7 +180,11 @@ class _SideNavigationBarState extends ConsumerState<SideNavigationBar> {
selected,
true,
shouldExpand,
() => context.pushRoute(LibrarySearchRoute(viewModelId: view.id)),
() => context.pushRoute(
LibrarySearchRoute(
viewModelId: view.id,
).withFilter(view.collectionType.defaultFilters),
),
onLongPress: () => showBottomSheetPill(
context: context,
content: (context, scrollController) => ListView(
@ -240,7 +256,9 @@ class _SideNavigationBarState extends ConsumerState<SideNavigationBar> {
.sublist(views.length - remaining)
.map(
(e) => PopupMenuItem(
onTap: () => context.pushRoute(LibrarySearchRoute(viewModelId: e.id)),
onTap: () => context.pushRoute(LibrarySearchRoute(
viewModelId: e.id,
).withFilter(e.collectionType.defaultFilters)),
child: Row(
spacing: 8,
children: [
@ -298,6 +316,8 @@ class _SideNavigationBarState extends ConsumerState<SideNavigationBar> {
),
),
),
),
),
],
);
}

View file

@ -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(

View file

@ -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) {

View file

@ -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,

View file

@ -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,

View file

@ -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