diff --git a/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart b/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart index e09d14c4e8..5c93d37696 100644 --- a/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart +++ b/mobile/lib/presentation/widgets/bottom_app_bar/home_bottom_app_bar.widget.dart @@ -31,8 +31,9 @@ class HomeBottomAppBar extends ConsumerWidget { return BaseBottomSheet( initialChildSize: 0.25, minChildSize: 0.22, + expand: false, actions: [ - const ShareActionButton(), + if (multiselect.isEnabled) const ShareActionButton(), if (multiselect.hasRemote) ...[ const ShareLinkActionButton(source: ActionSource.timeline), const ArchiveActionButton(source: ActionSource.timeline), diff --git a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart index fd0806cff0..59c4c46135 100644 --- a/mobile/lib/presentation/widgets/timeline/timeline.widget.dart +++ b/mobile/lib/presentation/widgets/timeline/timeline.widget.dart @@ -43,15 +43,26 @@ class Timeline extends StatelessWidget { } } -class _SliverTimeline extends StatefulWidget { +class _SliverTimeline extends ConsumerStatefulWidget { const _SliverTimeline(); @override - State createState() => _SliverTimelineState(); + ConsumerState createState() => _SliverTimelineState(); } -class _SliverTimelineState extends State<_SliverTimeline> { +class _SliverTimelineState extends ConsumerState<_SliverTimeline> { final _scrollController = ScrollController(); + PersistentBottomSheetController? _bottomSheetController; + bool _isMultiSelectEnabled = false; + + @override + void initState() { + super.initState(); + ref.listenManual( + multiSelectProvider.select((s) => s.isEnabled), + _onMultiSelectChanged, + ); + } @override void dispose() { @@ -59,80 +70,93 @@ class _SliverTimelineState extends State<_SliverTimeline> { super.dispose(); } + void _onMultiSelectChanged(bool? previous, bool current) { + if (context.mounted && current != _isMultiSelectEnabled) { + setState(() { + _isMultiSelectEnabled = current; + if (current) { + _bottomSheetController = showBottomSheet( + context: context, + builder: (_) => const HomeBottomAppBar(), + ); + _bottomSheetController?.closed.then((_) { + _bottomSheetController = null; + // Reset the multi-select state when the bottom sheet is closed + ref.read(multiSelectProvider.notifier).reset(); + }); + } else { + _bottomSheetController?.close(); + } + }); + } + } + @override Widget build(BuildContext _) { - return Consumer( - builder: (context, ref, child) { - final asyncSegments = ref.watch(timelineSegmentProvider); - final maxHeight = - ref.watch(timelineArgsProvider.select((args) => args.maxHeight)); - final isMultiSelectEnabled = - ref.watch(multiSelectProvider.select((s) => s.isEnabled)); - return asyncSegments.widgetWhen( - onData: (segments) { - final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1; - final statusBarHeight = context.padding.top; - final totalAppBarHeight = statusBarHeight + kToolbarHeight; - const scrubberBottomPadding = 100.0; + final asyncSegments = ref.watch(timelineSegmentProvider); + final maxHeight = + ref.watch(timelineArgsProvider.select((args) => args.maxHeight)); - return PrimaryScrollController( - controller: _scrollController, - child: Stack( - children: [ - Scrubber( - layoutSegments: segments, - timelineHeight: maxHeight, - topPadding: totalAppBarHeight + 10, - bottomPadding: - context.padding.bottom + scrubberBottomPadding, - child: CustomScrollView( - primary: true, - cacheExtent: maxHeight * 2, - slivers: [ - SliverAnimatedOpacity( - duration: Durations.medium1, - opacity: isMultiSelectEnabled ? 0 : 1, - sliver: const ImmichSliverAppBar( - floating: true, - pinned: false, - snap: false, - ), - ), - _SliverSegmentedList( - segments: segments, - delegate: SliverChildBuilderDelegate( - (ctx, index) { - if (index >= childCount) return null; - final segment = segments.findByIndex(index); - return segment?.builder(ctx, index) ?? - const SizedBox.shrink(); - }, - childCount: childCount, - addAutomaticKeepAlives: false, - // We add repaint boundary around tiles, so skip the auto boundaries - addRepaintBoundaries: false, - ), - ), - const SliverPadding( - padding: EdgeInsets.only( - bottom: scrubberBottomPadding, - ), - ), - ], + return asyncSegments.widgetWhen( + onData: (segments) { + final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1; + final statusBarHeight = context.padding.top; + final totalAppBarHeight = statusBarHeight + kToolbarHeight; + const scrubberBottomPadding = 100.0; + + return PrimaryScrollController( + controller: _scrollController, + child: Stack( + children: [ + Scrubber( + layoutSegments: segments, + timelineHeight: maxHeight, + topPadding: totalAppBarHeight + 10, + bottomPadding: context.padding.bottom + scrubberBottomPadding, + child: CustomScrollView( + primary: true, + cacheExtent: maxHeight * 2, + slivers: [ + SliverAnimatedOpacity( + duration: Durations.medium1, + opacity: _isMultiSelectEnabled ? 0 : 1, + sliver: const ImmichSliverAppBar( + floating: true, + pinned: false, + snap: false, + ), ), - ), - if (isMultiSelectEnabled) ...[ - const Positioned( - top: 60, - left: 25, - child: _MultiSelectStatusButton(), + _SliverSegmentedList( + segments: segments, + delegate: SliverChildBuilderDelegate( + (ctx, index) { + if (index >= childCount) return null; + final segment = segments.findByIndex(index); + return segment?.builder(ctx, index) ?? + const SizedBox.shrink(); + }, + childCount: childCount, + addAutomaticKeepAlives: false, + // We add repaint boundary around tiles, so skip the auto boundaries + addRepaintBoundaries: false, + ), + ), + const SliverPadding( + padding: EdgeInsets.only( + bottom: scrubberBottomPadding, + ), ), - const HomeBottomAppBar(), ], - ], + ), ), - ); - }, + if (_isMultiSelectEnabled) + const Positioned( + top: 60, + left: 25, + child: _MultiSelectStatusButton(), + ), + ], + ), ); }, );