Compare commits

...

1 Commits

Author SHA1 Message Date
shenlong-tanwen
19c9f932ea fix: handle missing assets gracefully 2025-07-23 19:40:23 +05:30
4 changed files with 23 additions and 17 deletions

View File

@@ -10,6 +10,7 @@ import 'package:immich_mobile/domain/services/setting.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart'; import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
import 'package:immich_mobile/utils/async_mutex.dart'; import 'package:immich_mobile/utils/async_mutex.dart';
import 'package:logging/logging.dart';
typedef TimelineAssetSource = Future<List<BaseAsset>> Function( typedef TimelineAssetSource = Future<List<BaseAsset>> Function(
int index, int index,
@@ -77,6 +78,7 @@ class TimelineService {
int _bufferOffset = 0; int _bufferOffset = 0;
List<BaseAsset> _buffer = []; List<BaseAsset> _buffer = [];
StreamSubscription? _bucketSubscription; StreamSubscription? _bucketSubscription;
final _log = Logger('TimelineService');
int _totalAssets = 0; int _totalAssets = 0;
int get totalAssets => _totalAssets; int get totalAssets => _totalAssets;
@@ -128,10 +130,10 @@ class TimelineService {
Stream<List<Bucket>> Function() get watchBuckets => _bucketSource; Stream<List<Bucket>> Function() get watchBuckets => _bucketSource;
Future<List<BaseAsset>> loadAssets(int index, int count) => Future<List<BaseAsset>?> loadAssets(int index, int count) =>
_mutex.run(() => _loadAssets(index, count)); _mutex.run(() => _loadAssets(index, count));
Future<List<BaseAsset>> _loadAssets(int index, int count) async { Future<List<BaseAsset>?> _loadAssets(int index, int count) async {
if (hasRange(index, count)) { if (hasRange(index, count)) {
return getAssets(index, count); return getAssets(index, count);
} }
@@ -169,9 +171,10 @@ class TimelineService {
index + count <= _bufferOffset + _buffer.length && index + count <= _bufferOffset + _buffer.length &&
index + count <= _totalAssets; index + count <= _totalAssets;
List<BaseAsset> getAssets(int index, int count) { List<BaseAsset>? getAssets(int index, int count) {
if (!hasRange(index, count)) { if (!hasRange(index, count)) {
throw RangeError('TimelineService::getAssets Index out of range'); _log.warning('TimelineService::getAssets Index out of range');
return null;
} }
int start = index - _bufferOffset; int start = index - _bufferOffset;
return _buffer.slice(start, start + count); return _buffer.slice(start, start + count);

View File

@@ -107,19 +107,22 @@ class _FixedSegmentRow extends ConsumerWidget {
} }
if (timelineService.hasRange(assetIndex, assetCount)) { if (timelineService.hasRange(assetIndex, assetCount)) {
return _buildAssetRow( final assets = timelineService.getAssets(assetIndex, assetCount);
context, if (assets == null) {
timelineService.getAssets(assetIndex, assetCount), return _buildPlaceholder(context);
); }
return _buildAssetRow(context, assets);
} }
return FutureBuilder<List<BaseAsset>>( return FutureBuilder<List<BaseAsset>?>(
future: timelineService.loadAssets(assetIndex, assetCount), future: timelineService.loadAssets(assetIndex, assetCount),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) { if (snapshot.connectionState != ConnectionState.done ||
snapshot.data == null) {
return _buildPlaceholder(context); return _buildPlaceholder(context);
} }
return _buildAssetRow(context, snapshot.requireData); return _buildAssetRow(context, snapshot.data!);
}, },
); );
} }

View File

@@ -112,8 +112,9 @@ class _BulkSelectIconButton extends ConsumerWidget {
List<BaseAsset> bucketAssets; List<BaseAsset> bucketAssets;
try { try {
bucketAssets = ref bucketAssets = ref
.watch(timelineServiceProvider) .watch(timelineServiceProvider)
.getAssets(assetOffset, bucket.assetCount); .getAssets(assetOffset, bucket.assetCount) ??
[];
} catch (e) { } catch (e) {
bucketAssets = <BaseAsset>[]; bucketAssets = <BaseAsset>[];
} }

View File

@@ -1,6 +1,5 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.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/domain/services/timeline.service.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
@@ -130,7 +129,7 @@ class MultiSelectNotifier extends Notifier<MultiSelectState> {
final assets = await _timelineService.loadAssets(offset, bucketCount); final assets = await _timelineService.loadAssets(offset, bucketCount);
final selectedAssets = state.selectedAssets.toSet(); final selectedAssets = state.selectedAssets.toSet();
selectedAssets.addAll(assets); selectedAssets.addAll(assets ?? []);
state = state.copyWith( state = state.copyWith(
selectedAssets: selectedAssets, selectedAssets: selectedAssets,
@@ -141,14 +140,14 @@ class MultiSelectNotifier extends Notifier<MultiSelectState> {
final assets = await _timelineService.loadAssets(offset, bucketCount); final assets = await _timelineService.loadAssets(offset, bucketCount);
final selectedAssets = state.selectedAssets.toSet(); final selectedAssets = state.selectedAssets.toSet();
selectedAssets.removeAll(assets); selectedAssets.removeAll(assets ?? []);
state = state.copyWith(selectedAssets: selectedAssets); state = state.copyWith(selectedAssets: selectedAssets);
} }
void toggleBucketSelection(int offset, int bucketCount) async { void toggleBucketSelection(int offset, int bucketCount) async {
final assets = await _timelineService.loadAssets(offset, bucketCount); final assets = await _timelineService.loadAssets(offset, bucketCount);
toggleBucketSelectionByAssets(assets); toggleBucketSelectionByAssets(assets ?? []);
} }
void toggleBucketSelectionByAssets(List<BaseAsset> bucketAssets) { void toggleBucketSelectionByAssets(List<BaseAsset> bucketAssets) {