1
0
mirror of https://github.com/immich-app/immich.git synced 2025-08-08 23:07:06 +02:00

feat: selection mode timeline (#19734)

* feat: new page

* force multi-selection state

* fix: provider scoping

* Return selected assets

* lint

* lint

* simplify provider scope and drop drilling

* selection styling
This commit is contained in:
Alex
2025-07-07 23:41:37 -05:00
committed by GitHub
parent dd94ad17aa
commit 87dd09d103
12 changed files with 471 additions and 85 deletions

View File

@ -18,9 +18,14 @@ 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/selection_sliver_app_bar.dart';
class Timeline extends StatelessWidget {
const Timeline({super.key, this.topSliverWidget, this.topSliverWidgetHeight});
const Timeline({
super.key,
this.topSliverWidget,
this.topSliverWidgetHeight,
});
final Widget? topSliverWidget;
final double? topSliverWidgetHeight;
@ -52,7 +57,10 @@ class Timeline extends StatelessWidget {
}
class _SliverTimeline extends ConsumerStatefulWidget {
const _SliverTimeline({this.topSliverWidget, this.topSliverWidgetHeight});
const _SliverTimeline({
this.topSliverWidget,
this.topSliverWidgetHeight,
});
final Widget? topSliverWidget;
final double? topSliverWidgetHeight;
@ -84,6 +92,10 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
final asyncSegments = ref.watch(timelineSegmentProvider);
final maxHeight =
ref.watch(timelineArgsProvider.select((args) => args.maxHeight));
final isSelectionMode = ref.watch(
multiSelectProvider.select((s) => s.forceEnable),
);
return asyncSegments.widgetWhen(
onData: (segments) {
final childCount = (segments.lastOrNull?.lastIndex ?? -1) + 1;
@ -105,11 +117,14 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
primary: true,
cacheExtent: maxHeight * 2,
slivers: [
const ImmichSliverAppBar(
floating: true,
pinned: false,
snap: false,
),
if (isSelectionMode)
const SelectionSliverAppBar()
else
const ImmichSliverAppBar(
floating: true,
pinned: false,
snap: false,
),
if (widget.topSliverWidget != null) widget.topSliverWidget!,
_SliverSegmentedList(
segments: segments,
@ -134,40 +149,42 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
],
),
),
Consumer(
builder: (_, consumerRef, child) {
final isMultiSelectEnabled = consumerRef.watch(
multiSelectProvider.select(
(s) => s.isEnabled,
),
);
if (!isSelectionMode) ...[
Consumer(
builder: (_, consumerRef, child) {
final isMultiSelectEnabled = consumerRef.watch(
multiSelectProvider.select(
(s) => s.isEnabled,
),
);
if (isMultiSelectEnabled) {
return child!;
}
return const SizedBox.shrink();
},
child: const Positioned(
top: 60,
left: 25,
child: _MultiSelectStatusButton(),
if (isMultiSelectEnabled) {
return child!;
}
return const SizedBox.shrink();
},
child: const Positioned(
top: 60,
left: 25,
child: _MultiSelectStatusButton(),
),
),
),
Consumer(
builder: (_, consumerRef, child) {
final isMultiSelectEnabled = consumerRef.watch(
multiSelectProvider.select(
(s) => s.isEnabled,
),
);
Consumer(
builder: (_, consumerRef, child) {
final isMultiSelectEnabled = consumerRef.watch(
multiSelectProvider.select(
(s) => s.isEnabled,
),
);
if (isMultiSelectEnabled) {
return child!;
}
return const SizedBox.shrink();
},
child: const HomeBottomAppBar(),
),
if (isMultiSelectEnabled) {
return child!;
}
return const SizedBox.shrink();
},
child: const HomeBottomAppBar(),
),
],
],
),
);