mirror of
https://github.com/immich-app/immich.git
synced 2026-02-18 09:10:12 -05:00
Some widgets, like Icon widgets, automatically inherit opacity from the icon theme in the context. Many other widgets however, do not. The Immich logo, profile picture, and backup badge are examples of widgets of this. All unsupported toolbar widgets have been updated to support inheriting the opacity from the icon theme. IconButtons internally animate properties like opacity, which is kind of nice, but means we have to do more work to replicate that behaviour for other widgets. In most cases, we can simply use an IconButton widget and forward the correct opacity. The Immich logo however is not a button, and therefore we need to use a custom TweenAnimationBuilder. All widgets are using efficient, native opacity rather than the heavy Opacity widget.
114 lines
4.1 KiB
Dart
114 lines
4.1 KiB
Dart
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/presentation/widgets/images/remote_image_provider.dart';
|
|
import 'package:immich_mobile/providers/activity_service.provider.dart';
|
|
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
|
|
import 'package:immich_mobile/widgets/common/user_circle_avatar.dart';
|
|
|
|
class ActivityTile extends HookConsumerWidget {
|
|
final Activity activity;
|
|
final bool isBottomSheet;
|
|
|
|
const ActivityTile(this.activity, {super.key, this.isBottomSheet = false});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final asset = ref.watch(currentAssetProvider);
|
|
final isLike = activity.type == ActivityType.like;
|
|
// Asset thumbnail is displayed when we are accessing activities from the album page
|
|
// 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
|
|
? Container(
|
|
width: isBottomSheet ? 30 : 44,
|
|
alignment: Alignment.center,
|
|
child: Icon(Icons.thumb_up, color: context.primaryColor),
|
|
)
|
|
: isBottomSheet
|
|
? UserCircleAvatar(user: activity.user, size: 30)
|
|
: UserCircleAvatar(user: activity.user),
|
|
title: _ActivityTitle(
|
|
userName: activity.user.name,
|
|
createdAt: activity.createdAt.timeAgo(),
|
|
leftAlign: isBottomSheet ? false : (isLike || showAssetThumbnail),
|
|
),
|
|
// No subtitle for like, so center title
|
|
titleAlignment: !isLike ? ListTileTitleAlignment.top : ListTileTitleAlignment.center,
|
|
trailing: showAssetThumbnail ? _ActivityAssetThumbnail(activity.assetId!, onTap) : null,
|
|
subtitle: !isLike ? Text(activity.comment!) : null,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ActivityTitle extends StatelessWidget {
|
|
final String userName;
|
|
final String createdAt;
|
|
final bool leftAlign;
|
|
|
|
const _ActivityTitle({required this.userName, required this.createdAt, required this.leftAlign});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final textColor = context.isDarkTheme ? Colors.white : Colors.black;
|
|
final textStyle = context.textTheme.bodyMedium?.copyWith(color: textColor.withValues(alpha: 0.6));
|
|
|
|
return Row(
|
|
mainAxisAlignment: leftAlign ? MainAxisAlignment.start : MainAxisAlignment.spaceBetween,
|
|
mainAxisSize: leftAlign ? MainAxisSize.min : MainAxisSize.max,
|
|
children: [
|
|
Text(userName, style: textStyle, overflow: TextOverflow.ellipsis),
|
|
if (leftAlign) Text(" • ", style: textStyle),
|
|
Expanded(
|
|
child: Text(
|
|
createdAt,
|
|
style: textStyle,
|
|
overflow: TextOverflow.ellipsis,
|
|
textAlign: leftAlign ? TextAlign.left : TextAlign.right,
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _ActivityAssetThumbnail extends StatelessWidget {
|
|
final String assetId;
|
|
final GestureTapCallback? onTap;
|
|
|
|
const _ActivityAssetThumbnail(this.assetId, this.onTap);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return GestureDetector(
|
|
onTap: onTap,
|
|
child: Container(
|
|
width: 40,
|
|
height: 30,
|
|
decoration: BoxDecoration(
|
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
|
image: DecorationImage(
|
|
image: RemoteImageProvider.thumbnail(assetId: assetId, thumbhash: ""),
|
|
fit: BoxFit.cover,
|
|
),
|
|
),
|
|
child: const SizedBox.shrink(),
|
|
),
|
|
);
|
|
}
|
|
}
|