feat: open asset from activities page on list tile tap

feat: remove autofocus on activity page open

feat: made only activity tile thumbnail image tappable
This commit is contained in:
Dmitry 2025-07-15 23:54:50 +07:00 committed by bwees
parent 8fe54a4de1
commit aa7027bb95
No known key found for this signature in database
2 changed files with 76 additions and 17 deletions

View file

@ -13,24 +13,25 @@ class ActivityTextField extends HookConsumerWidget {
final String? likeId; final String? likeId;
final Function(String) onSubmit; final Function(String) onSubmit;
const ActivityTextField({required this.onSubmit, this.isEnabled = true, this.likeId, super.key}); const ActivityTextField({
required this.onSubmit,
this.isEnabled = true,
this.likeId,
super.key,
});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final album = ref.watch(currentAlbumProvider)!; final album = ref.watch(currentAlbumProvider)!;
final asset = ref.watch(currentAssetProvider); final asset = ref.watch(currentAssetProvider);
final activityNotifier = ref.read(albumActivityProvider(album.remoteId!, asset?.remoteId).notifier); final activityNotifier = ref.read(
albumActivityProvider(album.remoteId!, asset?.remoteId).notifier,
);
final user = ref.watch(currentUserProvider); final user = ref.watch(currentUserProvider);
final inputController = useTextEditingController(); final inputController = useTextEditingController();
final inputFocusNode = useFocusNode(); final inputFocusNode = useFocusNode();
final liked = likeId != null; final liked = likeId != null;
// Show keyboard immediately on activities open
useEffect(() {
inputFocusNode.requestFocus();
return null;
}, []);
// Pass text to callback and reset controller // Pass text to callback and reset controller
void onEditingComplete() { void onEditingComplete() {
onSubmit(inputController.text); onSubmit(inputController.text);
@ -68,13 +69,21 @@ class ActivityTextField extends HookConsumerWidget {
suffixIcon: Padding( suffixIcon: Padding(
padding: const EdgeInsets.only(right: 10), padding: const EdgeInsets.only(right: 10),
child: IconButton( child: IconButton(
icon: Icon(liked ? Icons.favorite_rounded : Icons.favorite_border_rounded), icon: Icon(
liked ? Icons.favorite_rounded : Icons.favorite_border_rounded,
),
onPressed: liked ? removeLike : addLike, onPressed: liked ? removeLike : addLike,
), ),
), ),
suffixIconColor: liked ? Colors.red[700] : null, suffixIconColor: liked ? Colors.red[700] : null,
hintText: !isEnabled ? 'shared_album_activities_input_disable'.tr() : 'say_something'.tr(), hintText: !isEnabled
hintStyle: TextStyle(fontWeight: FontWeight.normal, fontSize: 14, color: Colors.grey[600]), ? 'shared_album_activities_input_disable'.tr()
: 'say_something'.tr(),
hintStyle: TextStyle(
fontWeight: FontWeight.normal,
fontSize: 14,
color: Colors.grey[600],
),
), ),
onEditingComplete: onEditingComplete, onEditingComplete: onEditingComplete,
onTapOutside: (_) => inputFocusNode.unfocus(), onTapOutside: (_) => inputFocusNode.unfocus(),

View file

@ -1,10 +1,15 @@
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/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/providers/asset_viewer/show_controls.provider.dart';
import 'package:immich_mobile/providers/image/immich_remote_thumbnail_provider.dart';
import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart'; import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
class ActivityTile extends HookConsumerWidget { class ActivityTile extends HookConsumerWidget {
@ -14,6 +19,36 @@ class ActivityTile extends HookConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
Future<void> onTap() async {
if (activity.assetId == null) {
return;
}
final asset = await ref
.read(assetRepositoryProvider)
.getByRemoteId(activity.assetId!);
if (asset == null) {
return;
}
final renderList = await RenderList.fromAssets([
asset,
], GroupAssetsBy.none);
final assetNotifier = ref.read(currentAssetProvider.notifier);
assetNotifier.set(asset);
if (asset.isVideo) {
ref.read(showControlsProvider.notifier).show = false;
}
await context.pushRoute(
GalleryViewerRoute(
initialIndex: 0,
heroOffset: 0,
renderList: renderList,
),
);
assetNotifier.set(null);
}
final asset = ref.watch(currentAssetProvider); final asset = ref.watch(currentAssetProvider);
final isLike = activity.type == ActivityType.like; final isLike = activity.type == ActivityType.like;
// Asset thumbnail is displayed when we are accessing activities from the album page // Asset thumbnail is displayed when we are accessing activities from the album page
@ -35,8 +70,15 @@ class ActivityTile extends HookConsumerWidget {
leftAlign: isLike || showAssetThumbnail, leftAlign: isLike || showAssetThumbnail,
), ),
// No subtitle for like, so center title // No subtitle for like, so center title
titleAlignment: !isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center, titleAlignment: !isLike
trailing: showAssetThumbnail ? _ActivityAssetThumbnail(activity.assetId!) : null, ? ListTileTitleAlignment.top
: ListTileTitleAlignment.center,
trailing: showAssetThumbnail
? GestureDetector(
onTap: onTap,
child: _ActivityAssetThumbnail(activity.assetId!),
)
: null,
subtitle: !isLike ? Text(activity.comment!) : null, subtitle: !isLike ? Text(activity.comment!) : null,
); );
} }
@ -47,15 +89,23 @@ class _ActivityTitle extends StatelessWidget {
final String createdAt; final String createdAt;
final bool leftAlign; final bool leftAlign;
const _ActivityTitle({required this.userName, required this.createdAt, required this.leftAlign}); const _ActivityTitle({
required this.userName,
required this.createdAt,
required this.leftAlign,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final textColor = context.isDarkTheme ? Colors.white : Colors.black; final textColor = context.isDarkTheme ? Colors.white : Colors.black;
final textStyle = context.textTheme.bodyMedium?.copyWith(color: textColor.withValues(alpha: 0.6)); final textStyle = context.textTheme.bodyMedium?.copyWith(
color: textColor.withValues(alpha: 0.6),
);
return Row( return Row(
mainAxisAlignment: leftAlign ? MainAxisAlignment.start : MainAxisAlignment.spaceBetween, mainAxisAlignment: leftAlign
? MainAxisAlignment.start
: MainAxisAlignment.spaceBetween,
mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max, mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max,
children: [ children: [
Text(userName, style: textStyle, overflow: TextOverflow.ellipsis), Text(userName, style: textStyle, overflow: TextOverflow.ellipsis),