mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
feat: (mobile) open asset viewer from album activity page (#23182)
* feat(mobile): open assetviewer via album activities page * adjust ui behavior: keep current asset & disable initial forcus * fix: Run 'make build' and 'make pigeon'
This commit is contained in:
parent
221e0ef02f
commit
2129f889f5
7 changed files with 61 additions and 19 deletions
|
|
@ -33,6 +33,7 @@ enum TimelineOrigin {
|
||||||
map,
|
map,
|
||||||
search,
|
search,
|
||||||
deepLink,
|
deepLink,
|
||||||
|
albumActivities,
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimelineFactory {
|
class TimelineFactory {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ class DriftActivitiesPage extends HookConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final album = ref.watch(currentRemoteAlbumProvider)!;
|
final album = ref.watch(currentRemoteAlbumProvider)!;
|
||||||
final asset = ref.watch(currentAssetNotifier) as RemoteAsset?;
|
final asset = ref.read(currentAssetNotifier) as RemoteAsset?;
|
||||||
final user = ref.watch(currentUserProvider);
|
final user = ref.watch(currentUserProvider);
|
||||||
|
|
||||||
final activityNotifier = ref.read(albumActivityProvider(album.id, asset?.id).notifier);
|
final activityNotifier = ref.read(albumActivityProvider(album.id, asset?.id).notifier);
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,6 @@ class _DriftActivityTextFieldState extends ConsumerState<DriftActivityTextField>
|
||||||
inputController = TextEditingController();
|
inputController = TextEditingController();
|
||||||
inputFocusNode = FocusNode();
|
inputFocusNode = FocusNode();
|
||||||
|
|
||||||
if (!widget.isBottomSheet) {
|
|
||||||
inputFocusNode.requestFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
inputFocusNode.addListener(() {
|
inputFocusNode.addListener(() {
|
||||||
if (inputFocusNode.hasFocus) {
|
if (inputFocusNode.hasFocus) {
|
||||||
widget.onKeyboardFocus?.call();
|
widget.onKeyboardFocus?.call();
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/activity_api.repository.dart';
|
import 'package:immich_mobile/repositories/activity_api.repository.dart';
|
||||||
import 'package:immich_mobile/services/activity.service.dart';
|
import 'package:immich_mobile/services/activity.service.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
@ -6,4 +8,8 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
part 'activity_service.provider.g.dart';
|
part 'activity_service.provider.g.dart';
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
ActivityService activityService(Ref ref) => ActivityService(ref.watch(activityApiRepositoryProvider));
|
ActivityService activityService(Ref ref) => ActivityService(
|
||||||
|
ref.watch(activityApiRepositoryProvider),
|
||||||
|
ref.watch(timelineFactoryProvider),
|
||||||
|
ref.watch(assetServiceProvider),
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part of 'activity_service.provider.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$activityServiceHash() => r'ce775779787588defe1e76406e09a9c109470310';
|
String _$activityServiceHash() => r'3ce0eb33948138057cc63f07a7598047b99e7599';
|
||||||
|
|
||||||
/// See also [activityService].
|
/// See also [activityService].
|
||||||
@ProviderFor(activityService)
|
@ProviderFor(activityService)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,24 @@
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/errors.dart';
|
import 'package:immich_mobile/constants/errors.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/asset.service.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||||
import 'package:immich_mobile/mixins/error_logger.mixin.dart';
|
import 'package:immich_mobile/mixins/error_logger.mixin.dart';
|
||||||
import 'package:immich_mobile/models/activities/activity.model.dart';
|
import 'package:immich_mobile/models/activities/activity.model.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.page.dart';
|
||||||
import 'package:immich_mobile/repositories/activity_api.repository.dart';
|
import 'package:immich_mobile/repositories/activity_api.repository.dart';
|
||||||
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart' as immich_store;
|
||||||
|
|
||||||
class ActivityService with ErrorLoggerMixin {
|
class ActivityService with ErrorLoggerMixin {
|
||||||
final ActivityApiRepository _activityApiRepository;
|
final ActivityApiRepository _activityApiRepository;
|
||||||
|
final TimelineFactory _timelineFactory;
|
||||||
|
final AssetService _assetService;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final Logger logger = Logger("ActivityService");
|
final Logger logger = Logger("ActivityService");
|
||||||
|
|
||||||
ActivityService(this._activityApiRepository);
|
ActivityService(this._activityApiRepository, this._timelineFactory, this._assetService);
|
||||||
|
|
||||||
Future<List<Activity>> getAllActivities(String albumId, {String? assetId}) async {
|
Future<List<Activity>> getAllActivities(String albumId, {String? assetId}) async {
|
||||||
return logError(
|
return logError(
|
||||||
|
|
@ -49,4 +57,21 @@ class ActivityService with ErrorLoggerMixin {
|
||||||
errorMessage: "Failed to create $type for album $albumId",
|
errorMessage: "Failed to create $type for album $albumId",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<AssetViewerRoute?> buildAssetViewerRoute(String assetId, WidgetRef ref) async {
|
||||||
|
if (immich_store.Store.isBetaTimelineEnabled) {
|
||||||
|
final asset = await _assetService.getRemoteAsset(assetId);
|
||||||
|
if (asset == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetViewer.setAsset(ref, asset);
|
||||||
|
return AssetViewerRoute(
|
||||||
|
initialIndex: 0,
|
||||||
|
timelineService: _timelineFactory.fromAssets([asset], TimelineOrigin.albumActivities),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/datetime_extensions.dart';
|
import 'package:immich_mobile/extensions/datetime_extensions.dart';
|
||||||
import 'package:immich_mobile/models/activities/activity.model.dart';
|
import 'package:immich_mobile/models/activities/activity.model.dart';
|
||||||
|
import 'package:immich_mobile/providers/activity_service.provider.dart';
|
||||||
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
|
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
||||||
|
|
@ -21,6 +23,14 @@ class ActivityTile extends HookConsumerWidget {
|
||||||
// currentAssetProvider will not be set until we open the gallery viewer
|
// currentAssetProvider will not be set until we open the gallery viewer
|
||||||
final showAssetThumbnail = asset == null && activity.assetId != null && !isBottomSheet;
|
final showAssetThumbnail = asset == null && activity.assetId != null && !isBottomSheet;
|
||||||
|
|
||||||
|
onTap() async {
|
||||||
|
final activityService = ref.read(activityServiceProvider);
|
||||||
|
final route = await activityService.buildAssetViewerRoute(activity.assetId!, ref);
|
||||||
|
if (route != null) {
|
||||||
|
await context.pushRoute(route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
minVerticalPadding: 15,
|
minVerticalPadding: 15,
|
||||||
leading: isLike
|
leading: isLike
|
||||||
|
|
@ -39,7 +49,7 @@ class ActivityTile extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
// No subtitle for like, so center title
|
// No subtitle for like, so center title
|
||||||
titleAlignment: !isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center,
|
titleAlignment: !isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center,
|
||||||
trailing: showAssetThumbnail ? _ActivityAssetThumbnail(activity.assetId!) : null,
|
trailing: showAssetThumbnail ? _ActivityAssetThumbnail(activity.assetId!, onTap) : null,
|
||||||
subtitle: !isLike ? Text(activity.comment!) : null,
|
subtitle: !isLike ? Text(activity.comment!) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -78,12 +88,15 @@ class _ActivityTitle extends StatelessWidget {
|
||||||
|
|
||||||
class _ActivityAssetThumbnail extends StatelessWidget {
|
class _ActivityAssetThumbnail extends StatelessWidget {
|
||||||
final String assetId;
|
final String assetId;
|
||||||
|
final GestureTapCallback? onTap;
|
||||||
|
|
||||||
const _ActivityAssetThumbnail(this.assetId);
|
const _ActivityAssetThumbnail(this.assetId, this.onTap);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
width: 40,
|
width: 40,
|
||||||
height: 30,
|
height: 30,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
|
|
@ -94,6 +107,7 @@ class _ActivityAssetThumbnail extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: const SizedBox.shrink(),
|
child: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue