You've already forked immich
mirror of
https://github.com/immich-app/immich.git
synced 2025-08-07 23:03:36 +02:00
feat: expanded sliver app bar (#19827)
* use mutex
* feat: cool app bar
* animation
* adapt to more pages
* animation
* better animation
* fix: asset count
* Revert "fix: asset count"
This reverts commit 673a5b264b
.
* fix: asset count
* fix: shaky animation on Android
* tunning
* offset SizedBox to fix scroll jump on multiselect
---------
Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
@ -1,9 +1,11 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftArchivePage extends StatelessWidget {
|
||||
@ -27,7 +29,12 @@ class DriftArchivePage extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const Timeline(),
|
||||
child: Timeline(
|
||||
appBar: MesmerizingSliverAppBar(
|
||||
title: 'archive'.t(context: context),
|
||||
icon: Icons.archive_outlined,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
|
||||
|
||||
@RoutePage()
|
||||
class DriftFavoritePage extends StatelessWidget {
|
||||
@ -27,7 +29,12 @@ class DriftFavoritePage extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const Timeline(),
|
||||
child: Timeline(
|
||||
appBar: MesmerizingSliverAppBar(
|
||||
title: 'favorites'.t(context: context),
|
||||
icon: Icons.favorite_outline,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ class _AlbumList extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
onTap: () =>
|
||||
context.pushRoute(LocalTimelineRoute(albumId: album.id)),
|
||||
context.pushRoute(LocalTimelineRoute(album: album)),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
@ -27,7 +28,16 @@ class DriftTrashPage extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const Timeline(),
|
||||
child: Timeline(
|
||||
appBar: SliverAppBar(
|
||||
title: Text('trash'.t(context: context)),
|
||||
floating: true,
|
||||
snap: true,
|
||||
pinned: true,
|
||||
centerTitle: true,
|
||||
elevation: 0,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
|
||||
|
||||
@RoutePage()
|
||||
class LocalTimelinePage extends StatelessWidget {
|
||||
final String albumId;
|
||||
final LocalAlbum album;
|
||||
|
||||
const LocalTimelinePage({super.key, required this.albumId});
|
||||
const LocalTimelinePage({super.key, required this.album});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -16,14 +18,17 @@ class LocalTimelinePage extends StatelessWidget {
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith(
|
||||
(ref) {
|
||||
final timelineService =
|
||||
ref.watch(timelineFactoryProvider).localAlbum(albumId: albumId);
|
||||
final timelineService = ref
|
||||
.watch(timelineFactoryProvider)
|
||||
.localAlbum(albumId: album.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const Timeline(),
|
||||
child: Timeline(
|
||||
appBar: MesmerizingSliverAppBar(title: album.name),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ class LocalMediaSummaryPage extends StatelessWidget {
|
||||
name: album.name,
|
||||
countFuture: countFuture,
|
||||
onTap: () => context.router.push(
|
||||
LocalTimelineRoute(albumId: album.id),
|
||||
LocalTimelineRoute(album: album),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -226,7 +226,7 @@ class RemoteMediaSummaryPage extends StatelessWidget {
|
||||
name: album.name,
|
||||
countFuture: countFuture,
|
||||
onTap: () => context.router.push(
|
||||
RemoteTimelineRoute(albumId: album.id),
|
||||
RemoteTimelineRoute(album: album),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -1,14 +1,16 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
|
||||
|
||||
@RoutePage()
|
||||
class RemoteTimelinePage extends StatelessWidget {
|
||||
final String albumId;
|
||||
final RemoteAlbum album;
|
||||
|
||||
const RemoteTimelinePage({super.key, required this.albumId});
|
||||
const RemoteTimelinePage({super.key, required this.album});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -18,13 +20,18 @@ class RemoteTimelinePage extends StatelessWidget {
|
||||
(ref) {
|
||||
final timelineService = ref
|
||||
.watch(timelineFactoryProvider)
|
||||
.remoteAlbum(albumId: albumId);
|
||||
.remoteAlbum(albumId: album.id);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
},
|
||||
),
|
||||
],
|
||||
child: const Timeline(),
|
||||
child: Timeline(
|
||||
appBar: MesmerizingSliverAppBar(
|
||||
title: album.name,
|
||||
icon: Icons.photo_album_outlined,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -475,7 +475,7 @@ class _AlbumList extends StatelessWidget {
|
||||
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
final List<Album> albums;
|
||||
final List<RemoteAlbum> albums;
|
||||
final String? userId;
|
||||
|
||||
@override
|
||||
@ -555,7 +555,7 @@ class _AlbumList extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
onTap: () => context.router.push(
|
||||
RemoteTimelineRoute(albumId: album.id),
|
||||
RemoteTimelineRoute(album: album),
|
||||
),
|
||||
leadingPadding: const EdgeInsets.only(
|
||||
right: 16,
|
||||
@ -573,13 +573,24 @@ class _AlbumList extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox(
|
||||
: SizedBox(
|
||||
width: 80,
|
||||
height: 80,
|
||||
child: Icon(
|
||||
Icons.photo_album_rounded,
|
||||
size: 40,
|
||||
color: Colors.grey,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surfaceContainer,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(16)),
|
||||
border: Border.all(
|
||||
color: context.colorScheme.outline.withAlpha(50),
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.photo_album_rounded,
|
||||
size: 24,
|
||||
color: Colors.grey,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -599,7 +610,7 @@ class _AlbumGrid extends StatelessWidget {
|
||||
required this.error,
|
||||
});
|
||||
|
||||
final List<Album> albums;
|
||||
final List<RemoteAlbum> albums;
|
||||
final String? userId;
|
||||
final bool isLoading;
|
||||
final String? error;
|
||||
@ -674,14 +685,14 @@ class _GridAlbumCard extends StatelessWidget {
|
||||
required this.userId,
|
||||
});
|
||||
|
||||
final Album album;
|
||||
final RemoteAlbum album;
|
||||
final String? userId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => context.router.push(
|
||||
RemoteTimelineRoute(albumId: album.id),
|
||||
RemoteTimelineRoute(album: album),
|
||||
),
|
||||
child: Card(
|
||||
elevation: 0,
|
||||
|
@ -27,7 +27,12 @@ class DriftLibraryPage extends ConsumerWidget {
|
||||
return const Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
ImmichSliverAppBar(),
|
||||
ImmichSliverAppBar(
|
||||
snap: false,
|
||||
floating: false,
|
||||
pinned: true,
|
||||
showUploadButton: false,
|
||||
),
|
||||
_ActionButtonGrid(),
|
||||
_CollectionCards(),
|
||||
_QuickAccessButtonList(),
|
||||
|
@ -335,7 +335,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
||||
final isDraggingDown = currentExtent < previousExtent;
|
||||
previousExtent = currentExtent;
|
||||
// Closes the bottom sheet if the user is dragging down
|
||||
if (isDraggingDown && delta.extent < 0.5) {
|
||||
if (isDraggingDown && delta.extent < 0.55) {
|
||||
if (dragInProgress) {
|
||||
blockGestures = true;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
|
||||
import 'package:immich_mobile/widgets/common/mesmerizing_sliver_app_bar.dart';
|
||||
import 'package:immich_mobile/widgets/common/selection_sliver_app_bar.dart';
|
||||
|
||||
class Timeline extends StatelessWidget {
|
||||
@ -25,10 +26,12 @@ class Timeline extends StatelessWidget {
|
||||
super.key,
|
||||
this.topSliverWidget,
|
||||
this.topSliverWidgetHeight,
|
||||
this.appBar,
|
||||
});
|
||||
|
||||
final Widget? topSliverWidget;
|
||||
final double? topSliverWidgetHeight;
|
||||
final Widget? appBar;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -49,6 +52,7 @@ class Timeline extends StatelessWidget {
|
||||
child: _SliverTimeline(
|
||||
topSliverWidget: topSliverWidget,
|
||||
topSliverWidgetHeight: topSliverWidgetHeight,
|
||||
appBar: appBar,
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -60,10 +64,12 @@ class _SliverTimeline extends ConsumerStatefulWidget {
|
||||
const _SliverTimeline({
|
||||
this.topSliverWidget,
|
||||
this.topSliverWidgetHeight,
|
||||
this.appBar,
|
||||
});
|
||||
|
||||
final Widget? topSliverWidget;
|
||||
final double? topSliverWidgetHeight;
|
||||
final Widget? appBar;
|
||||
|
||||
@override
|
||||
ConsumerState createState() => _SliverTimelineState();
|
||||
@ -100,6 +106,10 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
onData: (segments) {
|
||||
final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1;
|
||||
final statusBarHeight = context.padding.top;
|
||||
final double appBarExpandedHeight =
|
||||
widget.appBar != null && widget.appBar is MesmerizingSliverAppBar
|
||||
? 200
|
||||
: 0;
|
||||
final totalAppBarHeight = statusBarHeight + kToolbarHeight;
|
||||
const scrubberBottomPadding = 100.0;
|
||||
|
||||
@ -112,7 +122,8 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
timelineHeight: maxHeight,
|
||||
topPadding: totalAppBarHeight + 10,
|
||||
bottomPadding: context.padding.bottom + scrubberBottomPadding,
|
||||
monthSegmentSnappingOffset: widget.topSliverWidgetHeight,
|
||||
monthSegmentSnappingOffset:
|
||||
widget.topSliverWidgetHeight ?? 0 + appBarExpandedHeight,
|
||||
child: CustomScrollView(
|
||||
primary: true,
|
||||
cacheExtent: maxHeight * 2,
|
||||
@ -120,11 +131,12 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
if (isSelectionMode)
|
||||
const SelectionSliverAppBar()
|
||||
else
|
||||
const ImmichSliverAppBar(
|
||||
floating: true,
|
||||
pinned: false,
|
||||
snap: false,
|
||||
),
|
||||
widget.appBar ??
|
||||
const ImmichSliverAppBar(
|
||||
floating: true,
|
||||
pinned: false,
|
||||
snap: false,
|
||||
),
|
||||
if (widget.topSliverWidget != null) widget.topSliverWidget!,
|
||||
_SliverSegmentedList(
|
||||
segments: segments,
|
||||
|
Reference in New Issue
Block a user