Compare commits

...

5 Commits

Author SHA1 Message Date
shenlong-tanwen 76abdc3c82 chore(format): dart format 2024-02-23 17:24:38 +00:00
Marty Fuhry 8b79b31ae5 Handle merge conflicts 2024-02-23 17:24:38 +00:00
Marty Fuhry a7b7d7b417 Removes async, sets offset to 0 2024-02-23 17:24:38 +00:00
Marty Fuhry 0ab6a26871 Removes redundant factory fromAssetsOnly constructor from RenderList 2024-02-23 17:24:38 +00:00
Marty Fuhry ed1294a0ea Fixes an issue where deleted images would still appear in the gallery
Fixes an issue I had with renaming the branch...
2024-02-23 17:24:38 +00:00
7 changed files with 132 additions and 130 deletions
@@ -23,6 +23,7 @@ import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
import 'package:immich_mobile/modules/backup/providers/manual_upload.provider.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/modules/home/ui/upload_dialog.dart';
import 'package:immich_mobile/modules/partner/providers/partner.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@@ -46,17 +47,15 @@ import 'package:openapi/api.dart' show ThumbnailFormat;
@RoutePage()
// ignore: must_be_immutable
class GalleryViewerPage extends HookConsumerWidget {
final Asset Function(int index) loadAsset;
final int totalAssets;
final int initialIndex;
final int heroOffset;
final bool showStack;
final RenderList renderList;
GalleryViewerPage({
super.key,
required this.initialIndex,
required this.loadAsset,
required this.totalAssets,
required this.renderList,
this.initialIndex = 0,
this.heroOffset = 0,
this.showStack = false,
}) : controller = PageController(initialPage: initialIndex);
@@ -69,6 +68,8 @@ class GalleryViewerPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final settings = ref.watch(appSettingsServiceProvider);
final loadAsset = renderList.loadAsset;
final totalAssets = useState(renderList.totalAssets);
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
final isZoomed = useState<bool>(false);
@@ -137,7 +138,7 @@ class GalleryViewerPage extends HookConsumerWidget {
debugPrint('Error precaching next image: $exception, $stackTrace');
}
if (index < totalAssets && index >= 0) {
if (index < totalAssets.value && index >= 0) {
final asset = loadAsset(index);
precacheImage(
ImmichImage.imageProvider(asset: asset),
@@ -199,16 +200,14 @@ class GalleryViewerPage extends HookConsumerWidget {
force: force,
);
if (isDeleted && isParent) {
if (totalAssets == 1) {
// Workaround for asset remaining in the gallery
renderList.deleteAsset(deleteAsset);
if (totalAssets.value == 1) {
// Handle only one asset
context.popRoute();
} else {
// Go to next page otherwise
controller.nextPage(
duration: const Duration(milliseconds: 100),
curve: Curves.fastLinearToSlowEaseIn,
);
}
totalAssets.value -= 1;
}
return isDeleted;
}
@@ -751,7 +750,7 @@ class GalleryViewerPage extends HookConsumerWidget {
? const ScrollPhysics() // Use bouncing physics for iOS
: const ClampingScrollPhysics() // Use heavy physics for Android
),
itemCount: totalAssets,
itemCount: totalAssets.value,
scrollDirection: Axis.horizontal,
onPageChanged: (value) {
final next = currentIndex.value < value ? value + 1 : value - 1;
@@ -311,4 +311,12 @@ class RenderList {
GroupAssetsBy 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,6 +2,7 @@ import 'dart:collection';
import 'dart:developer';
import 'dart:math';
import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -11,6 +12,7 @@ import 'package:immich_mobile/extensions/collection_extensions.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_placeholder.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
@@ -587,6 +589,7 @@ class _AssetRow extends StatelessWidget {
key: key,
children: assets.mapIndexed((int index, Asset asset) {
final bool last = index + 1 == assetsPerRow;
final isSelected = isSelectionActive && selectedAssets.contains(asset);
return Container(
width: width * widthDistribution[index],
height: width,
@@ -594,18 +597,37 @@ class _AssetRow extends StatelessWidget {
bottom: margin,
right: last ? 0.0 : margin,
),
child: ThumbnailImage(
asset: asset,
index: absoluteOffset + index,
loadAsset: renderList.loadAsset,
totalAssets: renderList.totalAssets,
multiselectEnabled: selectionActive,
isSelected: isSelectionActive && selectedAssets.contains(asset),
onSelect: () => onSelect?.call(asset),
onDeselect: () => onDeselect?.call(asset),
showStorageIndicator: showStorageIndicator,
heroOffset: heroOffset,
showStack: showStack,
child: GestureDetector(
onTap: () {
if (selectionActive) {
if (isSelected) {
onDeselect?.call(asset);
} else {
onSelect?.call(asset);
}
} else {
context.pushRoute(
GalleryViewerRoute(
renderList: renderList,
initialIndex: absoluteOffset + index,
heroOffset: heroOffset,
showStack: showStack,
),
);
}
},
onLongPress: () {
onSelect?.call(asset);
HapticFeedback.heavyImpact();
},
child: ThumbnailImage(
asset: asset,
multiselectEnabled: selectionActive,
isSelected: isSelected,
showStorageIndicator: showStorageIndicator,
heroOffset: heroOffset,
showStack: showStack,
),
),
);
}).toList(),
@@ -1,39 +1,42 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_image.dart';
import 'package:immich_mobile/utils/storage_indicator.dart';
import 'package:isar/isar.dart';
/// Shows the thumbnail images in the asset grid view
class ThumbnailImage extends StatelessWidget {
/// The asset to show the thumbnail image for
final Asset asset;
final int index;
final Asset Function(int index) loadAsset;
final int totalAssets;
/// Whether to show the storage indicator icont over the image or not
final bool showStorageIndicator;
/// Whether to show the show stack icon over the image or not
final bool showStack;
/// Whether to show the checkmark indicating that this image is selected
final bool isSelected;
/// Can override [isSelected] and never show the selection indicator
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;
const ThumbnailImage({
super.key,
required this.asset,
required this.index,
required this.loadAsset,
required this.totalAssets,
this.showStorageIndicator = true,
this.showStack = false,
this.isSelected = false,
this.multiselectEnabled = false,
this.onDeselect,
this.onSelect,
this.heroOffset = 0,
this.canDeselect = true,
});
@override
@@ -146,11 +149,7 @@ class ThumbnailImage extends StatelessWidget {
}
return Container(
decoration: BoxDecoration(
border: Border.all(
width: 0,
color: onDeselect == null ? Colors.grey : assetContainerColor,
),
color: onDeselect == null ? Colors.grey : assetContainerColor,
color: canDeselect ? assetContainerColor : Colors.grey,
),
child: ClipRRect(
borderRadius: const BorderRadius.only(
@@ -164,79 +163,52 @@ class ThumbnailImage extends StatelessWidget {
);
}
return GestureDetector(
onTap: () {
if (multiselectEnabled) {
if (isSelected) {
onDeselect?.call();
} else {
onSelect?.call();
}
} else {
context.pushRoute(
GalleryViewerRoute(
initialIndex: index,
loadAsset: loadAsset,
totalAssets: totalAssets,
heroOffset: heroOffset,
showStack: showStack,
),
);
}
},
onLongPress: () {
onSelect?.call();
HapticFeedback.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(),
return Stack(
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.decelerate,
decoration: BoxDecoration(
border: multiselectEnabled && isSelected
? Border.all(
color: canDeselect ? assetContainerColor : Colors.grey,
width: 8,
)
: const Border(),
),
if (multiselectEnabled)
Padding(
padding: const EdgeInsets.all(3.0),
child: Align(
alignment: Alignment.topLeft,
child: buildSelectionIcon(asset),
),
child: buildImage(),
),
if (multiselectEnabled)
Padding(
padding: const EdgeInsets.all(3.0),
child: Align(
alignment: Alignment.topLeft,
child: buildSelectionIcon(asset),
),
if (showStorageIndicator)
Positioned(
right: 8,
bottom: 5,
child: Icon(
storageIcon(asset),
color: Colors.white,
size: 18,
),
),
if (showStorageIndicator)
Positioned(
right: 8,
bottom: 5,
child: Icon(
storageIcon(asset),
color: Colors.white,
size: 18,
),
if (asset.isFavorite)
const Positioned(
left: 8,
bottom: 5,
child: Icon(
Icons.favorite,
color: Colors.white,
size: 18,
),
),
if (asset.isFavorite)
const Positioned(
left: 8,
bottom: 5,
child: Icon(
Icons.favorite,
color: Colors.white,
size: 18,
),
if (!asset.isImage) buildVideoIcon(),
if (asset.stackChildrenCount > 0) buildStackIcon(),
],
),
),
if (!asset.isImage) buildVideoIcon(),
if (asset.stackChildrenCount > 0) buildStackIcon(),
],
);
}
}
+8 -2
View File
@@ -11,6 +11,7 @@ import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/latlngbounds_extension.dart';
import 'package:immich_mobile/extensions/maplibrecontroller_extensions.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/modules/map/models/map_event.model.dart';
import 'package:immich_mobile/modules/map/models/map_marker.dart';
import 'package:immich_mobile/modules/map/providers/map_marker.provider.dart';
@@ -178,11 +179,16 @@ class MapPage extends HookConsumerWidget {
return;
}
// Since we only have a single asset, we can just show GroupAssetBy.none
final renderList = await RenderList.fromAssets(
[asset],
GroupAssetsBy.none,
);
context.pushRoute(
GalleryViewerRoute(
initialIndex: 0,
loadAsset: (index) => asset,
totalAssets: 1,
renderList: renderList,
heroOffset: 0,
),
);
+1
View File
@@ -9,6 +9,7 @@ import 'package:immich_mobile/modules/album/views/asset_selection_page.dart';
import 'package:immich_mobile/modules/album/views/create_album_page.dart';
import 'package:immich_mobile/modules/album/views/library_page.dart';
import 'package:immich_mobile/modules/backup/views/backup_options_page.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/modules/map/views/map_location_picker_page.dart';
import 'package:immich_mobile/modules/map/views/map_page.dart';
import 'package:immich_mobile/modules/memories/models/memory.dart';
+9 -15
View File
@@ -162,9 +162,8 @@ abstract class _$AppRouter extends RootStackRouter {
routeData: routeData,
child: GalleryViewerPage(
key: args.key,
renderList: args.renderList,
initialIndex: args.initialIndex,
loadAsset: args.loadAsset,
totalAssets: args.totalAssets,
heroOffset: args.heroOffset,
showStack: args.showStack,
),
@@ -793,9 +792,8 @@ class FavoritesRoute extends PageRouteInfo<void> {
class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
GalleryViewerRoute({
Key? key,
required int initialIndex,
required Asset Function(int) loadAsset,
required int totalAssets,
required RenderList renderList,
int initialIndex = 0,
int heroOffset = 0,
bool showStack = false,
List<PageRouteInfo>? children,
@@ -803,9 +801,8 @@ class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
GalleryViewerRoute.name,
args: GalleryViewerRouteArgs(
key: key,
renderList: renderList,
initialIndex: initialIndex,
loadAsset: loadAsset,
totalAssets: totalAssets,
heroOffset: heroOffset,
showStack: showStack,
),
@@ -821,28 +818,25 @@ class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
class GalleryViewerRouteArgs {
const GalleryViewerRouteArgs({
this.key,
required this.initialIndex,
required this.loadAsset,
required this.totalAssets,
required this.renderList,
this.initialIndex = 0,
this.heroOffset = 0,
this.showStack = false,
});
final Key? key;
final RenderList renderList;
final int initialIndex;
final Asset Function(int) loadAsset;
final int totalAssets;
final int heroOffset;
final bool showStack;
@override
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}';
}
}