chore: refactor show view in timeline button (#22894)

* chore: refactor show view in timeline button

This refactor includes changes to notify asset viewer about where an asset was shown from.

* chore: realized I could just pull from the timelineProvider instead of storing it in the asset viewer state

* chore: rename enum to TimelineOrigin and update members

* fix: update isOwner condition

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
This commit is contained in:
Brandon Wees 2025-10-14 10:25:48 -05:00 committed by GitHub
parent d778286777
commit b484a52252
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 60 additions and 17 deletions

View File

@ -16,7 +16,24 @@ typedef TimelineAssetSource = Future<List<BaseAsset>> Function(int index, int co
typedef TimelineBucketSource = Stream<List<Bucket>> Function();
typedef TimelineQuery = ({TimelineAssetSource assetSource, TimelineBucketSource bucketSource});
typedef TimelineQuery = ({TimelineAssetSource assetSource, TimelineBucketSource bucketSource, TimelineOrigin origin});
enum TimelineOrigin {
main,
localAlbum,
remoteAlbum,
remoteAssets,
favorite,
trash,
archive,
lockedFolder,
video,
place,
person,
map,
search,
deepLink,
}
class TimelineFactory {
final DriftTimelineRepository _timelineRepository;
@ -57,7 +74,8 @@ class TimelineFactory {
TimelineService person(String userId, String personId) =>
TimelineService(_timelineRepository.person(userId, personId, groupBy));
TimelineService fromAssets(List<BaseAsset> assets) => TimelineService(_timelineRepository.fromAssets(assets));
TimelineService fromAssets(List<BaseAsset> assets, TimelineOrigin type) =>
TimelineService(_timelineRepository.fromAssets(assets, type));
TimelineService map(String userId, LatLngBounds bounds) =>
TimelineService(_timelineRepository.map(userId, bounds, groupBy));
@ -66,6 +84,7 @@ class TimelineFactory {
class TimelineService {
final TimelineAssetSource _assetSource;
final TimelineBucketSource _bucketSource;
final TimelineOrigin origin;
final AsyncMutex _mutex = AsyncMutex();
int _bufferOffset = 0;
List<BaseAsset> _buffer = [];
@ -74,11 +93,15 @@ class TimelineService {
int _totalAssets = 0;
int get totalAssets => _totalAssets;
TimelineService(TimelineQuery query) : this._(assetSource: query.assetSource, bucketSource: query.bucketSource);
TimelineService(TimelineQuery query)
: this._(assetSource: query.assetSource, bucketSource: query.bucketSource, origin: query.origin);
TimelineService._({required TimelineAssetSource assetSource, required TimelineBucketSource bucketSource})
: _assetSource = assetSource,
_bucketSource = bucketSource {
TimelineService._({
required TimelineAssetSource assetSource,
required TimelineBucketSource bucketSource,
required this.origin,
}) : _assetSource = assetSource,
_bucketSource = bucketSource {
_bucketSubscription = _bucketSource().listen((buckets) {
_mutex.run(() async {
final totalAssets = buckets.fold<int>(0, (acc, bucket) => acc + bucket.assetCount);

View File

@ -35,6 +35,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
TimelineQuery main(List<String> userIds, GroupAssetsBy groupBy) => (
bucketSource: () => _watchMainBucket(userIds, groupBy: groupBy),
assetSource: (offset, count) => _getMainBucketAssets(userIds, offset: offset, count: count),
origin: TimelineOrigin.main,
);
Stream<List<Bucket>> _watchMainBucket(List<String> userIds, {GroupAssetsBy groupBy = GroupAssetsBy.day}) {
@ -91,6 +92,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
TimelineQuery localAlbum(String albumId, GroupAssetsBy groupBy) => (
bucketSource: () => _watchLocalAlbumBucket(albumId, groupBy: groupBy),
assetSource: (offset, count) => _getLocalAlbumBucketAssets(albumId, offset: offset, count: count),
origin: TimelineOrigin.localAlbum,
);
Stream<List<Bucket>> _watchLocalAlbumBucket(String albumId, {GroupAssetsBy groupBy = GroupAssetsBy.day}) {
@ -156,6 +158,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
TimelineQuery remoteAlbum(String albumId, GroupAssetsBy groupBy) => (
bucketSource: () => _watchRemoteAlbumBucket(albumId, groupBy: groupBy),
assetSource: (offset, count) => _getRemoteAlbumBucketAssets(albumId, offset: offset, count: count),
origin: TimelineOrigin.remoteAlbum,
);
Stream<List<Bucket>> _watchRemoteAlbumBucket(String albumId, {GroupAssetsBy groupBy = GroupAssetsBy.day}) {
@ -244,15 +247,17 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
.get();
}
TimelineQuery fromAssets(List<BaseAsset> assets) => (
TimelineQuery fromAssets(List<BaseAsset> assets, TimelineOrigin origin) => (
bucketSource: () => Stream.value(_generateBuckets(assets.length)),
assetSource: (offset, count) => Future.value(assets.skip(offset).take(count).toList(growable: false)),
origin: origin,
);
TimelineQuery remote(String ownerId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) =>
row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.timeline) & row.ownerId.equals(ownerId),
groupBy: groupBy,
origin: TimelineOrigin.remoteAssets,
);
TimelineQuery favorite(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
@ -262,11 +267,13 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
row.ownerId.equals(userId) &
row.visibility.equalsValue(AssetVisibility.timeline),
groupBy: groupBy,
origin: TimelineOrigin.favorite,
);
TimelineQuery trash(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) => row.deletedAt.isNotNull() & row.ownerId.equals(userId),
groupBy: groupBy,
origin: TimelineOrigin.trash,
joinLocal: true,
);
@ -274,11 +281,13 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
filter: (row) =>
row.deletedAt.isNull() & row.ownerId.equals(userId) & row.visibility.equalsValue(AssetVisibility.archive),
groupBy: groupBy,
origin: TimelineOrigin.archive,
);
TimelineQuery locked(String userId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
filter: (row) =>
row.deletedAt.isNull() & row.visibility.equalsValue(AssetVisibility.locked) & row.ownerId.equals(userId),
origin: TimelineOrigin.lockedFolder,
groupBy: groupBy,
);
@ -288,17 +297,20 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
row.type.equalsValue(AssetType.video) &
row.visibility.equalsValue(AssetVisibility.timeline) &
row.ownerId.equals(userId),
origin: TimelineOrigin.video,
groupBy: groupBy,
);
TimelineQuery place(String place, GroupAssetsBy groupBy) => (
bucketSource: () => _watchPlaceBucket(place, groupBy: groupBy),
assetSource: (offset, count) => _getPlaceBucketAssets(place, offset: offset, count: count),
origin: TimelineOrigin.place,
);
TimelineQuery person(String userId, String personId, GroupAssetsBy groupBy) => (
bucketSource: () => _watchPersonBucket(userId, personId, groupBy: groupBy),
assetSource: (offset, count) => _getPersonBucketAssets(userId, personId, offset: offset, count: count),
origin: TimelineOrigin.person,
);
Stream<List<Bucket>> _watchPlaceBucket(String place, {GroupAssetsBy groupBy = GroupAssetsBy.day}) {
@ -434,6 +446,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
TimelineQuery map(String userId, LatLngBounds bounds, GroupAssetsBy groupBy) => (
bucketSource: () => _watchMapBucket(userId, bounds, groupBy: groupBy),
assetSource: (offset, count) => _getMapBucketAssets(userId, bounds, offset: offset, count: count),
origin: TimelineOrigin.map,
);
Stream<List<Bucket>> _watchMapBucket(
@ -502,6 +515,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
@pragma('vm:prefer-inline')
TimelineQuery _remoteQueryBuilder({
required Expression<bool> Function($RemoteAssetEntityTable row) filter,
required TimelineOrigin origin,
GroupAssetsBy groupBy = GroupAssetsBy.day,
bool joinLocal = false,
}) {
@ -509,6 +523,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
bucketSource: () => _watchRemoteBucket(filter: filter, groupBy: groupBy),
assetSource: (offset, count) =>
_getRemoteAssets(filter: filter, offset: offset, count: count, joinLocal: joinLocal),
origin: origin,
);
}

View File

@ -8,6 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/entities/asset.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
@ -624,7 +625,7 @@ class _SearchResultGrid extends ConsumerWidget {
child: ProviderScope(
overrides: [
timelineServiceProvider.overrideWith((ref) {
final timelineService = ref.watch(timelineFactoryProvider).fromAssets(assets);
final timelineService = ref.watch(timelineFactoryProvider).fromAssets(assets, TimelineOrigin.search);
ref.onDispose(timelineService.dispose);
return timelineService;
}),

View File

@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/timeline.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
@ -17,8 +18,8 @@ import 'package:immich_mobile/providers/cast.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/routes.provider.dart';
import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@ -39,13 +40,13 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
final isInLockedView = ref.watch(inLockedViewProvider);
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
final previousRouteName = ref.watch(previousRouteNameProvider);
final tabRoute = ref.watch(tabProvider);
final timelineOrigin = ref.read(timelineServiceProvider).origin;
final showViewInTimelineButton =
(previousRouteName != TabShellRoute.name || tabRoute == TabEnum.search) &&
previousRouteName != AssetViewerRoute.name &&
previousRouteName != null &&
previousRouteName != LocalTimelineRoute.name &&
timelineOrigin != TimelineOrigin.main &&
timelineOrigin != TimelineOrigin.deepLink &&
timelineOrigin != TimelineOrigin.trash &&
timelineOrigin != TimelineOrigin.archive &&
timelineOrigin != TimelineOrigin.localAlbum &&
isOwner;
final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet));

View File

@ -8,8 +8,8 @@ import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
import 'package:immich_mobile/providers/album/current_album.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart' as beta_asset_provider;
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart' as beta_asset_provider;
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
@ -150,7 +150,10 @@ class DeepLinkService {
}
AssetViewer.setAsset(ref, asset);
return AssetViewerRoute(initialIndex: 0, timelineService: _betaTimelineFactory.fromAssets([asset]));
return AssetViewerRoute(
initialIndex: 0,
timelineService: _betaTimelineFactory.fromAssets([asset], TimelineOrigin.deepLink),
);
} else {
// TODO: Remove this when beta is default
final asset = await _assetService.getAssetByRemoteId(assetId);