mirror of
https://github.com/immich-app/immich.git
synced 2025-06-06 15:17:49 -04:00
fix(mobile): asset state remain in gallery view after being deleted (#10603)
* fix(mobile): asset doesn't get removed from state renderList * fix delete last assets * refactor
This commit is contained in:
parent
922430da36
commit
d8175d8da8
@ -19,6 +19,7 @@ import 'package:immich_mobile/providers/asset_viewer/video_player_value_provider
|
|||||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
||||||
import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart';
|
import 'package:immich_mobile/providers/image/immich_remote_image_provider.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/advanced_bottom_sheet.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/advanced_bottom_sheet.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/bottom_gallery_bar.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/bottom_gallery_bar.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/exif_sheet/exif_bottom_sheet.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/exif_sheet/exif_bottom_sheet.dart';
|
||||||
@ -34,17 +35,15 @@ import 'package:isar/isar.dart';
|
|||||||
@RoutePage()
|
@RoutePage()
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class GalleryViewerPage extends HookConsumerWidget {
|
class GalleryViewerPage extends HookConsumerWidget {
|
||||||
final Asset Function(int index) loadAsset;
|
|
||||||
final int totalAssets;
|
|
||||||
final int initialIndex;
|
final int initialIndex;
|
||||||
final int heroOffset;
|
final int heroOffset;
|
||||||
final bool showStack;
|
final bool showStack;
|
||||||
|
final RenderList renderList;
|
||||||
|
|
||||||
GalleryViewerPage({
|
GalleryViewerPage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.initialIndex,
|
required this.renderList,
|
||||||
required this.loadAsset,
|
this.initialIndex = 0,
|
||||||
required this.totalAssets,
|
|
||||||
this.heroOffset = 0,
|
this.heroOffset = 0,
|
||||||
this.showStack = false,
|
this.showStack = false,
|
||||||
}) : controller = PageController(initialPage: initialIndex);
|
}) : controller = PageController(initialPage: initialIndex);
|
||||||
@ -54,6 +53,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final settings = ref.watch(appSettingsServiceProvider);
|
final settings = ref.watch(appSettingsServiceProvider);
|
||||||
|
final loadAsset = renderList.loadAsset;
|
||||||
|
final totalAssets = useState(renderList.totalAssets);
|
||||||
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
||||||
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
||||||
final shouldLoopVideo = useState(AppSettingsEnum.loopVideo.defaultValue);
|
final shouldLoopVideo = useState(AppSettingsEnum.loopVideo.defaultValue);
|
||||||
@ -62,6 +63,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
final localPosition = useState<Offset?>(null);
|
final localPosition = useState<Offset?>(null);
|
||||||
final currentIndex = useState(initialIndex);
|
final currentIndex = useState(initialIndex);
|
||||||
final currentAsset = loadAsset(currentIndex.value);
|
final currentAsset = loadAsset(currentIndex.value);
|
||||||
|
|
||||||
// Update is playing motion video
|
// Update is playing motion video
|
||||||
ref.listen(videoPlaybackValueProvider.select((v) => v.state), (_, state) {
|
ref.listen(videoPlaybackValueProvider.select((v) => v.state), (_, state) {
|
||||||
isPlayingVideo.value = state == VideoPlaybackState.playing;
|
isPlayingVideo.value = state == VideoPlaybackState.playing;
|
||||||
@ -112,13 +114,19 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
debugPrint('Error precaching next image: $exception, $stackTrace');
|
debugPrint('Error precaching next image: $exception, $stackTrace');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index < totalAssets && index >= 0) {
|
try {
|
||||||
final asset = loadAsset(index);
|
if (index < totalAssets.value && index >= 0) {
|
||||||
await precacheImage(
|
final asset = loadAsset(index);
|
||||||
ImmichImage.imageProvider(asset: asset),
|
await precacheImage(
|
||||||
context,
|
ImmichImage.imageProvider(asset: asset),
|
||||||
onError: onError,
|
context,
|
||||||
);
|
onError: onError,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// swallow error silently
|
||||||
|
debugPrint('Error precaching next image: $e');
|
||||||
|
context.maybePop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +305,7 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
? const ScrollPhysics() // Use bouncing physics for iOS
|
? const ScrollPhysics() // Use bouncing physics for iOS
|
||||||
: const ClampingScrollPhysics() // Use heavy physics for Android
|
: const ClampingScrollPhysics() // Use heavy physics for Android
|
||||||
),
|
),
|
||||||
itemCount: totalAssets,
|
itemCount: totalAssets.value,
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
onPageChanged: (value) async {
|
onPageChanged: (value) async {
|
||||||
final next = currentIndex.value < value ? value + 1 : value - 1;
|
final next = currentIndex.value < value ? value + 1 : value - 1;
|
||||||
@ -406,11 +414,13 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
BottomGalleryBar(
|
BottomGalleryBar(
|
||||||
|
renderList: renderList,
|
||||||
totalAssets: totalAssets,
|
totalAssets: totalAssets,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
showStack: showStack,
|
showStack: showStack,
|
||||||
stackIndex: stackIndex.value,
|
stackIndex: stackIndex.value,
|
||||||
asset: asset,
|
asset: asset,
|
||||||
|
assetIndex: currentIndex.value,
|
||||||
showVideoPlayerControls: !asset.isImage && !isMotionPhoto,
|
showVideoPlayerControls: !asset.isImage && !isMotionPhoto,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -16,6 +16,7 @@ import 'package:immich_mobile/models/map/map_marker.model.dart';
|
|||||||
import 'package:immich_mobile/providers/map/map_marker.provider.dart';
|
import 'package:immich_mobile/providers/map/map_marker.provider.dart';
|
||||||
import 'package:immich_mobile/providers/map/map_state.provider.dart';
|
import 'package:immich_mobile/providers/map/map_state.provider.dart';
|
||||||
import 'package:immich_mobile/utils/map_utils.dart';
|
import 'package:immich_mobile/utils/map_utils.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||||
import 'package:immich_mobile/widgets/map/map_app_bar.dart';
|
import 'package:immich_mobile/widgets/map/map_app_bar.dart';
|
||||||
import 'package:immich_mobile/widgets/map/map_asset_grid.dart';
|
import 'package:immich_mobile/widgets/map/map_asset_grid.dart';
|
||||||
import 'package:immich_mobile/widgets/map/map_bottom_sheet.dart';
|
import 'package:immich_mobile/widgets/map/map_bottom_sheet.dart';
|
||||||
@ -178,12 +179,17 @@ class MapPage extends HookConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Since we only have a single asset, we can just show GroupAssetBy.none
|
||||||
|
final renderList = await RenderList.fromAssets(
|
||||||
|
[asset],
|
||||||
|
GroupAssetsBy.none,
|
||||||
|
);
|
||||||
|
|
||||||
context.pushRoute(
|
context.pushRoute(
|
||||||
GalleryViewerRoute(
|
GalleryViewerRoute(
|
||||||
initialIndex: 0,
|
initialIndex: 0,
|
||||||
loadAsset: (index) => asset,
|
|
||||||
totalAssets: 1,
|
|
||||||
heroOffset: 0,
|
heroOffset: 0,
|
||||||
|
renderList: renderList,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,7 @@ import 'package:immich_mobile/routing/backup_permission_guard.dart';
|
|||||||
import 'package:immich_mobile/routing/custom_transition_builders.dart';
|
import 'package:immich_mobile/routing/custom_transition_builders.dart';
|
||||||
import 'package:immich_mobile/routing/duplicate_guard.dart';
|
import 'package:immich_mobile/routing/duplicate_guard.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart' hide LatLng;
|
import 'package:photo_manager/photo_manager.dart' hide LatLng;
|
||||||
|
@ -183,9 +183,8 @@ abstract class _$AppRouter extends RootStackRouter {
|
|||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
child: GalleryViewerPage(
|
child: GalleryViewerPage(
|
||||||
key: args.key,
|
key: args.key,
|
||||||
|
renderList: args.renderList,
|
||||||
initialIndex: args.initialIndex,
|
initialIndex: args.initialIndex,
|
||||||
loadAsset: args.loadAsset,
|
|
||||||
totalAssets: args.totalAssets,
|
|
||||||
heroOffset: args.heroOffset,
|
heroOffset: args.heroOffset,
|
||||||
showStack: args.showStack,
|
showStack: args.showStack,
|
||||||
),
|
),
|
||||||
@ -870,9 +869,8 @@ class FavoritesRoute extends PageRouteInfo<void> {
|
|||||||
class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
|
class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
|
||||||
GalleryViewerRoute({
|
GalleryViewerRoute({
|
||||||
Key? key,
|
Key? key,
|
||||||
required int initialIndex,
|
required RenderList renderList,
|
||||||
required Asset Function(int) loadAsset,
|
int initialIndex = 0,
|
||||||
required int totalAssets,
|
|
||||||
int heroOffset = 0,
|
int heroOffset = 0,
|
||||||
bool showStack = false,
|
bool showStack = false,
|
||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
@ -880,9 +878,8 @@ class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
|
|||||||
GalleryViewerRoute.name,
|
GalleryViewerRoute.name,
|
||||||
args: GalleryViewerRouteArgs(
|
args: GalleryViewerRouteArgs(
|
||||||
key: key,
|
key: key,
|
||||||
|
renderList: renderList,
|
||||||
initialIndex: initialIndex,
|
initialIndex: initialIndex,
|
||||||
loadAsset: loadAsset,
|
|
||||||
totalAssets: totalAssets,
|
|
||||||
heroOffset: heroOffset,
|
heroOffset: heroOffset,
|
||||||
showStack: showStack,
|
showStack: showStack,
|
||||||
),
|
),
|
||||||
@ -898,28 +895,25 @@ class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
|
|||||||
class GalleryViewerRouteArgs {
|
class GalleryViewerRouteArgs {
|
||||||
const GalleryViewerRouteArgs({
|
const GalleryViewerRouteArgs({
|
||||||
this.key,
|
this.key,
|
||||||
required this.initialIndex,
|
required this.renderList,
|
||||||
required this.loadAsset,
|
this.initialIndex = 0,
|
||||||
required this.totalAssets,
|
|
||||||
this.heroOffset = 0,
|
this.heroOffset = 0,
|
||||||
this.showStack = false,
|
this.showStack = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
|
final RenderList renderList;
|
||||||
|
|
||||||
final int initialIndex;
|
final int initialIndex;
|
||||||
|
|
||||||
final Asset Function(int) loadAsset;
|
|
||||||
|
|
||||||
final int totalAssets;
|
|
||||||
|
|
||||||
final int heroOffset;
|
final int heroOffset;
|
||||||
|
|
||||||
final bool showStack;
|
final bool showStack;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'GalleryViewerRouteArgs{key: $key, initialIndex: $initialIndex, loadAsset: $loadAsset, totalAssets: $totalAssets, heroOffset: $heroOffset, showStack: $showStack}';
|
return 'GalleryViewerRouteArgs{key: $key, renderList: $renderList, initialIndex: $initialIndex, heroOffset: $heroOffset, showStack: $showStack}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,4 +311,12 @@ class RenderList {
|
|||||||
GroupAssetsBy groupBy,
|
GroupAssetsBy groupBy,
|
||||||
) =>
|
) =>
|
||||||
_buildRenderList(assets, null, groupBy);
|
_buildRenderList(assets, null, groupBy);
|
||||||
|
|
||||||
|
/// Deletes an asset from the render list and clears the buffer
|
||||||
|
/// This is only a workaround for deleted images still appearing in the gallery
|
||||||
|
void deleteAsset(Asset deleteAsset) {
|
||||||
|
allAssets?.remove(deleteAsset);
|
||||||
|
_buf.clear();
|
||||||
|
_bufOffset = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,12 @@ import 'dart:collection';
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/services.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/collection_extensions.dart';
|
import 'package:immich_mobile/extensions/collection_extensions.dart';
|
||||||
@ -815,6 +817,7 @@ class _AssetRow extends StatelessWidget {
|
|||||||
key: key,
|
key: key,
|
||||||
children: assets.mapIndexed((int index, Asset asset) {
|
children: assets.mapIndexed((int index, Asset asset) {
|
||||||
final bool last = index + 1 == assetsPerRow;
|
final bool last = index + 1 == assetsPerRow;
|
||||||
|
final isSelected = isSelectionActive && selectedAssets.contains(asset);
|
||||||
return Container(
|
return Container(
|
||||||
width: width * widthDistribution[index],
|
width: width * widthDistribution[index],
|
||||||
height: width,
|
height: width,
|
||||||
@ -822,21 +825,40 @@ class _AssetRow extends StatelessWidget {
|
|||||||
bottom: margin,
|
bottom: margin,
|
||||||
right: last ? 0.0 : margin,
|
right: last ? 0.0 : margin,
|
||||||
),
|
),
|
||||||
child: AssetIndexWrapper(
|
child: GestureDetector(
|
||||||
rowIndex: rowStartIndex + index,
|
onTap: () {
|
||||||
sectionIndex: sectionIndex,
|
if (selectionActive) {
|
||||||
child: ThumbnailImage(
|
if (isSelected) {
|
||||||
asset: asset,
|
onDeselect?.call(asset);
|
||||||
index: absoluteOffset + index,
|
} else {
|
||||||
loadAsset: renderList.loadAsset,
|
onSelect?.call(asset);
|
||||||
totalAssets: renderList.totalAssets,
|
}
|
||||||
multiselectEnabled: selectionActive,
|
} else {
|
||||||
isSelected: isSelectionActive && selectedAssets.contains(asset),
|
context.pushRoute(
|
||||||
onSelect: () => onSelect?.call(asset),
|
GalleryViewerRoute(
|
||||||
onDeselect: () => onDeselect?.call(asset),
|
renderList: renderList,
|
||||||
showStorageIndicator: showStorageIndicator,
|
initialIndex: absoluteOffset + index,
|
||||||
heroOffset: heroOffset,
|
heroOffset: heroOffset,
|
||||||
showStack: showStack,
|
showStack: showStack,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
onSelect?.call(asset);
|
||||||
|
HapticFeedback.heavyImpact();
|
||||||
|
},
|
||||||
|
child: AssetIndexWrapper(
|
||||||
|
rowIndex: rowStartIndex + index,
|
||||||
|
sectionIndex: sectionIndex,
|
||||||
|
child: ThumbnailImage(
|
||||||
|
asset: asset,
|
||||||
|
multiselectEnabled: selectionActive,
|
||||||
|
isSelected: isSelectionActive && selectedAssets.contains(asset),
|
||||||
|
showStorageIndicator: showStorageIndicator,
|
||||||
|
heroOffset: heroOffset,
|
||||||
|
showStack: showStack,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,40 +1,42 @@
|
|||||||
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/routing/router.dart';
|
|
||||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||||
import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
|
|
||||||
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
|
import 'package:immich_mobile/widgets/common/immich_thumbnail.dart';
|
||||||
import 'package:immich_mobile/utils/storage_indicator.dart';
|
import 'package:immich_mobile/utils/storage_indicator.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
|
||||||
class ThumbnailImage extends ConsumerWidget {
|
class ThumbnailImage extends ConsumerWidget {
|
||||||
|
/// The asset to show the thumbnail image for
|
||||||
final Asset asset;
|
final Asset asset;
|
||||||
final int index;
|
|
||||||
final Asset Function(int index) loadAsset;
|
/// Whether to show the storage indicator icont over the image or not
|
||||||
final int totalAssets;
|
|
||||||
final bool showStorageIndicator;
|
final bool showStorageIndicator;
|
||||||
|
|
||||||
|
/// Whether to show the show stack icon over the image or not
|
||||||
final bool showStack;
|
final bool showStack;
|
||||||
|
|
||||||
|
/// Whether to show the checkmark indicating that this image is selected
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
|
|
||||||
|
/// Can override [isSelected] and never show the selection indicator
|
||||||
final bool multiselectEnabled;
|
final bool multiselectEnabled;
|
||||||
final Function? onSelect;
|
|
||||||
final Function? onDeselect;
|
/// If we are allowed to deselect this image
|
||||||
|
final bool canDeselect;
|
||||||
|
|
||||||
|
/// The offset index to apply to this hero tag for animation
|
||||||
final int heroOffset;
|
final int heroOffset;
|
||||||
|
|
||||||
const ThumbnailImage({
|
const ThumbnailImage({
|
||||||
super.key,
|
super.key,
|
||||||
required this.asset,
|
required this.asset,
|
||||||
required this.index,
|
|
||||||
required this.loadAsset,
|
|
||||||
required this.totalAssets,
|
|
||||||
this.showStorageIndicator = true,
|
this.showStorageIndicator = true,
|
||||||
this.showStack = false,
|
this.showStack = false,
|
||||||
this.isSelected = false,
|
this.isSelected = false,
|
||||||
this.multiselectEnabled = false,
|
this.multiselectEnabled = false,
|
||||||
this.onDeselect,
|
|
||||||
this.onSelect,
|
|
||||||
this.heroOffset = 0,
|
this.heroOffset = 0,
|
||||||
|
this.canDeselect = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -147,11 +149,7 @@ class ThumbnailImage extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(
|
color: canDeselect ? assetContainerColor : Colors.grey,
|
||||||
width: 0,
|
|
||||||
color: onDeselect == null ? Colors.grey : assetContainerColor,
|
|
||||||
),
|
|
||||||
color: onDeselect == null ? Colors.grey : assetContainerColor,
|
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.only(
|
borderRadius: const BorderRadius.only(
|
||||||
@ -165,79 +163,52 @@ class ThumbnailImage extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return Stack(
|
||||||
onTap: () {
|
children: [
|
||||||
if (multiselectEnabled) {
|
AnimatedContainer(
|
||||||
if (isSelected) {
|
duration: const Duration(milliseconds: 300),
|
||||||
onDeselect?.call();
|
curve: Curves.decelerate,
|
||||||
} else {
|
decoration: BoxDecoration(
|
||||||
onSelect?.call();
|
border: multiselectEnabled && isSelected
|
||||||
}
|
? Border.all(
|
||||||
} else {
|
color: canDeselect ? assetContainerColor : Colors.grey,
|
||||||
context.pushRoute(
|
width: 8,
|
||||||
GalleryViewerRoute(
|
)
|
||||||
initialIndex: index,
|
: const Border(),
|
||||||
loadAsset: loadAsset,
|
|
||||||
totalAssets: totalAssets,
|
|
||||||
heroOffset: heroOffset,
|
|
||||||
showStack: showStack,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onLongPress: () {
|
|
||||||
onSelect?.call();
|
|
||||||
ref.read(hapticFeedbackProvider.notifier).heavyImpact();
|
|
||||||
},
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
AnimatedContainer(
|
|
||||||
duration: const Duration(milliseconds: 300),
|
|
||||||
curve: Curves.decelerate,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: multiselectEnabled && isSelected
|
|
||||||
? Border.all(
|
|
||||||
color: onDeselect == null
|
|
||||||
? Colors.grey
|
|
||||||
: assetContainerColor,
|
|
||||||
width: 8,
|
|
||||||
)
|
|
||||||
: const Border(),
|
|
||||||
),
|
|
||||||
child: buildImage(),
|
|
||||||
),
|
),
|
||||||
if (multiselectEnabled)
|
child: buildImage(),
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.all(3.0),
|
if (multiselectEnabled)
|
||||||
child: Align(
|
Padding(
|
||||||
alignment: Alignment.topLeft,
|
padding: const EdgeInsets.all(3.0),
|
||||||
child: buildSelectionIcon(asset),
|
child: Align(
|
||||||
),
|
alignment: Alignment.topLeft,
|
||||||
|
child: buildSelectionIcon(asset),
|
||||||
),
|
),
|
||||||
if (showStorageIndicator)
|
),
|
||||||
Positioned(
|
if (showStorageIndicator)
|
||||||
right: 8,
|
Positioned(
|
||||||
bottom: 5,
|
right: 8,
|
||||||
child: Icon(
|
bottom: 5,
|
||||||
storageIcon(asset),
|
child: Icon(
|
||||||
color: Colors.white,
|
storageIcon(asset),
|
||||||
size: 18,
|
color: Colors.white,
|
||||||
),
|
size: 18,
|
||||||
),
|
),
|
||||||
if (asset.isFavorite)
|
),
|
||||||
const Positioned(
|
if (asset.isFavorite)
|
||||||
left: 8,
|
const Positioned(
|
||||||
bottom: 5,
|
left: 8,
|
||||||
child: Icon(
|
bottom: 5,
|
||||||
Icons.favorite,
|
child: Icon(
|
||||||
color: Colors.white,
|
Icons.favorite,
|
||||||
size: 18,
|
color: Colors.white,
|
||||||
),
|
size: 18,
|
||||||
),
|
),
|
||||||
if (!asset.isImage) buildVideoIcon(),
|
),
|
||||||
if (asset.stackChildrenCount > 0) buildStackIcon(),
|
if (!asset.isImage) buildVideoIcon(),
|
||||||
],
|
if (asset.stackChildrenCount > 0) buildStackIcon(),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import 'package:immich_mobile/providers/asset_viewer/asset_stack.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/asset_viewer/image_viewer_page_state.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/image_viewer_page_state.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart';
|
||||||
import 'package:immich_mobile/services/asset_stack.service.dart';
|
import 'package:immich_mobile/services/asset_stack.service.dart';
|
||||||
|
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_viewer/video_controls.dart';
|
import 'package:immich_mobile/widgets/asset_viewer/video_controls.dart';
|
||||||
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
|
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
@ -21,20 +22,24 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
|||||||
|
|
||||||
class BottomGalleryBar extends ConsumerWidget {
|
class BottomGalleryBar extends ConsumerWidget {
|
||||||
final Asset asset;
|
final Asset asset;
|
||||||
|
final int assetIndex;
|
||||||
final bool showStack;
|
final bool showStack;
|
||||||
final int stackIndex;
|
final int stackIndex;
|
||||||
final int totalAssets;
|
final ValueNotifier<int> totalAssets;
|
||||||
final bool showVideoPlayerControls;
|
final bool showVideoPlayerControls;
|
||||||
final PageController controller;
|
final PageController controller;
|
||||||
|
final RenderList renderList;
|
||||||
|
|
||||||
const BottomGalleryBar({
|
const BottomGalleryBar({
|
||||||
super.key,
|
super.key,
|
||||||
required this.showStack,
|
required this.showStack,
|
||||||
required this.stackIndex,
|
required this.stackIndex,
|
||||||
required this.asset,
|
required this.asset,
|
||||||
|
required this.assetIndex,
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.totalAssets,
|
required this.totalAssets,
|
||||||
required this.showVideoPlayerControls,
|
required this.showVideoPlayerControls,
|
||||||
|
required this.renderList,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -108,16 +113,17 @@ class BottomGalleryBar extends ConsumerWidget {
|
|||||||
force: force,
|
force: force,
|
||||||
);
|
);
|
||||||
if (isDeleted && isParent) {
|
if (isDeleted && isParent) {
|
||||||
if (totalAssets == 1) {
|
// Workaround for asset remaining in the gallery
|
||||||
|
renderList.deleteAsset(asset);
|
||||||
|
|
||||||
|
// `assetIndex == totalAssets.value - 1` handle the case of removing the last asset
|
||||||
|
// to not throw the error when the next preCache index is called
|
||||||
|
if (totalAssets.value == 1 || assetIndex == totalAssets.value - 1) {
|
||||||
// Handle only one asset
|
// Handle only one asset
|
||||||
context.maybePop();
|
context.maybePop();
|
||||||
} else {
|
|
||||||
// Go to next page otherwise
|
|
||||||
controller.nextPage(
|
|
||||||
duration: const Duration(milliseconds: 100),
|
|
||||||
curve: Curves.fastLinearToSlowEaseIn,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
totalAssets.value -= 1;
|
||||||
}
|
}
|
||||||
return isDeleted;
|
return isDeleted;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user