You've already forked immich
mirror of
https://github.com/immich-app/immich.git
synced 2025-08-08 23:07:06 +02:00
feat: new timeline multi-selection (#19443)
* feat: new timeline multiselection * select all from bucket * wip * group multi-select * group multi-select * pr feedback * pr feedback * lint
This commit is contained in:
144
mobile/lib/providers/timeline/multiselect.provider.dart
Normal file
144
mobile/lib/providers/timeline/multiselect.provider.dart
Normal file
@ -0,0 +1,144 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
|
||||
final multiSelectProvider =
|
||||
NotifierProvider<MultiSelectNotifier, MultiSelectState>(
|
||||
MultiSelectNotifier.new,
|
||||
dependencies: [timelineServiceProvider],
|
||||
);
|
||||
|
||||
class MultiSelectState {
|
||||
final Set<BaseAsset> selectedAssets;
|
||||
|
||||
MultiSelectState({
|
||||
required this.selectedAssets,
|
||||
});
|
||||
|
||||
bool get isEnabled => selectedAssets.isNotEmpty;
|
||||
|
||||
MultiSelectState copyWith({
|
||||
Set<BaseAsset>? selectedAssets,
|
||||
}) {
|
||||
return MultiSelectState(
|
||||
selectedAssets: selectedAssets ?? this.selectedAssets,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'MultiSelectState(selectedAssets: $selectedAssets)';
|
||||
|
||||
@override
|
||||
bool operator ==(covariant MultiSelectState other) {
|
||||
if (identical(this, other)) return true;
|
||||
final listEquals = const DeepCollectionEquality().equals;
|
||||
|
||||
return listEquals(other.selectedAssets, selectedAssets);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => selectedAssets.hashCode;
|
||||
}
|
||||
|
||||
class MultiSelectNotifier extends Notifier<MultiSelectState> {
|
||||
late final TimelineService _timelineService;
|
||||
|
||||
@override
|
||||
MultiSelectState build() {
|
||||
_timelineService = ref.read(timelineServiceProvider);
|
||||
|
||||
return MultiSelectState(
|
||||
selectedAssets: {},
|
||||
);
|
||||
}
|
||||
|
||||
void selectAsset(BaseAsset asset) {
|
||||
if (state.selectedAssets.contains(asset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = state.copyWith(
|
||||
selectedAssets: {...state.selectedAssets, asset},
|
||||
);
|
||||
}
|
||||
|
||||
void deselectAsset(BaseAsset asset) {
|
||||
if (!state.selectedAssets.contains(asset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
state = state.copyWith(
|
||||
selectedAssets: state.selectedAssets.where((a) => a != asset).toSet(),
|
||||
);
|
||||
}
|
||||
|
||||
void toggleAssetSelection(BaseAsset asset) {
|
||||
if (state.selectedAssets.contains(asset)) {
|
||||
deselectAsset(asset);
|
||||
} else {
|
||||
selectAsset(asset);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bucket bulk operations
|
||||
void selectBucket(int offset, int bucketCount) async {
|
||||
final assets = await _timelineService.loadAssets(offset, bucketCount);
|
||||
final selectedAssets = state.selectedAssets.toSet();
|
||||
|
||||
selectedAssets.addAll(assets);
|
||||
|
||||
state = state.copyWith(
|
||||
selectedAssets: selectedAssets,
|
||||
);
|
||||
}
|
||||
|
||||
void deselectBucket(int offset, int bucketCount) async {
|
||||
final assets = await _timelineService.loadAssets(offset, bucketCount);
|
||||
final selectedAssets = state.selectedAssets.toSet();
|
||||
|
||||
selectedAssets.removeAll(assets);
|
||||
|
||||
state = state.copyWith(selectedAssets: selectedAssets);
|
||||
}
|
||||
|
||||
void toggleBucketSelection(int offset, int bucketCount) async {
|
||||
final assets = await _timelineService.loadAssets(offset, bucketCount);
|
||||
toggleBucketSelectionByAssets(assets);
|
||||
}
|
||||
|
||||
void toggleBucketSelectionByAssets(List<BaseAsset> bucketAssets) {
|
||||
if (bucketAssets.isEmpty) return;
|
||||
|
||||
// Check if all assets in this bucket are currently selected
|
||||
final allSelected =
|
||||
bucketAssets.every((asset) => state.selectedAssets.contains(asset));
|
||||
|
||||
final selectedAssets = state.selectedAssets.toSet();
|
||||
|
||||
if (allSelected) {
|
||||
// If all assets in this bucket are selected, deselect them
|
||||
selectedAssets.removeAll(bucketAssets);
|
||||
} else {
|
||||
// If not all assets in this bucket are selected, select them all
|
||||
selectedAssets.addAll(bucketAssets);
|
||||
}
|
||||
|
||||
state = state.copyWith(selectedAssets: selectedAssets);
|
||||
}
|
||||
}
|
||||
|
||||
final bucketSelectionProvider = Provider.family<bool, List<BaseAsset>>(
|
||||
(ref, bucketAssets) {
|
||||
final selectedAssets =
|
||||
ref.watch(multiSelectProvider.select((s) => s.selectedAssets));
|
||||
|
||||
if (bucketAssets.isEmpty) return false;
|
||||
|
||||
// Check if all assets in the bucket are selected
|
||||
return bucketAssets.every((asset) => selectedAssets.contains(asset));
|
||||
},
|
||||
dependencies: [multiSelectProvider],
|
||||
);
|
Reference in New Issue
Block a user