Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions mobile/lib/domain/services/timeline.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ enum TimelineOrigin {
map,
search,
deepLink,
albumActivities,
}

class TimelineFactory {
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/presentation/pages/drift_activities.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class DriftActivitiesPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final album = ref.watch(currentRemoteAlbumProvider)!;
final asset = ref.watch(currentAssetNotifier) as RemoteAsset?;
final asset = ref.read(currentAssetNotifier) as RemoteAsset?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there reason we are changing from .watch to .read here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the review.
Opening the asset viewer updates the current assetId. Of using "watch," when return to the activities page, the activity timeline will be filtered to the activity of the current asset only. so, changed to "read". I think no side effect.

Copy link
Collaborator Author

@idubnori idubnori Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, with the merge of #23075, asset-only activities viewing feature was no longer be necessary in the Activities page. might be able to remove the asset retrieval altogether in future PR.

final user = ref.watch(currentUserProvider);

final activityNotifier = ref.read(albumActivityProvider(album.id, asset?.id).notifier);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ class _DriftActivityTextFieldState extends ConsumerState<DriftActivityTextField>
inputController = TextEditingController();
inputFocusNode = FocusNode();

if (!widget.isBottomSheet) {
inputFocusNode.requestFocus();
}

inputFocusNode.addListener(() {
if (inputFocusNode.hasFocus) {
widget.onKeyboardFocus?.call();
Expand Down
8 changes: 7 additions & 1 deletion mobile/lib/providers/activity_service.provider.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
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/services/activity.service.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'activity_service.provider.g.dart';

@riverpod
ActivityService activityService(Ref ref) => ActivityService(ref.watch(activityApiRepositoryProvider));
ActivityService activityService(Ref ref) => ActivityService(
ref.watch(activityApiRepositoryProvider),
ref.watch(timelineFactoryProvider),
ref.watch(assetServiceProvider),
);
2 changes: 1 addition & 1 deletion mobile/lib/providers/activity_service.provider.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 26 additions & 1 deletion mobile/lib/services/activity.service.dart
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import 'package:hooks_riverpod/hooks_riverpod.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/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/routing/router.dart';
import 'package:logging/logging.dart';
import 'package:immich_mobile/entities/store.entity.dart' as immich_store;

class ActivityService with ErrorLoggerMixin {
final ActivityApiRepository _activityApiRepository;
final TimelineFactory _timelineFactory;
final AssetService _assetService;

@override
final Logger logger = Logger("ActivityService");

ActivityService(this._activityApiRepository);
ActivityService(this._activityApiRepository, this._timelineFactory, this._assetService);

Future<List<Activity>> getAllActivities(String albumId, {String? assetId}) async {
return logError(
Expand Down Expand Up @@ -49,4 +57,21 @@ class ActivityService with ErrorLoggerMixin {
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;
}
}
36 changes: 25 additions & 11 deletions mobile/lib/widgets/activities/activity_tile.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/datetime_extensions.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/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
Expand All @@ -21,6 +23,14 @@ class ActivityTile extends HookConsumerWidget {
// currentAssetProvider will not be set until we open the gallery viewer
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(
minVerticalPadding: 15,
leading: isLike
Expand All @@ -39,7 +49,7 @@ class ActivityTile extends HookConsumerWidget {
),
// No subtitle for like, so center title
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,
);
}
Expand Down Expand Up @@ -78,22 +88,26 @@ class _ActivityTitle extends StatelessWidget {

class _ActivityAssetThumbnail extends StatelessWidget {
final String assetId;
final GestureTapCallback? onTap;

const _ActivityAssetThumbnail(this.assetId);
const _ActivityAssetThumbnail(this.assetId, this.onTap);

@override
Widget build(BuildContext context) {
return Container(
width: 40,
height: 30,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4)),
image: DecorationImage(
image: ImmichRemoteThumbnailProvider(assetId: assetId),
fit: BoxFit.cover,
return GestureDetector(
onTap: onTap,
child: Container(
width: 40,
height: 30,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4)),
image: DecorationImage(
image: ImmichRemoteThumbnailProvider(assetId: assetId),
fit: BoxFit.cover,
),
),
child: const SizedBox.shrink(),
),
child: const SizedBox.shrink(),
);
}
}
Loading