Browse Source

Move selection logic to asset grid class

Matthias Rupp 2 years ago
parent
commit
a117e897ca

+ 14 - 47
mobile/lib/modules/home/ui/asset_grid/daily_title_text.dart

@@ -8,11 +8,17 @@ class DailyTitleText extends ConsumerWidget {
   const DailyTitleText({
     Key? key,
     required this.isoDate,
-    required this.assetGroup,
+    required this.multiselectEnabled,
+    required this.onSelect,
+    required this.onDeselect,
+    required this.selected,
   }) : super(key: key);
 
   final String isoDate;
-  final List<AssetResponseDto> assetGroup;
+  final bool multiselectEnabled;
+  final Function onSelect;
+  final Function onDeselect;
+  final bool selected;
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
@@ -23,51 +29,12 @@ class DailyTitleText extends ConsumerWidget {
         : "daily_title_text_date_year".tr();
     var dateText =
         DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
-    var isMultiSelectEnable =
-        ref.watch(homePageStateProvider).isMultiSelectEnable;
-    var selectedDateGroup = ref.watch(homePageStateProvider).selectedDateGroup;
-    var selectedItems = ref.watch(homePageStateProvider).selectedItems;
 
-    void _handleTitleIconClick() {
-      if (isMultiSelectEnable &&
-          selectedDateGroup.contains(dateText) &&
-          selectedDateGroup.length == 1 &&
-          selectedItems.length <= assetGroup.length) {
-        // Multi select is active - click again on the icon while it is the only active group -> disable multi select
-        ref.watch(homePageStateProvider.notifier).disableMultiSelect();
-      } else if (isMultiSelectEnable &&
-          selectedDateGroup.contains(dateText) &&
-          selectedItems.length != assetGroup.length) {
-        // Multi select is active - click again on the icon while it is not the only active group -> remove that group from selected group/items
-        ref
-            .watch(homePageStateProvider.notifier)
-            .removeSelectedDateGroup(dateText);
-        ref
-            .watch(homePageStateProvider.notifier)
-            .removeMultipleSelectedItem(assetGroup);
-      } else if (isMultiSelectEnable &&
-          selectedDateGroup.contains(dateText) &&
-          selectedDateGroup.length > 1) {
-        ref
-            .watch(homePageStateProvider.notifier)
-            .removeSelectedDateGroup(dateText);
-        ref
-            .watch(homePageStateProvider.notifier)
-            .removeMultipleSelectedItem(assetGroup);
-      } else if (isMultiSelectEnable && !selectedDateGroup.contains(dateText)) {
-        ref
-            .watch(homePageStateProvider.notifier)
-            .addSelectedDateGroup(dateText);
-        ref
-            .watch(homePageStateProvider.notifier)
-            .addMultipleSelectedItems(assetGroup);
+    void handleTitleIconClick() {
+      if (selected) {
+        onDeselect();
       } else {
-        ref
-            .watch(homePageStateProvider.notifier)
-            .enableMultiSelect(assetGroup.toSet());
-        ref
-            .watch(homePageStateProvider.notifier)
-            .addSelectedDateGroup(dateText);
+        onSelect();
       }
     }
 
@@ -89,8 +56,8 @@ class DailyTitleText extends ConsumerWidget {
           ),
           const Spacer(),
           GestureDetector(
-            onTap: _handleTitleIconClick,
-            child: isMultiSelectEnable && selectedDateGroup.contains(dateText)
+            onTap: handleTitleIconClick,
+            child: multiselectEnabled && selected
                 ? Icon(
                     Icons.check_circle_rounded,
                     color: Theme.of(context).primaryColor,

+ 36 - 40
mobile/lib/modules/home/ui/disable_multi_select_button.dart → mobile/lib/modules/home/ui/asset_grid/disable_multi_select_button.dart

@@ -1,40 +1,36 @@
-import 'package:flutter/material.dart';
-import 'package:hooks_riverpod/hooks_riverpod.dart';
-
-class DisableMultiSelectButton extends ConsumerWidget {
-  const DisableMultiSelectButton({
-    Key? key,
-    required this.onPressed,
-    required this.selectedItemCount,
-  }) : super(key: key);
-
-  final Function onPressed;
-  final int selectedItemCount;
-
-  @override
-  Widget build(BuildContext context, WidgetRef ref) {
-    return Positioned(
-      top: 10,
-      left: 0,
-      child: Padding(
-        padding: const EdgeInsets.only(left: 16.0, top: 46),
-        child: Padding(
-          padding: const EdgeInsets.symmetric(horizontal: 4.0),
-          child: ElevatedButton.icon(
-            onPressed: () {
-              onPressed();
-            },
-            icon: const Icon(Icons.close_rounded),
-            label: Text(
-              '$selectedItemCount',
-              style: const TextStyle(
-                fontWeight: FontWeight.w600,
-                fontSize: 18,
-              ),
-            ),
-          ),
-        ),
-      ),
-    );
-  }
-}
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+
+class DisableMultiSelectButton extends ConsumerWidget {
+  const DisableMultiSelectButton({
+    Key? key,
+    required this.onPressed,
+    required this.selectedItemCount,
+  }) : super(key: key);
+
+  final Function onPressed;
+  final int selectedItemCount;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    return Padding(
+        padding: const EdgeInsets.only(left: 16.0, top: 15),
+        child: Padding(
+          padding: const EdgeInsets.symmetric(horizontal: 4.0),
+          child: ElevatedButton.icon(
+            onPressed: () {
+              onPressed();
+            },
+            icon: const Icon(Icons.close_rounded),
+            label: Text(
+              '$selectedItemCount',
+              style: const TextStyle(
+                fontWeight: FontWeight.w600,
+                fontSize: 18,
+              ),
+            ),
+          ),
+        ),
+    );
+  }
+}

+ 119 - 47
mobile/lib/modules/home/ui/asset_grid/immich_asset_grid.dart

@@ -1,3 +1,4 @@
+import 'dart:collection';
 import 'dart:math';
 
 import 'package:collection/collection.dart';
@@ -5,35 +6,27 @@ import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/cupertino.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/src/widgets/framework.dart';
-import 'package:flutter_hooks/flutter_hooks.dart';
-import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/home/ui/thumbnail_image.dart';
+import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart';
 import 'package:openapi/api.dart';
 import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
 import 'asset_grid_data_structure.dart';
 import 'daily_title_text.dart';
+import 'disable_multi_select_button.dart';
 import 'draggable_scrollbar_custom.dart';
 
-class ImmichAssetGrid extends HookConsumerWidget {
+typedef ImmichAssetGridSelectionListener = void Function(bool);
+
+class ImmichAssetGridState extends State<ImmichAssetGrid> {
   final ItemScrollController _itemScrollController = ItemScrollController();
   final ItemPositionsListener _itemPositionsListener =
-      ItemPositionsListener.create();
-
-  final List<RenderAssetGridElement> renderList;
-  final int assetsPerRow;
-  final double margin;
-  final bool showStorageIndicator;
+  ItemPositionsListener.create();
 
-  ImmichAssetGrid({
-    super.key,
-    required this.renderList,
-    required this.assetsPerRow,
-    required this.showStorageIndicator,
-    this.margin = 5.0,
-  });
+  bool _scrolling = false;
+  bool _multiselect = false;
+  Set<String> _selectedAssets = HashSet();
 
   List<AssetResponseDto> get _assets {
-    return renderList
+    return widget.renderList
         .map((e) {
           if (e.type == RenderAssetGridElementType.assetRow) {
             return e.assetRow!.assets;
@@ -44,10 +37,49 @@ class ImmichAssetGrid extends HookConsumerWidget {
         .flattened
         .toList();
   }
+  
+  void _selectAssets(List<AssetResponseDto> assets) {
+    setState(() {
+
+      if (!_multiselect) {
+        _multiselect = true;
+        widget.listener?.call(true);
+      }
+
+      for (var e in assets) {
+        _selectedAssets.add(e.id);
+      }
+    });
+  }
+
+  void _deselectAssets(List<AssetResponseDto> assets) {
+    setState(() {
+      for (var e in assets) {
+        _selectedAssets.remove(e.id);
+      }
+
+      if (_selectedAssets.isEmpty) {
+        _multiselect = false;
+        widget.listener?.call(false);
+      }
+    });
+  }
+
+  void _deselectAll() {
+    setState(() {
+      _multiselect = false;
+      _selectedAssets.clear();
+    });
+    widget.listener?.call(false);
+  }
+
+  bool _allAssetsSelected(List<AssetResponseDto> assets) {
+    return _multiselect && assets.firstWhereOrNull((e) => !_selectedAssets.contains(e.id)) == null;
+  }
 
   double _getItemSize(BuildContext context) {
-    return MediaQuery.of(context).size.width / assetsPerRow -
-        margin * (assetsPerRow - 1) / assetsPerRow;
+    return MediaQuery.of(context).size.width / widget.assetsPerRow -
+        widget.margin * (widget.assetsPerRow - 1) / widget.assetsPerRow;
   }
 
   Widget _buildThumbnailOrPlaceholder(
@@ -60,7 +92,10 @@ class ImmichAssetGrid extends HookConsumerWidget {
     return ThumbnailImage(
       asset: asset,
       assetList: _assets,
-      showStorageIndicator: showStorageIndicator,
+      multiselectEnabled: _multiselect,
+      isSelected: _selectedAssets.contains(asset.id),
+      onSelect: () => _selectAssets([asset]),
+      onDeselect: () => _deselectAssets([asset]),
       useGrayBoxPlaceholder: true,
     );
   }
@@ -78,7 +113,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
           key: Key("asset-${asset.id}"),
           width: size,
           height: size,
-          margin: EdgeInsets.only(top: margin, right: last ? 0.0 : margin),
+          margin: EdgeInsets.only(top: widget.margin, right: last ? 0.0 : widget.margin),
           child: _buildThumbnailOrPlaceholder(asset, scrolling),
         );
       }).toList(),
@@ -89,7 +124,10 @@ class ImmichAssetGrid extends HookConsumerWidget {
       BuildContext context, String title, List<AssetResponseDto> assets) {
     return DailyTitleText(
       isoDate: title,
-      assetGroup: assets,
+      multiselectEnabled: _multiselect,
+      onSelect: () => _selectAssets(assets),
+      onDeselect: () => _deselectAssets(assets),
+      selected: _allAssetsSelected(assets),
     );
   }
 
@@ -111,22 +149,22 @@ class ImmichAssetGrid extends HookConsumerWidget {
     );
   }
 
-  Widget _itemBuilder(BuildContext c, int position, bool scrolling) {
-    final item = renderList[position];
+  Widget _itemBuilder(BuildContext c, int position) {
+    final item = widget.renderList[position];
 
     if (item.type == RenderAssetGridElementType.dayTitle) {
       return _buildTitle(c, item.title!, item.relatedAssetList!);
     } else if (item.type == RenderAssetGridElementType.monthTitle) {
       return _buildMonthTitle(c, item.title!);
     } else if (item.type == RenderAssetGridElementType.assetRow) {
-      return _buildAssetRow(c, item.assetRow!, scrolling);
+      return _buildAssetRow(c, item.assetRow!, _scrolling);
     }
 
     return const Text("Invalid widget type!");
   }
 
   Text _labelBuilder(int pos) {
-    final date = renderList[pos].date;
+    final date = widget.renderList[pos].date;
     return Text(DateFormat.yMMMd().format(date),
       style: const TextStyle(
         color: Colors.white,
@@ -135,26 +173,27 @@ class ImmichAssetGrid extends HookConsumerWidget {
     );
   }
 
+  Widget _buildMultiSelectIndicator() {
+    return DisableMultiSelectButton(
+      onPressed: () => _deselectAll(),
+      selectedItemCount: _selectedAssets.length,
+    );
+  }
 
-  @override
-  Widget build(BuildContext context, WidgetRef ref) {
-    final scrolling = useState(false);
-
+  Widget _buildAssetGrid() {
     final useDragScrolling = _assets.length > 100;
 
     void dragScrolling(bool active) {
-      scrolling.value = active;
-    }
-
-    Widget itemBuilder(BuildContext c, int position) {
-      return _itemBuilder(c, position, scrolling.value);
+      setState(() {
+        _scrolling = active;
+      });
     }
 
     final listWidget = ScrollablePositionedList.builder(
-      itemBuilder: itemBuilder,
+      itemBuilder: _itemBuilder,
       itemPositionsListener: _itemPositionsListener,
       itemScrollController: _itemScrollController,
-      itemCount: renderList.length,
+      itemCount: widget.renderList.length,
     );
 
     if (!useDragScrolling) {
@@ -162,15 +201,48 @@ class ImmichAssetGrid extends HookConsumerWidget {
     }
 
     return DraggableScrollbar.semicircle(
-        scrollStateListener: dragScrolling,
-        itemPositionsListener: _itemPositionsListener,
-        controller: _itemScrollController,
-        backgroundColor: Theme.of(context).hintColor,
-        labelTextBuilder: _labelBuilder,
-        labelConstraints: const BoxConstraints(maxHeight: 28),
-        scrollbarAnimationDuration: const Duration(seconds: 1),
-        scrollbarTimeToFade: const Duration(seconds: 4),
-        child: listWidget,
+      scrollStateListener: dragScrolling,
+      itemPositionsListener: _itemPositionsListener,
+      controller: _itemScrollController,
+      backgroundColor: Theme.of(context).hintColor,
+      labelTextBuilder: _labelBuilder,
+      labelConstraints: const BoxConstraints(maxHeight: 28),
+      scrollbarAnimationDuration: const Duration(seconds: 1),
+      scrollbarTimeToFade: const Duration(seconds: 4),
+      child: listWidget,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Stack(
+      children: [
+        _buildAssetGrid(),
+        if (_multiselect) _buildMultiSelectIndicator(),
+      ],
     );
   }
 }
+
+class ImmichAssetGrid extends StatefulWidget {
+  final List<RenderAssetGridElement> renderList;
+  final int assetsPerRow;
+  final double margin;
+  final bool showStorageIndicator;
+  final ImmichAssetGridSelectionListener? listener;
+
+
+  ImmichAssetGrid({
+    super.key,
+    required this.renderList,
+    required this.assetsPerRow,
+    required this.showStorageIndicator,
+    this.listener,
+    this.margin = 5.0,
+  });
+
+  @override
+  State<StatefulWidget> createState() {
+    return ImmichAssetGridState();
+  }
+}

+ 174 - 176
mobile/lib/modules/home/ui/thumbnail_image.dart → mobile/lib/modules/home/ui/asset_grid/thumbnail_image.dart

@@ -1,176 +1,174 @@
-import 'package:auto_route/auto_route.dart';
-import 'package:cached_network_image/cached_network_image.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:hive_flutter/hive_flutter.dart';
-import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/constants/hive_box.dart';
-import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
-import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
-import 'package:immich_mobile/routing/router.dart';
-import 'package:immich_mobile/utils/image_url_builder.dart';
-import 'package:openapi/api.dart';
-
-class ThumbnailImage extends HookConsumerWidget {
-  final AssetResponseDto asset;
-  final List<AssetResponseDto> assetList;
-  final bool showStorageIndicator;
-  final bool useGrayBoxPlaceholder;
-
-  const ThumbnailImage({
-    Key? key,
-    required this.asset,
-    required this.assetList,
-    this.showStorageIndicator = true,
-    this.useGrayBoxPlaceholder = false,
-  }) : super(key: key);
-
-  @override
-  Widget build(BuildContext context, WidgetRef ref) {
-    var box = Hive.box(userInfoBox);
-    var thumbnailRequestUrl = getThumbnailUrl(asset);
-    var selectedAsset = ref.watch(homePageStateProvider).selectedItems;
-    var isMultiSelectEnable =
-        ref.watch(homePageStateProvider).isMultiSelectEnable;
-    var deviceId = ref.watch(authenticationProvider).deviceId;
-
-    Widget buildSelectionIcon(AssetResponseDto asset) {
-      if (selectedAsset.contains(asset)) {
-        return Icon(
-          Icons.check_circle,
-          color: Theme.of(context).primaryColor,
-        );
-      } else {
-        return const Icon(
-          Icons.circle_outlined,
-          color: Colors.white,
-        );
-      }
-    }
-
-    return GestureDetector(
-      onTap: () {
-        if (isMultiSelectEnable &&
-            selectedAsset.contains(asset) &&
-            selectedAsset.length == 1) {
-          ref.watch(homePageStateProvider.notifier).disableMultiSelect();
-        } else if (isMultiSelectEnable &&
-            selectedAsset.contains(asset) &&
-            selectedAsset.length > 1) {
-          ref
-              .watch(homePageStateProvider.notifier)
-              .removeSingleSelectedItem(asset);
-        } else if (isMultiSelectEnable && !selectedAsset.contains(asset)) {
-          ref
-              .watch(homePageStateProvider.notifier)
-              .addSingleSelectedItem(asset);
-        } else {
-          AutoRouter.of(context).push(
-            GalleryViewerRoute(
-              assetList: assetList,
-              asset: asset,
-            ),
-          );
-        }
-      },
-      onLongPress: () {
-        // Enable multi select function
-        ref.watch(homePageStateProvider.notifier).enableMultiSelect({asset});
-        HapticFeedback.heavyImpact();
-      },
-      child: Hero(
-        tag: asset.id,
-        child: Stack(
-          children: [
-            Container(
-              decoration: BoxDecoration(
-                border: isMultiSelectEnable && selectedAsset.contains(asset)
-                    ? Border.all(
-                        color: Theme.of(context).primaryColorLight,
-                        width: 10,
-                      )
-                    : const Border(),
-              ),
-              child: CachedNetworkImage(
-                cacheKey: 'thumbnail-image-${asset.id}',
-                width: 300,
-                height: 300,
-                memCacheHeight: 200,
-                maxWidthDiskCache: 200,
-                maxHeightDiskCache: 200,
-                fit: BoxFit.cover,
-                imageUrl: thumbnailRequestUrl,
-                httpHeaders: {
-                  "Authorization": "Bearer ${box.get(accessTokenKey)}"
-                },
-                fadeInDuration: const Duration(milliseconds: 250),
-                progressIndicatorBuilder: (context, url, downloadProgress) {
-                  if (useGrayBoxPlaceholder) {
-                    return const DecoratedBox(
-                      decoration: BoxDecoration(color: Colors.grey),
-                    );
-                  }
-                  return Transform.scale(
-                    scale: 0.2,
-                    child: CircularProgressIndicator(
-                      value: downloadProgress.progress,
-                    ),
-                  );
-                },
-                errorWidget: (context, url, error) {
-                  debugPrint("Error getting thumbnail $url = $error");
-                  CachedNetworkImage.evictFromCache(thumbnailRequestUrl);
-
-                  return Icon(
-                    Icons.image_not_supported_outlined,
-                    color: Theme.of(context).primaryColor,
-                  );
-                },
-              ),
-            ),
-            if (isMultiSelectEnable)
-              Padding(
-                padding: const EdgeInsets.all(3.0),
-                child: Align(
-                  alignment: Alignment.topLeft,
-                  child: buildSelectionIcon(asset),
-                ),
-              ),
-            if (showStorageIndicator)
-              Positioned(
-                right: 10,
-                bottom: 5,
-                child: Icon(
-                  (deviceId != asset.deviceId)
-                      ? Icons.cloud_done_outlined
-                      : Icons.photo_library_rounded,
-                  color: Colors.white,
-                  size: 18,
-                ),
-              ),
-            if (asset.type != AssetTypeEnum.IMAGE)
-              Positioned(
-                top: 5,
-                right: 5,
-                child: Row(
-                  children: [
-                    Text(
-                      asset.duration.toString().substring(0, 7),
-                      style: const TextStyle(
-                        color: Colors.white,
-                        fontSize: 10,
-                      ),
-                    ),
-                    const Icon(
-                      Icons.play_circle_outline_rounded,
-                      color: Colors.white,
-                    ),
-                  ],
-                ),
-              ),
-          ],
-        ),
-      ),
-    );
-  }
-}
+import 'package:auto_route/auto_route.dart';
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:collection/collection.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:hive_flutter/hive_flutter.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/constants/hive_box.dart';
+import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
+import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
+import 'package:immich_mobile/routing/router.dart';
+import 'package:immich_mobile/utils/image_url_builder.dart';
+import 'package:openapi/api.dart';
+
+class ThumbnailImage extends HookConsumerWidget {
+  final AssetResponseDto asset;
+  final List<AssetResponseDto> assetList;
+  final bool showStorageIndicator;
+  final bool useGrayBoxPlaceholder;
+  final bool isSelected;
+  final bool multiselectEnabled;
+  final Function? onSelect;
+  final Function? onDeselect;
+
+  const ThumbnailImage({
+    Key? key,
+    required this.asset,
+    required this.assetList,
+    this.showStorageIndicator = true,
+    this.useGrayBoxPlaceholder = false,
+    this.isSelected = false,
+    this.multiselectEnabled = false,
+    this.onDeselect,
+    this.onSelect,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    var box = Hive.box(userInfoBox);
+    var thumbnailRequestUrl = getThumbnailUrl(asset);
+    var deviceId = ref.watch(authenticationProvider).deviceId;
+
+
+    Widget buildSelectionIcon(AssetResponseDto asset) {
+      if (isSelected) {
+        return Icon(
+          Icons.check_circle,
+          color: Theme.of(context).primaryColor,
+        );
+      } else {
+        return const Icon(
+          Icons.circle_outlined,
+          color: Colors.white,
+        );
+      }
+    }
+
+    return GestureDetector(
+      onTap: () {
+        if (multiselectEnabled) {
+          if (isSelected) {
+            onDeselect?.call();
+          } else {
+            onSelect?.call();
+          }
+        } else {
+          AutoRouter.of(context).push(
+            GalleryViewerRoute(
+              assetList: assetList,
+              asset: asset,
+            ),
+          );
+        }
+      },
+      onLongPress: () {
+        onSelect?.call();
+        HapticFeedback.heavyImpact();
+      },
+      child: Hero(
+        tag: asset.id,
+        child: Stack(
+          children: [
+            Container(
+              decoration: BoxDecoration(
+                border: multiselectEnabled && isSelected
+                    ? Border.all(
+                        color: Theme.of(context).primaryColorLight,
+                        width: 10,
+                      )
+                    : const Border(),
+              ),
+              child: CachedNetworkImage(
+                cacheKey: 'thumbnail-image-${asset.id}',
+                width: 300,
+                height: 300,
+                memCacheHeight: 200,
+                maxWidthDiskCache: 200,
+                maxHeightDiskCache: 200,
+                fit: BoxFit.cover,
+                imageUrl: thumbnailRequestUrl,
+                httpHeaders: {
+                  "Authorization": "Bearer ${box.get(accessTokenKey)}"
+                },
+                fadeInDuration: const Duration(milliseconds: 250),
+                progressIndicatorBuilder: (context, url, downloadProgress) {
+                  if (useGrayBoxPlaceholder) {
+                    return const DecoratedBox(
+                      decoration: BoxDecoration(color: Colors.grey),
+                    );
+                  }
+                  return Transform.scale(
+                    scale: 0.2,
+                    child: CircularProgressIndicator(
+                      value: downloadProgress.progress,
+                    ),
+                  );
+                },
+                errorWidget: (context, url, error) {
+                  debugPrint("Error getting thumbnail $url = $error");
+                  CachedNetworkImage.evictFromCache(thumbnailRequestUrl);
+
+                  return Icon(
+                    Icons.image_not_supported_outlined,
+                    color: Theme.of(context).primaryColor,
+                  );
+                },
+              ),
+            ),
+            if (multiselectEnabled)
+              Padding(
+                padding: const EdgeInsets.all(3.0),
+                child: Align(
+                  alignment: Alignment.topLeft,
+                  child: buildSelectionIcon(asset),
+                ),
+              ),
+            if (showStorageIndicator)
+              Positioned(
+                right: 10,
+                bottom: 5,
+                child: Icon(
+                  (deviceId != asset.deviceId)
+                      ? Icons.cloud_done_outlined
+                      : Icons.photo_library_rounded,
+                  color: Colors.white,
+                  size: 18,
+                ),
+              ),
+            if (asset.type != AssetTypeEnum.IMAGE)
+              Positioned(
+                top: 5,
+                right: 5,
+                child: Row(
+                  children: [
+                    Text(
+                      asset.duration.toString().substring(0, 7),
+                      style: const TextStyle(
+                        color: Colors.white,
+                        fontSize: 10,
+                      ),
+                    ),
+                    const Icon(
+                      Icons.play_circle_outline_rounded,
+                      color: Colors.white,
+                    ),
+                  ],
+                ),
+              ),
+          ],
+        ),
+      ),
+    );
+  }
+}

+ 1 - 1
mobile/lib/modules/home/ui/image_grid.dart

@@ -1,6 +1,6 @@
 import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:immich_mobile/modules/home/ui/thumbnail_image.dart';
+import 'package:immich_mobile/modules/home/ui/asset_grid/thumbnail_image.dart';
 import 'package:openapi/api.dart';
 
 // ignore: must_be_immutable

+ 10 - 17
mobile/lib/modules/home/views/home_page.dart

@@ -5,7 +5,6 @@ import 'package:immich_mobile/modules/home/providers/home_page_render_list_provi
 import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
 import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
 import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart';
-import 'package:immich_mobile/modules/home/ui/disable_multi_select_button.dart';
 import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart';
 import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer.dart';
 import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
@@ -20,12 +19,9 @@ class HomePage extends HookConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     final appSettingService = ref.watch(appSettingsServiceProvider);
-
     var renderList = ref.watch(renderListProvider);
 
-    var isMultiSelectEnable =
-        ref.watch(homePageStateProvider).isMultiSelectEnable;
-    var homePageState = ref.watch(homePageStateProvider);
+    final multiselectEnabled = useState(false);
 
     useEffect(
       () {
@@ -41,16 +37,9 @@ class HomePage extends HookConsumerWidget {
       ref.read(assetProvider.notifier).getAllAsset();
     }
 
-    buildSelectedItemCountIndicator() {
-      return DisableMultiSelectButton(
-        onPressed: ref.watch(homePageStateProvider.notifier).disableMultiSelect,
-        selectedItemCount: homePageState.selectedItems.length,
-      );
-    }
-
     Widget buildBody() {
       buildSliverAppBar() {
-        return isMultiSelectEnable
+        return multiselectEnabled.value
             ? const SliverToBoxAdapter(
                 child: SizedBox(
                   height: 70,
@@ -62,9 +51,13 @@ class HomePage extends HookConsumerWidget {
               );
       }
 
+      void selectionListener(bool multiselect) {
+        multiselectEnabled.value = multiselect;
+      }
+
       return SafeArea(
-        bottom: !isMultiSelectEnable,
-        top: !isMultiSelectEnable,
+        bottom: !multiselectEnabled.value,
+        top: !multiselectEnabled.value,
         child: Stack(
           children: [
             CustomScrollView(
@@ -80,10 +73,10 @@ class HomePage extends HookConsumerWidget {
                     appSettingService.getSetting(AppSettingsEnum.tilesPerRow),
                 showStorageIndicator: appSettingService
                     .getSetting(AppSettingsEnum.storageIndicator),
+                listener: selectionListener,
               ),
             ),
-            if (isMultiSelectEnable) ...[
-              buildSelectedItemCountIndicator(),
+            if (multiselectEnabled.value) ...[
               const ControlBottomAppBar(),
             ],
           ],