From 013a0f8324c90aadacfd201fa44bc34b986e005e Mon Sep 17 00:00:00 2001 From: Matthias Rupp Date: Sat, 20 Aug 2022 23:19:40 +0200 Subject: [PATCH] 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 --- mobile/assets/i18n/de-DE.json | 2 +- mobile/assets/i18n/en-US.json | 6 +- .../album/ui/album_viewer_thumbnail.dart | 4 +- .../album/views/album_viewer_page.dart | 12 ++- mobile/lib/modules/home/ui/image_grid.dart | 9 ++- .../lib/modules/home/ui/thumbnail_image.dart | 9 ++- mobile/lib/modules/home/views/home_page.dart | 6 ++ .../services/app_settings.service.dart | 78 ++++++------------- .../asset_list_settings.dart | 33 ++++++++ .../asset_list_storage_indicator.dart | 48 ++++++++++++ .../asset_list_tiles_per_row.dart | 63 +++++++++++++++ .../three_stage_loading.dart | 1 + .../modules/settings/views/settings_page.dart | 2 + 13 files changed, 209 insertions(+), 64 deletions(-) create mode 100644 mobile/lib/modules/settings/ui/asset_list_settings/asset_list_settings.dart create mode 100644 mobile/lib/modules/settings/ui/asset_list_settings/asset_list_storage_indicator.dart create mode 100644 mobile/lib/modules/settings/ui/asset_list_settings/asset_list_tiles_per_row.dart diff --git a/mobile/assets/i18n/de-DE.json b/mobile/assets/i18n/de-DE.json index a8298e3e94..86ff256307 100644 --- a/mobile/assets/i18n/de-DE.json +++ b/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" } diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index 2147ef0410..54dfc2dbe5 100644 --- a/mobile/assets/i18n/en-US.json +++ b/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 ({})" } diff --git a/mobile/lib/modules/album/ui/album_viewer_thumbnail.dart b/mobile/lib/modules/album/ui/album_viewer_thumbnail.dart index c5441fe9d7..fba82cfb26 100644 --- a/mobile/lib/modules/album/ui/album_viewer_thumbnail.dart +++ b/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 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(), ], diff --git a/mobile/lib/modules/album/views/album_viewer_page.dart b/mobile/lib/modules/album/views/album_viewer_page.dart index 237635dd18..c525c9922b 100644 --- a/mobile/lib/modules/album/views/album_viewer_page.dart +++ b/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, diff --git a/mobile/lib/modules/home/ui/image_grid.dart b/mobile/lib/modules/home/ui/image_grid.dart index e3c31995a4..30ad9b3938 100644 --- a/mobile/lib/modules/home/ui/image_grid.dart +++ b/mobile/lib/modules/home/ui/image_grid.dart @@ -7,11 +7,15 @@ import 'package:openapi/api.dart'; class ImageGrid extends ConsumerWidget { final List assetGroup; final List 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 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( diff --git a/mobile/lib/modules/home/ui/thumbnail_image.dart b/mobile/lib/modules/home/ui/thumbnail_image.dart index 683ec2d470..025fafef42 100644 --- a/mobile/lib/modules/home/ui/thumbnail_image.dart +++ b/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 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( diff --git a/mobile/lib/modules/home/views/home_page.dart b/mobile/lib/modules/home/views/home_page.dart index e532bf7e5a..6c9362c13e 100644 --- a/mobile/lib/modules/home/views/home_page.dart +++ b/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 imageGridGroup = []; @@ -86,6 +90,8 @@ class HomePage extends HookConsumerWidget { ImageGrid( assetGroup: immichAssetList, sortedAssetGroup: sortedAssetList, + tilesPerRow: appSettingService.getSetting(AppSettingsEnum.tilesPerRow), + showStorageIndicator: appSettingService.getSetting(AppSettingsEnum.storageIndicator), ), ); diff --git a/mobile/lib/modules/settings/services/app_settings.service.dart b/mobile/lib/modules/settings/services/app_settings.service.dart index 4324c32c1b..38f32822b6 100644 --- a/mobile/lib/modules/settings/services/app_settings.service.dart +++ b/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 { + threeStageLoading("threeStageLoading", false), + themeMode("themeMode", "system"), // "light","dark","system" + tilesPerRow("tilesPerRow", 4), + storageIndicator("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(AppSettingsEnum settingType) { - var settingKey = _settingHiveBoxKeyLookup(settingType); - - if (!hiveBox.containsKey(settingKey)) { - T defaultSetting = _setDefaultSetting(settingType); - return defaultSetting; + T getSetting(AppSettingsEnum 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); } + + return result; } - setSetting(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); - } + setSetting(AppSettingsEnum settingType, T value) { + hiveBox.put(settingType.hiveKey, value); } - _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"; - } - } - - String _settingHiveBoxKeyLookup(AppSettingsEnum settingType) { - switch (settingType) { - case AppSettingsEnum.threeStageLoading: - return 'threeStageLoading'; - case AppSettingsEnum.themeMode: - return 'themeMode'; - } + T _setDefault(AppSettingsEnum settingType) { + hiveBox.put(settingType.hiveKey, settingType.defaultValue); + return settingType.defaultValue; } } diff --git a/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_settings.dart b/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_settings.dart new file mode 100644 index 0000000000..350022fcbf --- /dev/null +++ b/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(), + ], + ); + } +} diff --git a/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_storage_indicator.dart b/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_storage_indicator.dart new file mode 100644 index 0000000000..c59c4de1da --- /dev/null +++ b/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(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, + ); + } +} diff --git a/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_tiles_per_row.dart b/mobile/lib/modules/settings/ui/asset_list_settings/asset_list_tiles_per_row.dart new file mode 100644 index 0000000000..734bbf30cc --- /dev/null +++ b/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()}", + ), + ], + ); + } +} diff --git a/mobile/lib/modules/settings/ui/image_viewer_quality_setting/three_stage_loading.dart b/mobile/lib/modules/settings/ui/image_viewer_quality_setting/three_stage_loading.dart index 24169a7fb8..f10e663add 100644 --- a/mobile/lib/modules/settings/ui/image_viewer_quality_setting/three_stage_loading.dart +++ b/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( diff --git a/mobile/lib/modules/settings/views/settings_page.dart b/mobile/lib/modules/settings/views/settings_page.dart index 5347999e51..84563a2f76 100644 --- a/mobile/lib/modules/settings/views/settings_page.dart +++ b/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(), ],