瀏覽代碼

Customization options for asset grid (#498)

* Add settings options for number of assets per row and storage indicator

* Add attributes to enum to avoid duplicate code

* Also apply customizations to albums

* Minor Refactorings

* Three stage loading i18n fix
Matthias Rupp 2 年之前
父節點
當前提交
013a0f8324

+ 1 - 1
mobile/assets/i18n/de-DE.json

@@ -122,5 +122,5 @@
   "theme_setting_image_viewer_quality_title": "Qualität des Bildbetrachters",
   "theme_setting_image_viewer_quality_subtitle": "Einstellen der Qualität des Detailbildbetrachters",
   "theme_setting_three_stage_loading_title": "Dreistufiges Laden aktivieren",
-  "theme_setting_three_stage_loading_subtitle": "Das dreistufige Ladeverfahren liefert die beste Bildqualität, ist dafür aber langsamer beim Laden."
+  "theme_setting_three_stage_loading_subtitle": "Das dreistufige Ladeverfahren kann die Performance beim Laden verbessern, erhöht allerdings den Datenverbrauch deutlich"
 }

+ 5 - 1
mobile/assets/i18n/en-US.json

@@ -135,5 +135,9 @@
   "theme_setting_image_viewer_quality_title": "Image viewer quality",
   "theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
   "theme_setting_three_stage_loading_title": "Enable three-stage loading",
-  "theme_setting_three_stage_loading_subtitle": "The three-stage loading delivers the best quality image in exchange for a slower loading speed"
+  "theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
+  "asset_list_settings_title": "Photo Grid",
+  "asset_list_settings_subtitle": "Photo grid layout settings",
+  "theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
+  "theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})"
 }

+ 3 - 1
mobile/lib/modules/album/ui/album_viewer_thumbnail.dart

@@ -14,11 +14,13 @@ import 'package:openapi/api.dart';
 class AlbumViewerThumbnail extends HookConsumerWidget {
   final AssetResponseDto asset;
   final List<AssetResponseDto> assetList;
+  final bool showStorageIndicator;
 
   const AlbumViewerThumbnail({
     Key? key,
     required this.asset,
     required this.assetList,
+    this.showStorageIndicator = true,
   }) : super(key: key);
 
   @override
@@ -166,7 +168,7 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
       child: Stack(
         children: [
           _buildThumbnailImage(),
-          _buildAssetStoreLocationIcon(),
+          if (showStorageIndicator) _buildAssetStoreLocationIcon(),
           if (asset.type != AssetTypeEnum.IMAGE) _buildVideoLabel(),
           if (isMultiSelectionEnable) _buildAssetSelectionIcon(),
         ],

+ 10 - 2
mobile/lib/modules/album/views/album_viewer_page.dart

@@ -13,6 +13,8 @@ import 'package:immich_mobile/modules/album/ui/album_action_outlined_button.dart
 import 'package:immich_mobile/modules/album/ui/album_viewer_appbar.dart';
 import 'package:immich_mobile/modules/album/ui/album_viewer_editable_title.dart';
 import 'package:immich_mobile/modules/album/ui/album_viewer_thumbnail.dart';
+import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
+import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
 import 'package:immich_mobile/routing/router.dart';
 import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
 import 'package:immich_mobile/shared/ui/immich_sliver_persistent_app_bar_delegate.dart';
@@ -186,12 +188,17 @@ class AlbumViewerPage extends HookConsumerWidget {
     }
 
     Widget _buildImageGrid(AlbumResponseDto albumInfo) {
+      final appSettingService = ref.watch(appSettingsServiceProvider);
+      final bool showStorageIndicator =
+          appSettingService.getSetting(AppSettingsEnum.storageIndicator);
+
       if (albumInfo.assets.isNotEmpty) {
         return SliverPadding(
           padding: const EdgeInsets.only(top: 10.0),
           sliver: SliverGrid(
-            gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
-              crossAxisCount: 3,
+            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+              crossAxisCount:
+                  appSettingService.getSetting(AppSettingsEnum.tilesPerRow),
               crossAxisSpacing: 5.0,
               mainAxisSpacing: 5,
             ),
@@ -200,6 +207,7 @@ class AlbumViewerPage extends HookConsumerWidget {
                 return AlbumViewerThumbnail(
                   asset: albumInfo.assets[index],
                   assetList: albumInfo.assets,
+                  showStorageIndicator: showStorageIndicator,
                 );
               },
               childCount: albumInfo.assetCount,

+ 7 - 2
mobile/lib/modules/home/ui/image_grid.dart

@@ -7,11 +7,15 @@ import 'package:openapi/api.dart';
 class ImageGrid extends ConsumerWidget {
   final List<AssetResponseDto> assetGroup;
   final List<AssetResponseDto> sortedAssetGroup;
+  final int tilesPerRow;
+  final bool showStorageIndicator;
 
   ImageGrid({
     Key? key,
     required this.assetGroup,
     required this.sortedAssetGroup,
+    this.tilesPerRow = 4,
+    this.showStorageIndicator = true,
   }) : super(key: key);
 
   List<AssetResponseDto> imageSortedList = [];
@@ -19,8 +23,8 @@ class ImageGrid extends ConsumerWidget {
   @override
   Widget build(BuildContext context, WidgetRef ref) {
     return SliverGrid(
-      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
-        crossAxisCount: 4,
+      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+        crossAxisCount: tilesPerRow,
         crossAxisSpacing: 5.0,
         mainAxisSpacing: 5,
       ),
@@ -34,6 +38,7 @@ class ImageGrid extends ConsumerWidget {
                 ThumbnailImage(
                   asset: assetGroup[index],
                   assetList: sortedAssetGroup,
+                  showStorageIndicator: showStorageIndicator,
                 ),
                 if (assetType != AssetTypeEnum.IMAGE)
                   Positioned(

+ 7 - 2
mobile/lib/modules/home/ui/thumbnail_image.dart

@@ -15,8 +15,13 @@ import 'package:openapi/api.dart';
 class ThumbnailImage extends HookConsumerWidget {
   final AssetResponseDto asset;
   final List<AssetResponseDto> assetList;
+  final bool showStorageIndicator;
 
-  const ThumbnailImage({Key? key, required this.asset, required this.assetList})
+  const ThumbnailImage(
+      {Key? key,
+      required this.asset,
+      required this.assetList,
+      this.showStorageIndicator = true})
       : super(key: key);
 
   @override
@@ -123,7 +128,7 @@ class ThumbnailImage extends HookConsumerWidget {
                   child: _buildSelectionIcon(asset),
                 ),
               ),
-            Positioned(
+            if (showStorageIndicator) Positioned(
               right: 10,
               bottom: 5,
               child: Icon(

+ 6 - 0
mobile/lib/modules/home/views/home_page.dart

@@ -10,6 +10,8 @@ import 'package:immich_mobile/modules/home/ui/image_grid.dart';
 import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart';
 import 'package:immich_mobile/modules/home/ui/monthly_title_text.dart';
 import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer.dart';
+import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
+import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
 
 import 'package:immich_mobile/shared/providers/asset.provider.dart';
 import 'package:immich_mobile/shared/providers/server_info.provider.dart';
@@ -21,6 +23,8 @@ class HomePage extends HookConsumerWidget {
 
   @override
   Widget build(BuildContext context, WidgetRef ref) {
+    final appSettingService = ref.watch(appSettingsServiceProvider);
+
     ScrollController scrollController = useScrollController();
     var assetGroupByDateTime = ref.watch(assetGroupByDateTimeProvider);
     List<Widget> imageGridGroup = [];
@@ -86,6 +90,8 @@ class HomePage extends HookConsumerWidget {
             ImageGrid(
               assetGroup: immichAssetList,
               sortedAssetGroup: sortedAssetList,
+              tilesPerRow: appSettingService.getSetting(AppSettingsEnum.tilesPerRow),
+              showStorageIndicator: appSettingService.getSetting(AppSettingsEnum.storageIndicator),
             ),
           );
 

+ 22 - 54
mobile/lib/modules/settings/services/app_settings.service.dart

@@ -1,11 +1,16 @@
-import 'package:flutter/material.dart';
 import 'package:hive_flutter/hive_flutter.dart';
-import 'package:hooks_riverpod/hooks_riverpod.dart';
 import 'package:immich_mobile/constants/hive_box.dart';
 
-enum AppSettingsEnum {
-  threeStageLoading, // true, false,
-  themeMode, // "light","dark","system"
+enum AppSettingsEnum<T> {
+  threeStageLoading<bool>("threeStageLoading", false),
+  themeMode<String>("themeMode", "system"), // "light","dark","system"
+  tilesPerRow<int>("tilesPerRow", 4),
+  storageIndicator<bool>("storageIndicator", true);
+
+  const AppSettingsEnum(this.hiveKey, this.defaultValue);
+
+  final String hiveKey;
+  final T defaultValue;
 }
 
 class AppSettingsService {
@@ -15,63 +20,26 @@ class AppSettingsService {
     hiveBox = Hive.box(userSettingInfoBox);
   }
 
-  T getSetting<T>(AppSettingsEnum settingType) {
-    var settingKey = _settingHiveBoxKeyLookup(settingType);
-
-    if (!hiveBox.containsKey(settingKey)) {
-      T defaultSetting = _setDefaultSetting(settingType);
-      return defaultSetting;
+  T getSetting<T>(AppSettingsEnum<T> settingType) {
+    if (!hiveBox.containsKey(settingType.hiveKey)) {
+      return _setDefault(settingType);
     }
 
-    var result = hiveBox.get(settingKey);
+    var result = hiveBox.get(settingType.hiveKey);
 
-    if (result is T) {
-      return result;
-    } else {
-      debugPrint("Incorrect setting type");
-      throw TypeError();
+    if (result is! T) {
+      return _setDefault(settingType);
     }
-  }
-
-  setSetting<T>(AppSettingsEnum settingType, T value) {
-    var settingKey = _settingHiveBoxKeyLookup(settingType);
-
-    if (hiveBox.containsKey(settingKey)) {
-      var result = hiveBox.get(settingKey);
-
-      if (result is! T) {
-        debugPrint("Incorrect setting type");
-        throw TypeError();
-      }
 
-      hiveBox.put(settingKey, value);
-    } else {
-      hiveBox.put(settingKey, value);
-    }
+    return result;
   }
 
-  _setDefaultSetting(AppSettingsEnum settingType) {
-    var settingKey = _settingHiveBoxKeyLookup(settingType);
-
-    // Default value of threeStageLoading is false
-    if (settingType == AppSettingsEnum.threeStageLoading) {
-      hiveBox.put(settingKey, false);
-      return false;
-    }
-
-    // Default value of themeMode is "light"
-    if (settingType == AppSettingsEnum.themeMode) {
-      hiveBox.put(settingKey, "system");
-      return "system";
-    }
+  setSetting<T>(AppSettingsEnum<T> settingType, T value) {
+    hiveBox.put(settingType.hiveKey, value);
   }
 
-  String _settingHiveBoxKeyLookup(AppSettingsEnum settingType) {
-    switch (settingType) {
-      case AppSettingsEnum.threeStageLoading:
-        return 'threeStageLoading';
-      case AppSettingsEnum.themeMode:
-        return 'themeMode';
-    }
+  T _setDefault<T>(AppSettingsEnum<T> settingType) {
+    hiveBox.put(settingType.hiveKey, settingType.defaultValue);
+    return settingType.defaultValue;
   }
 }

+ 33 - 0
mobile/lib/modules/settings/ui/asset_list_settings/asset_list_settings.dart

@@ -0,0 +1,33 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:immich_mobile/modules/settings/ui/asset_list_settings/asset_list_storage_indicator.dart';
+import 'asset_list_tiles_per_row.dart';
+
+class AssetListSettings extends StatelessWidget {
+  const AssetListSettings({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return ExpansionTile(
+      textColor: Theme.of(context).primaryColor,
+      title: const Text(
+        'asset_list_settings_title',
+        style: TextStyle(
+          fontWeight: FontWeight.bold,
+        ),
+      ).tr(),
+      subtitle: const Text(
+        'asset_list_settings_subtitle',
+        style: TextStyle(
+          fontSize: 13,
+        ),
+      ).tr(),
+      children: const [
+        TilesPerRow(),
+        StorageIndicator(),
+      ],
+    );
+  }
+}

+ 48 - 0
mobile/lib/modules/settings/ui/asset_list_settings/asset_list_storage_indicator.dart

@@ -0,0 +1,48 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
+import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
+import 'package:immich_mobile/shared/providers/asset.provider.dart';
+
+class StorageIndicator extends HookConsumerWidget {
+  const StorageIndicator({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final appSettingService = ref.watch(appSettingsServiceProvider);
+
+    final showStorageIndicator = useState(true);
+
+    void switchChanged(bool value) {
+      appSettingService.setSetting(AppSettingsEnum.storageIndicator, value);
+      showStorageIndicator.value = value;
+
+      ref.invalidate(assetGroupByDateTimeProvider);
+    }
+
+    useEffect(
+      () {
+        showStorageIndicator.value = appSettingService.getSetting<bool>(AppSettingsEnum.storageIndicator);
+
+        return null;
+      },
+      [],
+    );
+
+    return SwitchListTile.adaptive(
+      activeColor: Theme.of(context).primaryColor,
+      title: const Text(
+        "theme_setting_asset_list_storage_indicator_title",
+        style: TextStyle(
+          fontSize: 12,
+        ),
+      ).tr(),
+      onChanged: switchChanged,
+      value: showStorageIndicator.value,
+    );
+  }
+}

+ 63 - 0
mobile/lib/modules/settings/ui/asset_list_settings/asset_list_tiles_per_row.dart

@@ -0,0 +1,63 @@
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
+import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
+import 'package:immich_mobile/shared/providers/asset.provider.dart';
+
+class TilesPerRow extends HookConsumerWidget {
+  const TilesPerRow({
+    Key? key,
+  }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final appSettingService = ref.watch(appSettingsServiceProvider);
+
+    final itemsValue = useState(4.0);
+
+    void sliderChanged(double value) {
+      appSettingService.setSetting(AppSettingsEnum.tilesPerRow, value.toInt());
+      itemsValue.value = value;
+    }
+
+    void sliderChangedEnd(double _) {
+      ref.invalidate(assetGroupByDateTimeProvider);
+    }
+
+    useEffect(
+      () {
+        int tilesPerRow =
+            appSettingService.getSetting(AppSettingsEnum.tilesPerRow);
+        itemsValue.value = tilesPerRow.toDouble();
+        return null;
+      },
+      [],
+    );
+
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        ListTile(
+          title: const Text(
+            "theme_setting_asset_list_tiles_per_row_title",
+            style: TextStyle(
+              fontSize: 12,
+              fontWeight: FontWeight.bold,
+            ),
+          ).tr(args: ["${itemsValue.value.toInt()}"]),
+        ),
+        Slider(
+          onChangeEnd: sliderChangedEnd,
+          onChanged: sliderChanged,
+          value: itemsValue.value,
+          min: 2,
+          max: 6,
+          divisions: 4,
+          label: "${itemsValue.value.toInt()}",
+        ),
+      ],
+    );
+  }
+}

+ 1 - 0
mobile/lib/modules/settings/ui/image_viewer_quality_setting/three_stage_loading.dart

@@ -36,6 +36,7 @@ class ThreeStageLoading extends HookConsumerWidget {
     }
 
     return SwitchListTile.adaptive(
+      activeColor: Theme.of(context).primaryColor,
       title: const Text(
         "theme_setting_three_stage_loading_title",
         style: TextStyle(

+ 2 - 0
mobile/lib/modules/settings/views/settings_page.dart

@@ -1,6 +1,7 @@
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
 import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:immich_mobile/modules/settings/ui/asset_list_settings/asset_list_settings.dart';
 import 'package:immich_mobile/modules/settings/ui/image_viewer_quality_setting/image_viewer_quality_setting.dart';
 import 'package:immich_mobile/modules/settings/ui/theme_setting/theme_setting.dart';
 
@@ -36,6 +37,7 @@ class SettingsPage extends HookConsumerWidget {
             tiles: [
               const ImageViewerQualitySetting(),
               const ThemeSetting(),
+              const AssetListSettings()
             ],
           ).toList(),
         ],