mirror of
https://github.com/immich-app/immich.git
synced 2024-12-29 15:11:58 +00:00
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
This commit is contained in:
parent
07b58f46f9
commit
013a0f8324
13 changed files with 209 additions and 64 deletions
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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 ({})"
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
],
|
||||
|
|
|
@ -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,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(
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
setSetting<T>(AppSettingsEnum<T> 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<T>(AppSettingsEnum<T> settingType) {
|
||||
hiveBox.put(settingType.hiveKey, settingType.defaultValue);
|
||||
return settingType.defaultValue;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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()}",
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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(),
|
||||
],
|
||||
|
|
Loading…
Reference in a new issue