fix(mobile): suppress asset stack UI in trash timeline (#26536)

* fix(mobile): suppress asset stack UI in trash timeline

* refactor(mobile): apply review suggestions

* fix(mobile): hide unstack action in the trash timeline

* fix(mobile): move stack indicator out of asset type icons

---------

Co-authored-by: Peter Ombodi <peter.ombodi@gmail.com>
This commit is contained in:
Peter Ombodi 2026-04-29 13:49:47 +03:00 committed by GitHub
parent 7dc84f56c0
commit b74cfd4424
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 43 additions and 9 deletions

View File

@ -7,6 +7,7 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/events.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/scroll_extensions.dart';
@ -363,7 +364,8 @@ class _AssetPageState extends ConsumerState<AssetPage> {
}
BaseAsset displayAsset = asset;
final stackChildren = ref.watch(stackChildrenNotifier(asset)).valueOrNull;
final showAssetStack = ref.watch(timelineServiceProvider.select((s) => s.origin != TimelineOrigin.trash));
final stackChildren = showAssetStack ? ref.watch(stackChildrenNotifier(asset)).valueOrNull : null;
if (stackChildren != null && stackChildren.isNotEmpty) {
displayAsset = stackChildren.elementAt(stackIndex);
}

View File

@ -1,8 +1,10 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
class AssetStackRow extends ConsumerWidget {
final List<RemoteAsset> stack;
@ -15,6 +17,11 @@ class AssetStackRow extends ConsumerWidget {
return const SizedBox.shrink();
}
final hideAssetStack = ref.read(timelineServiceProvider).origin == TimelineOrigin.trash;
if (hideAssetStack) {
return const SizedBox.shrink();
}
final showingControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
double opacity = ref.watch(assetViewerProvider.select((s) => s.backgroundOpacity)) * (showingControls ? 1 : 0);

View File

@ -21,6 +21,7 @@ class ThumbnailTile extends ConsumerStatefulWidget {
this.showStorageIndicator = false,
this.lockSelection = false,
this.heroOffset,
this.showStackIndicator = false,
super.key,
});
@ -30,6 +31,7 @@ class ThumbnailTile extends ConsumerStatefulWidget {
final bool showStorageIndicator;
final bool lockSelection;
final int? heroOffset;
final bool showStackIndicator;
@override
ConsumerState<ThumbnailTile> createState() => _ThumbnailTileState();
@ -139,7 +141,14 @@ class _ThumbnailTileState extends ConsumerState<ThumbnailTile> {
duration: Durations.short4,
child: Align(
alignment: Alignment.topRight,
child: _AssetTypeIcons(asset: asset),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
_AssetTypeIcons(asset: asset),
if (widget.showStackIndicator) _StackIndicator(asset: asset),
],
),
),
),
if (storageIndicator && asset != null)
@ -286,8 +295,8 @@ class _AssetTypeIcons extends StatelessWidget {
@override
Widget build(BuildContext context) {
final hasStack = asset is RemoteAsset && (asset as RemoteAsset).stackId != null;
final isLivePhoto = asset is RemoteAsset && asset.livePhotoVideoId != null;
final remoteAsset = asset is RemoteAsset ? asset as RemoteAsset : null;
final isLivePhoto = remoteAsset?.livePhotoVideoId != null;
return Column(
mainAxisSize: MainAxisSize.min,
@ -295,11 +304,6 @@ class _AssetTypeIcons extends StatelessWidget {
children: [
if (asset.isVideo)
Padding(padding: const EdgeInsets.only(right: 10.0, top: 6.0), child: _VideoIndicator(asset.duration)),
if (hasStack)
const Padding(
padding: EdgeInsets.only(right: 10.0, top: 6.0),
child: _TileOverlayIcon(Icons.burst_mode_rounded),
),
if (isLivePhoto)
const Padding(
padding: EdgeInsets.only(right: 10.0, top: 6.0),
@ -312,6 +316,24 @@ class _AssetTypeIcons extends StatelessWidget {
}
}
class _StackIndicator extends StatelessWidget {
final BaseAsset asset;
const _StackIndicator({required this.asset});
@override
Widget build(BuildContext context) {
if (asset is! RemoteAsset || (asset as RemoteAsset).stackId == null) {
return const SizedBox.shrink();
}
return const Padding(
padding: EdgeInsets.only(right: 10.0, top: 6.0),
child: _TileOverlayIcon(Icons.burst_mode_rounded),
);
}
}
class _UploadProgressOverlay extends StatelessWidget {
final double progress;

View File

@ -244,6 +244,7 @@ class _AssetTileWidget extends ConsumerWidget {
final lockSelection = _getLockSelectionStatus(ref);
final showStorageIndicator = ref.watch(timelineArgsProvider.select((args) => args.showStorageIndicator));
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
final showStackIndicator = ref.read(timelineServiceProvider).origin != TimelineOrigin.trash;
return RepaintBoundary(
child: GestureDetector(
@ -253,6 +254,7 @@ class _AssetTileWidget extends ConsumerWidget {
asset,
lockSelection: lockSelection,
showStorageIndicator: showStorageIndicator,
showStackIndicator: showStackIndicator,
heroOffset: heroOffset,
),
),

View File

@ -148,6 +148,7 @@ enum ActionButtonType {
context.selectedCount == 1,
ActionButtonType.unstack =>
context.isOwner && //
context.timelineOrigin != TimelineOrigin.trash &&
!context.isInLockedView && //
context.isStacked,
ActionButtonType.openInBrowser => context.asset.hasRemote && !context.isInLockedView,