chore: mobile font tuning (#25349)

* chore: mobile font tuning

* chore: fix some paddings

* setting page tune

* chore: album sort dropdown button styling

* pr feedback

* tweak sync status card

* chore: refactor
This commit is contained in:
Alex 2026-01-19 14:56:35 -06:00 committed by GitHub
parent 4ef699e9fa
commit fe1d0edf4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 350 additions and 343 deletions

View file

@ -603,7 +603,7 @@
"backup_album_selection_page_select_albums": "Select albums", "backup_album_selection_page_select_albums": "Select albums",
"backup_album_selection_page_selection_info": "Selection Info", "backup_album_selection_page_selection_info": "Selection Info",
"backup_album_selection_page_total_assets": "Total unique assets", "backup_album_selection_page_total_assets": "Total unique assets",
"backup_albums_sync": "Backup albums synchronization", "backup_albums_sync": "Backup Albums Synchronization",
"backup_all": "All", "backup_all": "All",
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…", "backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
"backup_background_service_complete_notification": "Asset backup complete", "backup_background_service_complete_notification": "Asset backup complete",
@ -2257,7 +2257,7 @@
"url": "URL", "url": "URL",
"usage": "Usage", "usage": "Usage",
"use_biometric": "Use biometric", "use_biometric": "Use biometric",
"use_current_connection": "use current connection", "use_current_connection": "Use current connection",
"use_custom_date_range": "Use custom date range instead", "use_custom_date_range": "Use custom date range instead",
"user": "User", "user": "User",
"user_has_been_deleted": "This user has been deleted.", "user_has_been_deleted": "This user has been deleted.",

View file

@ -92,7 +92,7 @@ class _MobileLayout extends StatelessWidget {
], ],
) )
.toList(); .toList();
return ListView(padding: const EdgeInsets.only(top: 10.0, bottom: 16), children: [...settings]); return ListView(padding: const EdgeInsets.only(top: 10.0, bottom: 60), children: [...settings]);
} }
} }
@ -142,7 +142,7 @@ class SettingsSubPage extends StatelessWidget {
context.locale; context.locale;
return Scaffold( return Scaffold(
appBar: AppBar(centerTitle: false, title: Text(section.title).tr()), appBar: AppBar(centerTitle: false, title: Text(section.title).tr()),
body: section.widget, body: Padding(padding: const EdgeInsets.only(bottom: 60.0), child: section.widget),
); );
} }
} }

View file

@ -18,6 +18,7 @@ class SyncStatusPage extends StatelessWidget {
splashRadius: 24, splashRadius: 24,
icon: const Icon(Icons.arrow_back_ios_rounded), icon: const Icon(Icons.arrow_back_ios_rounded),
), ),
centerTitle: false,
), ),
body: const SyncStatusAndActions(), body: const SyncStatusAndActions(),
); );

View file

@ -311,18 +311,17 @@ class _SortButtonState extends ConsumerState<_SortButton> {
: const Icon(Icons.abc, color: Colors.transparent), : const Icon(Icons.abc, color: Colors.transparent),
onPressed: () => onMenuTapped(sortMode), onPressed: () => onMenuTapped(sortMode),
style: ButtonStyle( style: ButtonStyle(
padding: WidgetStateProperty.all(const EdgeInsets.fromLTRB(16, 16, 32, 16)), padding: WidgetStateProperty.all(const EdgeInsets.fromLTRB(12, 12, 24, 12)),
backgroundColor: WidgetStateProperty.all( backgroundColor: WidgetStateProperty.all(
albumSortOption == sortMode ? context.colorScheme.primary : Colors.transparent, albumSortOption == sortMode ? context.colorScheme.primary : Colors.transparent,
), ),
shape: WidgetStateProperty.all( shape: WidgetStateProperty.all(
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(24))), const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
), ),
), ),
child: Text( child: Text(
sortMode.label.t(context: context), sortMode.label.t(context: context),
style: context.textTheme.titleSmall?.copyWith( style: context.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w600,
color: albumSortOption == sortMode color: albumSortOption == sortMode
? context.colorScheme.onPrimary ? context.colorScheme.onPrimary
: context.colorScheme.onSurface.withAlpha(185), : context.colorScheme.onSurface.withAlpha(185),
@ -350,10 +349,7 @@ class _SortButtonState extends ConsumerState<_SortButton> {
), ),
Text( Text(
albumSortOption.label.t(context: context), albumSortOption.label.t(context: context),
style: context.textTheme.bodyLarge?.copyWith( style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurface.withAlpha(225)),
fontWeight: FontWeight.w500,
color: context.colorScheme.onSurface.withAlpha(225),
),
), ),
isSorting isSorting
? SizedBox( ? SizedBox(

View file

@ -10,6 +10,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/duration_extensions.dart'; import 'package:immich_mobile/extensions/duration_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/album/album_tile.dart'; import 'package:immich_mobile/presentation/widgets/album/album_tile.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
@ -164,11 +165,8 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
children: [ children: [
if (albums.isNotEmpty) if (albums.isNotEmpty)
SheetTile( SheetTile(
title: 'appears_in'.t(context: context).toUpperCase(), title: 'appears_in'.t(context: context),
titleStyle: context.textTheme.labelMedium?.copyWith( titleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
color: context.textTheme.labelMedium?.color?.withAlpha(200),
fontWeight: FontWeight.w600,
),
), ),
Padding( Padding(
padding: const EdgeInsets.only(left: 24), padding: const EdgeInsets.only(left: 24),
@ -224,9 +222,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
color: context.textTheme.labelLarge?.color, color: context.textTheme.labelLarge?.color,
), ),
subtitle: _getFileInfo(asset, exifInfo), subtitle: _getFileInfo(asset, exifInfo),
subtitleStyle: context.textTheme.labelMedium?.copyWith( subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
color: context.textTheme.labelMedium?.color?.withAlpha(200),
),
); );
}, },
); );
@ -241,9 +237,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
color: context.textTheme.labelLarge?.color, color: context.textTheme.labelLarge?.color,
), ),
subtitle: _getFileInfo(asset, exifInfo), subtitle: _getFileInfo(asset, exifInfo),
subtitleStyle: context.textTheme.labelMedium?.copyWith( subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
color: context.textTheme.labelMedium?.color?.withAlpha(200),
),
); );
} }
} }
@ -262,11 +256,8 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
const SheetLocationDetails(), const SheetLocationDetails(),
// Details header // Details header
SheetTile( SheetTile(
title: 'details'.t(context: context).toUpperCase(), title: 'details'.t(context: context),
titleStyle: context.textTheme.labelMedium?.copyWith( titleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
color: context.textTheme.labelMedium?.color?.withAlpha(200),
fontWeight: FontWeight.w600,
),
), ),
// File info // File info
buildFileInfoTile(), buildFileInfoTile(),
@ -278,9 +269,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
titleStyle: context.textTheme.labelLarge, titleStyle: context.textTheme.labelLarge,
leading: Icon(Icons.camera_alt_outlined, size: 24, color: context.textTheme.labelLarge?.color), leading: Icon(Icons.camera_alt_outlined, size: 24, color: context.textTheme.labelLarge?.color),
subtitle: _getCameraInfoSubtitle(exifInfo), subtitle: _getCameraInfoSubtitle(exifInfo),
subtitleStyle: context.textTheme.labelMedium?.copyWith( subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
color: context.textTheme.labelMedium?.color?.withAlpha(200),
),
), ),
], ],
// Lens info // Lens info
@ -291,15 +280,13 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
titleStyle: context.textTheme.labelLarge, titleStyle: context.textTheme.labelLarge,
leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color), leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color),
subtitle: _getLensInfoSubtitle(exifInfo), subtitle: _getLensInfoSubtitle(exifInfo),
subtitleStyle: context.textTheme.labelMedium?.copyWith( subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
color: context.textTheme.labelMedium?.color?.withAlpha(200),
),
), ),
], ],
// Appears in (Albums) // Appears in (Albums)
Padding(padding: const EdgeInsets.only(top: 16.0), child: _buildAppearsInList(ref, context)), Padding(padding: const EdgeInsets.only(top: 16.0), child: _buildAppearsInList(ref, context)),
// padding at the bottom to avoid cut-off // padding at the bottom to avoid cut-off
const SizedBox(height: 30), const SizedBox(height: 60),
], ],
); );
} }

View file

@ -4,6 +4,7 @@ import 'package:immich_mobile/constants/enums.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/exif.model.dart'; import 'package:immich_mobile/domain/models/exif.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/asset_viewer/sheet_tile.widget.dart'; import 'package:immich_mobile/presentation/widgets/asset_viewer/sheet_tile.widget.dart';
import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
@ -77,11 +78,8 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SheetTile( SheetTile(
title: 'location'.t(context: context).toUpperCase(), title: 'location'.t(context: context),
titleStyle: context.textTheme.labelMedium?.copyWith( titleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
color: context.textTheme.labelMedium?.color?.withAlpha(200),
fontWeight: FontWeight.w600,
),
trailing: hasCoordinates ? const Icon(Icons.edit_location_alt, size: 20) : null, trailing: hasCoordinates ? const Icon(Icons.edit_location_alt, size: 20) : null,
onTap: editLocation, onTap: editLocation,
), ),
@ -105,9 +103,7 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
), ),
Text( Text(
coordinates, coordinates,
style: context.textTheme.labelMedium?.copyWith( style: context.textTheme.bodySmall?.copyWith(color: context.colorScheme.onSurfaceSecondary),
color: context.textTheme.labelMedium?.color?.withAlpha(200),
),
), ),
], ],
), ),

View file

@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/models/person.model.dart'; import 'package:immich_mobile/domain/models/person.model.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/people/person_edit_name_modal.widget.dart'; import 'package:immich_mobile/presentation/widgets/people/person_edit_name_modal.widget.dart';
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
@ -53,11 +54,8 @@ class _SheetPeopleDetailsState extends ConsumerState<SheetPeopleDetails> {
Padding( Padding(
padding: const EdgeInsets.only(left: 16, top: 16, bottom: 16), padding: const EdgeInsets.only(left: 16, top: 16, bottom: 16),
child: Text( child: Text(
"people".t(context: context).toUpperCase(), "people".t(context: context),
style: context.textTheme.labelMedium?.copyWith( style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
color: context.textTheme.labelMedium?.color?.withAlpha(200),
fontWeight: FontWeight.w600,
),
), ),
), ),
SizedBox( SizedBox(

View file

@ -61,7 +61,12 @@ ThemeData getThemeData({required ColorScheme colorScheme, required Locale locale
), ),
), ),
chipTheme: const ChipThemeData(side: BorderSide.none), chipTheme: const ChipThemeData(side: BorderSide.none),
sliderTheme: const SliderThemeData(thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7), trackHeight: 2.0), sliderTheme: const SliderThemeData(
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
trackHeight: 2.0,
// ignore: deprecated_member_use
year2023: false,
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(type: BottomNavigationBarType.fixed), bottomNavigationBarTheme: const BottomNavigationBarThemeData(type: BottomNavigationBarType.fixed),
popupMenuTheme: const PopupMenuThemeData( popupMenuTheme: const PopupMenuThemeData(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))), shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(10))),

View file

@ -1,14 +1,14 @@
import 'dart:async'; import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
class GroupSettings extends HookConsumerWidget { class GroupSettings extends HookConsumerWidget {
const GroupSettings({super.key}); const GroupSettings({super.key});
@ -33,12 +33,24 @@ class GroupSettings extends HookConsumerWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SettingsSubTitle(title: "asset_list_group_by_sub_title".tr()), SettingGroupTitle(
title: "asset_list_group_by_sub_title".t(context: context),
icon: Icons.group_work_outlined,
),
SettingsRadioListTile( SettingsRadioListTile(
groups: [ groups: [
SettingsRadioGroup(title: 'asset_list_layout_settings_group_by_month_day'.tr(), value: GroupAssetsBy.day), SettingsRadioGroup(
SettingsRadioGroup(title: 'month'.tr(), value: GroupAssetsBy.month), title: 'asset_list_layout_settings_group_by_month_day'.t(context: context),
SettingsRadioGroup(title: 'asset_list_layout_settings_group_automatically'.tr(), value: GroupAssetsBy.auto), value: GroupAssetsBy.day,
),
SettingsRadioGroup(
title: 'month'.t(context: context),
value: GroupAssetsBy.month,
),
SettingsRadioGroup(
title: 'asset_list_layout_settings_group_automatically'.t(context: context),
value: GroupAssetsBy.auto,
),
], ],
groupBy: groupBy, groupBy: groupBy,
onRadioChanged: changeGroupValue, onRadioChanged: changeGroupValue,

View file

@ -1,11 +1,12 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
class LayoutSettings extends HookConsumerWidget { class LayoutSettings extends HookConsumerWidget {
@ -19,10 +20,13 @@ class LayoutSettings extends HookConsumerWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SettingsSubTitle(title: "asset_list_layout_sub_title".tr()), SettingGroupTitle(
title: "asset_list_layout_sub_title".t(context: context),
icon: Icons.view_module_outlined,
),
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: useDynamicLayout, valueNotifier: useDynamicLayout,
title: "asset_list_layout_settings_dynamic_layout_title".tr(), title: "asset_list_layout_settings_dynamic_layout_title".t(context: context),
onChanged: (_) => ref.invalidate(appSettingsServiceProvider), onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
), ),
SettingsSliderListTile( SettingsSliderListTile(

View file

@ -1,10 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
@ -19,21 +18,21 @@ class ImageViewerQualitySetting extends HookConsumerWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SettingsSubTitle(title: "setting_image_viewer_title".tr()), SettingGroupTitle(
ListTile( title: "photos".t(context: context),
contentPadding: const EdgeInsets.symmetric(horizontal: 20), icon: Icons.image_outlined,
title: Text('setting_image_viewer_help', style: context.textTheme.bodyMedium).tr(), subtitle: "setting_image_viewer_help".t(context: context),
), ),
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: isPreview, valueNotifier: isPreview,
title: "setting_image_viewer_preview_title".tr(), title: "setting_image_viewer_preview_title".t(context: context),
subtitle: "setting_image_viewer_preview_subtitle".tr(), subtitle: "setting_image_viewer_preview_subtitle".t(context: context),
onChanged: (_) => ref.invalidate(appSettingsServiceProvider), onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
), ),
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: isOriginal, valueNotifier: isOriginal,
title: "setting_image_viewer_original_title".tr(), title: "setting_image_viewer_original_title".t(context: context),
subtitle: "setting_image_viewer_original_subtitle".tr(), subtitle: "setting_image_viewer_original_subtitle".t(context: context),
onChanged: (_) => ref.invalidate(appSettingsServiceProvider), onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
), ),
], ],

View file

@ -1,9 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
@ -19,23 +19,26 @@ class VideoViewerSettings extends HookConsumerWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SettingsSubTitle(title: "videos".tr()), SettingGroupTitle(
title: "videos".t(context: context),
icon: Icons.video_camera_back_outlined,
),
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: useAutoPlayVideo, valueNotifier: useAutoPlayVideo,
title: "setting_video_viewer_auto_play_title".tr(), title: "setting_video_viewer_auto_play_title".t(context: context),
subtitle: "setting_video_viewer_auto_play_subtitle".tr(), subtitle: "setting_video_viewer_auto_play_subtitle".t(context: context),
onChanged: (_) => ref.invalidate(appSettingsServiceProvider), onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
), ),
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: useLoopVideo, valueNotifier: useLoopVideo,
title: "setting_video_viewer_looping_title".tr(), title: "setting_video_viewer_looping_title".t(context: context),
subtitle: "loop_videos_description".tr(), subtitle: "loop_videos_description".t(context: context),
onChanged: (_) => ref.invalidate(appSettingsServiceProvider), onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
), ),
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: useOriginalVideo, valueNotifier: useOriginalVideo,
title: "setting_video_viewer_original_video_title".tr(), title: "setting_video_viewer_original_video_title".t(context: context),
subtitle: "setting_video_viewer_original_video_subtitle".tr(), subtitle: "setting_video_viewer_original_video_subtitle".t(context: context),
onChanged: (_) => ref.invalidate(appSettingsServiceProvider), onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
), ),
], ],

View file

@ -16,6 +16,8 @@ import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/setting_list_tile.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
class DriftBackupSettings extends ConsumerWidget { class DriftBackupSettings extends ConsumerWidget {
@ -25,36 +27,25 @@ class DriftBackupSettings extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return SettingsSubPageScaffold( return SettingsSubPageScaffold(
settings: [ settings: [
Padding( SettingGroupTitle(
padding: const EdgeInsets.only(left: 16.0), title: "network_requirements".t(context: context),
child: Text( icon: Icons.cell_tower,
"network_requirements".t(context: context).toUpperCase(),
style: context.textTheme.labelSmall?.copyWith(color: context.colorScheme.onSurface.withValues(alpha: 0.7)),
),
), ),
const _UseWifiForUploadVideosButton(), const _UseWifiForUploadVideosButton(),
const _UseWifiForUploadPhotosButton(), const _UseWifiForUploadPhotosButton(),
if (CurrentPlatform.isAndroid) ...[ if (CurrentPlatform.isAndroid) ...[
const Divider(), const Divider(),
Padding( SettingGroupTitle(
padding: const EdgeInsets.only(left: 16.0), title: "background_options".t(context: context),
child: Text( icon: Icons.charging_station_rounded,
"background_options".t(context: context).toUpperCase(),
style: context.textTheme.labelSmall?.copyWith(
color: context.colorScheme.onSurface.withValues(alpha: 0.7),
),
),
), ),
const _BackupOnlyWhenChargingButton(), const _BackupOnlyWhenChargingButton(),
const _BackupDelaySlider(), const _BackupDelaySlider(),
], ],
const Divider(), const Divider(),
Padding( SettingGroupTitle(
padding: const EdgeInsets.only(left: 16.0), title: "backup_albums_sync".t(context: context),
child: Text( icon: Icons.sync,
"backup_albums_sync".t(context: context).toUpperCase(),
style: context.textTheme.labelSmall?.copyWith(color: context.colorScheme.onSurface.withValues(alpha: 0.7)),
),
), ),
const _AlbumSyncActionButton(), const _AlbumSyncActionButton(),
], ],
@ -105,7 +96,9 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView( return Padding(
padding: const EdgeInsets.only(left: 8.0),
child: ListView(
shrinkWrap: true, shrinkWrap: true,
children: [ children: [
StreamBuilder( StreamBuilder(
@ -115,15 +108,9 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
final albumSyncEnable = snapshot.data ?? false; final albumSyncEnable = snapshot.data ?? false;
return Column( return Column(
children: [ children: [
ListTile( SettingListTile(
title: Text( title: "sync_albums".t(context: context),
"sync_albums".t(context: context), subtitle: "sync_upload_album_setting_subtitle".t(context: context),
style: context.textTheme.titleMedium?.copyWith(color: context.primaryColor),
),
subtitle: Text(
"sync_upload_album_setting_subtitle".t(context: context),
style: context.textTheme.labelLarge,
),
trailing: Switch( trailing: Switch(
value: albumSyncEnable, value: albumSyncEnable,
onChanged: (bool newValue) async { onChanged: (bool newValue) async {
@ -142,22 +129,11 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 200),
opacity: albumSyncEnable ? 1.0 : 0.0, opacity: albumSyncEnable ? 1.0 : 0.0,
child: albumSyncEnable child: albumSyncEnable
? ListTile( ? SettingListTile(
onTap: _manualSyncAlbums, onTap: _manualSyncAlbums,
contentPadding: const EdgeInsets.only(left: 32, right: 16), contentPadding: const EdgeInsets.only(left: 32, right: 16),
title: Text( title: "organize_into_albums".t(context: context),
"organize_into_albums".t(context: context), subtitle: "organize_into_albums_description".t(context: context),
style: context.textTheme.titleSmall?.copyWith(
color: context.colorScheme.onSurface,
fontWeight: FontWeight.normal,
),
),
subtitle: Text(
"organize_into_albums_description".t(context: context),
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurface.withValues(alpha: 0.7),
),
),
trailing: isAlbumSyncInProgress trailing: isAlbumSyncInProgress
? const SizedBox( ? const SizedBox(
width: 32, width: 32,
@ -180,6 +156,7 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
}, },
), ),
], ],
),
); );
} }
} }
@ -222,12 +199,11 @@ class _SettingsSwitchTileState extends ConsumerState<_SettingsSwitchTile> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListTile( return Padding(
title: Text( padding: const EdgeInsets.only(left: 8.0),
widget.titleKey.t(context: context), child: SettingListTile(
style: context.textTheme.titleMedium?.copyWith(color: context.primaryColor), title: widget.titleKey.t(context: context),
), subtitle: widget.subtitleKey.t(context: context),
subtitle: Text(widget.subtitleKey.t(context: context), style: context.textTheme.labelLarge),
trailing: StreamBuilder( trailing: StreamBuilder(
stream: valueStream, stream: valueStream,
initialData: Store.tryGet(widget.appSettingsEnum.storeKey) ?? widget.appSettingsEnum.defaultValue, initialData: Store.tryGet(widget.appSettingsEnum.storeKey) ?? widget.appSettingsEnum.defaultValue,
@ -241,6 +217,7 @@ class _SettingsSwitchTileState extends ConsumerState<_SettingsSwitchTile> {
); );
}, },
), ),
),
); );
} }
} }
@ -354,7 +331,7 @@ class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> {
'backup_controller_page_background_delay'.tr( 'backup_controller_page_background_delay'.tr(
namedArgs: {'duration': formatBackupDelaySliderValue(currentValue)}, namedArgs: {'duration': formatBackupDelaySliderValue(currentValue)},
), ),
style: context.textTheme.titleMedium?.copyWith(color: context.primaryColor), style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500),
), ),
), ),
Slider( Slider(

View file

@ -34,23 +34,25 @@ class EntityCountTile extends StatelessWidget {
children: [ children: [
// Icon and Label // Icon and Label
Row( Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Icon(icon, color: context.primaryColor), Icon(icon, color: context.primaryColor, size: 14),
const SizedBox(width: 8), const SizedBox(width: 4),
Flexible( Flexible(
child: Text( child: Text(
label, label,
style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 16), style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.w500),
), ),
), ),
], ],
), ),
// Number // Number
const Spacer(), const Spacer(),
RichText( Padding(
padding: const EdgeInsets.only(top: 8.0),
child: RichText(
text: TextSpan( text: TextSpan(
style: const TextStyle(fontSize: 18, fontFamily: 'GoogleSansCode', fontWeight: FontWeight.w600), style: const TextStyle(fontSize: 18, fontFamily: 'GoogleSansCode'),
children: [ children: [
TextSpan( TextSpan(
text: zeroPadding(count, maxDigits), text: zeroPadding(count, maxDigits),
@ -58,11 +60,12 @@ class EntityCountTile extends StatelessWidget {
), ),
TextSpan( TextSpan(
text: count.toString(), text: count.toString(),
style: TextStyle(color: context.primaryColor), style: TextStyle(color: context.colorScheme.onSurface),
), ),
], ],
), ),
), ),
),
], ],
), ),
); );

View file

@ -16,6 +16,8 @@ import 'package:immich_mobile/providers/infrastructure/trash_sync.provider.dart'
import 'package:immich_mobile/providers/sync_status.provider.dart'; import 'package:immich_mobile/providers/sync_status.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart'; import 'package:immich_mobile/widgets/settings/beta_sync_settings/entity_count_tile.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/setting_list_tile.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
@ -112,48 +114,39 @@ class SyncStatusAndActions extends HookConsumerWidget {
padding: const EdgeInsets.only(top: 16, bottom: 96), padding: const EdgeInsets.only(top: 16, bottom: 96),
children: [ children: [
const _SyncStatsCounts(), const _SyncStatsCounts(),
const Divider(height: 1, indent: 16, endIndent: 16), const Divider(height: 10),
const SizedBox(height: 24), const SizedBox(height: 16),
_SectionHeaderText(text: "jobs".t(context: context)), SettingGroupTitle(title: "jobs".t(context: context)),
ListTile( SettingListTile(
title: Text( title: "sync_local".t(context: context),
"sync_local".t(context: context), subtitle: "tap_to_run_job".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text("tap_to_run_job".t(context: context)),
leading: const Icon(Icons.sync), leading: const Icon(Icons.sync),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).localSyncStatus), trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).localSyncStatus),
onTap: () { onTap: () {
ref.read(backgroundSyncProvider).syncLocal(full: true); ref.read(backgroundSyncProvider).syncLocal(full: true);
}, },
), ),
ListTile( SettingListTile(
title: Text( title: "sync_remote".t(context: context),
"sync_remote".t(context: context), subtitle: "tap_to_run_job".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text("tap_to_run_job".t(context: context)),
leading: const Icon(Icons.cloud_sync), leading: const Icon(Icons.cloud_sync),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).remoteSyncStatus), trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).remoteSyncStatus),
onTap: () { onTap: () {
ref.read(backgroundSyncProvider).syncRemote(); ref.read(backgroundSyncProvider).syncRemote();
}, },
), ),
ListTile( SettingListTile(
title: Text( title: "hash_asset".t(context: context),
"hash_asset".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
leading: const Icon(Icons.tag), leading: const Icon(Icons.tag),
subtitle: Text("tap_to_run_job".t(context: context)), subtitle: "tap_to_run_job".t(context: context),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).hashJobStatus), trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).hashJobStatus),
onTap: () { onTap: () {
ref.read(backgroundSyncProvider).hashAssets(); ref.read(backgroundSyncProvider).hashAssets();
}, },
), ),
const Divider(height: 1, indent: 16, endIndent: 16), const Divider(height: 1),
const SizedBox(height: 24), const SizedBox(height: 16),
_SectionHeaderText(text: "actions".t(context: context)), SettingGroupTitle(title: "actions".t(context: context)),
ListTile( ListTile(
title: Text( title: Text(
"clear_file_cache".t(context: context), "clear_file_cache".t(context: context),
@ -202,26 +195,6 @@ class _SyncStatusIcon extends StatelessWidget {
} }
} }
class _SectionHeaderText extends StatelessWidget {
final String text;
const _SectionHeaderText({required this.text});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 16.0),
child: Text(
text.toUpperCase(),
style: context.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.w500,
color: context.colorScheme.onSurface.withAlpha(200),
),
),
);
}
}
class _SyncStatsCounts extends ConsumerWidget { class _SyncStatsCounts extends ConsumerWidget {
const _SyncStatsCounts(); const _SyncStatsCounts();
@ -279,9 +252,9 @@ class _SyncStatsCounts extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
_SectionHeaderText(text: "assets".t(context: context)), SettingGroupTitle(title: "assets".t(context: context)),
Padding( Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
// 1. Wrap in IntrinsicHeight // 1. Wrap in IntrinsicHeight
child: IntrinsicHeight( child: IntrinsicHeight(
child: Flex( child: Flex(
@ -309,9 +282,9 @@ class _SyncStatsCounts extends ConsumerWidget {
), ),
), ),
), ),
_SectionHeaderText(text: "albums".t(context: context)), SettingGroupTitle(title: "albums".t(context: context)),
Padding( Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: IntrinsicHeight( child: IntrinsicHeight(
child: Flex( child: Flex(
direction: Axis.horizontal, direction: Axis.horizontal,
@ -337,9 +310,9 @@ class _SyncStatsCounts extends ConsumerWidget {
), ),
), ),
), ),
_SectionHeaderText(text: "other".t(context: context)), SettingGroupTitle(title: "other".t(context: context)),
Padding( Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: IntrinsicHeight( child: IntrinsicHeight(
child: Flex( child: Flex(
direction: Axis.horizontal, direction: Axis.horizontal,
@ -368,7 +341,7 @@ class _SyncStatsCounts extends ConsumerWidget {
// To be removed once the experimental feature is stable // To be removed once the experimental feature is stable
if (CurrentPlatform.isAndroid && if (CurrentPlatform.isAndroid &&
appSettingsService.getSetting<bool>(AppSettingsEnum.manageLocalMediaAndroid)) ...[ appSettingsService.getSetting<bool>(AppSettingsEnum.manageLocalMediaAndroid)) ...[
_SectionHeaderText(text: "trash".t(context: context)), SettingGroupTitle(title: "trash".t(context: context)),
Consumer( Consumer(
builder: (context, ref, _) { builder: (context, ref, _) {
final counts = ref.watch(trashedAssetsCountProvider); final counts = ref.watch(trashedAssetsCountProvider);

View file

@ -9,6 +9,7 @@ import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/setting_list_tile.dart';
class BetaTimelineListTile extends ConsumerWidget { class BetaTimelineListTile extends ConsumerWidget {
const BetaTimelineListTile({super.key}); const BetaTimelineListTile({super.key});
@ -56,8 +57,8 @@ class BetaTimelineListTile extends ConsumerWidget {
return Padding( return Padding(
padding: const EdgeInsets.only(left: 4.0), padding: const EdgeInsets.only(left: 4.0),
child: ListTile( child: SettingListTile(
title: Text("new_timeline".t(context: context)), title: "new_timeline".t(context: context),
trailing: Switch.adaptive( trailing: Switch.adaptive(
value: betaTimelineValue, value: betaTimelineValue,
onChanged: onSwitchChanged, onChanged: onSwitchChanged,

View file

@ -142,7 +142,9 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
final state = ref.watch(cleanupProvider); final state = ref.watch(cleanupProvider);
final hasDate = state.selectedDate != null; final hasDate = state.selectedDate != null;
final hasAssets = _hasScanned && state.assetsToDelete.isNotEmpty; final hasAssets = _hasScanned && state.assetsToDelete.isNotEmpty;
final subtitleStyle = context.textTheme.bodyMedium!.copyWith(
color: context.textTheme.bodyMedium!.color!.withAlpha(215),
);
StepStyle styleForState(StepState stepState, {bool isDestructive = false}) { StepStyle styleForState(StepState stepState, {bool isDestructive = false}) {
switch (stepState) { switch (stepState) {
case StepState.complete: case StepState.complete:
@ -214,10 +216,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
borderRadius: const BorderRadius.all(Radius.circular(12)), borderRadius: const BorderRadius.all(Radius.circular(12)),
border: Border.all(color: context.primaryColor.withValues(alpha: 0.25)), border: Border.all(color: context.primaryColor.withValues(alpha: 0.25)),
), ),
child: Text( child: Text('free_up_space_description'.t(context: context), style: context.textTheme.bodyMedium),
'free_up_space_description'.t(context: context),
style: context.textTheme.labelLarge?.copyWith(fontSize: 15),
),
), ),
), ),
@ -256,7 +255,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text('cutoff_date_description'.t(context: context), style: context.textTheme.labelLarge), Text('cutoff_date_description'.t(context: context), style: subtitleStyle),
const SizedBox(height: 16), const SizedBox(height: 16),
GridView.count( GridView.count(
shrinkWrap: true, shrinkWrap: true,
@ -352,7 +351,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
content: Column( content: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
Text('cleanup_filter_description'.t(context: context), style: context.textTheme.labelLarge), Text('cleanup_filter_description'.t(context: context), style: subtitleStyle),
const SizedBox(height: 16), const SizedBox(height: 16),
SegmentedButton<AssetFilterType>( SegmentedButton<AssetFilterType>(
segments: [ segments: [
@ -381,10 +380,15 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
const SizedBox(height: 16), const SizedBox(height: 16),
SwitchListTile( SwitchListTile(
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: Text('keep_favorites'.t(context: context), style: context.textTheme.titleSmall), title: Text(
'keep_favorites'.t(context: context),
style: context.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w500, height: 1.5),
),
subtitle: Text( subtitle: Text(
'keep_favorites_description'.t(context: context), 'keep_favorites_description'.t(context: context),
style: context.textTheme.labelLarge, style: context.textTheme.bodyMedium!.copyWith(
color: context.textTheme.bodyMedium!.color!.withAlpha(215),
),
), ),
value: state.keepFavorites, value: state.keepFavorites,
onChanged: (value) { onChanged: (value) {
@ -435,10 +439,7 @@ class _FreeUpSpaceSettingsState extends ConsumerState<FreeUpSpaceSettings> {
: null, : null,
content: Column( content: Column(
children: [ children: [
Text( Text('cleanup_step3_description'.t(context: context), style: subtitleStyle),
'cleanup_step3_description'.t(context: context),
style: context.textTheme.labelLarge?.copyWith(fontSize: 15),
),
if (CurrentPlatform.isIOS) ...[ if (CurrentPlatform.isIOS) ...[
const SizedBox(height: 12), const SizedBox(height: 12),
Container( Container(

View file

@ -117,7 +117,7 @@ class EndpointInputState extends ConsumerState<EndpointInput> {
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
validator: validateUrl, validator: validateUrl,
keyboardType: TextInputType.url, keyboardType: TextInputType.url,
style: const TextStyle(fontFamily: 'GoogleSansCode', fontWeight: FontWeight.w600, fontSize: 14), style: const TextStyle(fontFamily: 'GoogleSansCode', fontSize: 14),
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'http(s)://immich.domain.com', hintText: 'http(s)://immich.domain.com',
contentPadding: const EdgeInsets.all(16), contentPadding: const EdgeInsets.all(16),

View file

@ -1,12 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart';
import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
import 'package:immich_mobile/widgets/settings/networking_settings/endpoint_input.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/endpoint_input.dart';
@ -103,7 +103,7 @@ class ExternalNetworkPreference extends HookConsumerWidget {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 24), padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 24),
child: Text("external_network_sheet_info".tr(), style: context.textTheme.bodyMedium), child: Text("external_network_sheet_info".t(context: context), style: context.textTheme.bodyMedium),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Divider(color: context.colorScheme.surfaceContainerHighest), Divider(color: context.colorScheme.surfaceContainerHighest),
@ -135,7 +135,7 @@ class ExternalNetworkPreference extends HookConsumerWidget {
height: 48, height: 48,
child: OutlinedButton.icon( child: OutlinedButton.icon(
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
label: Text('add_endpoint'.tr().toUpperCase()), label: Text('add_endpoint'.t(context: context)),
onPressed: enabled onPressed: enabled
? () { ? () {
entries.value = [ entries.value = [

View file

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/network.provider.dart'; import 'package:immich_mobile/providers/network.provider.dart';
@ -167,13 +168,12 @@ class LocalNetworkPreference extends HookConsumerWidget {
enabled: enabled, enabled: enabled,
contentPadding: const EdgeInsets.only(left: 24, right: 8), contentPadding: const EdgeInsets.only(left: 24, right: 8),
leading: const Icon(Icons.lan_rounded), leading: const Icon(Icons.lan_rounded),
title: Text("server_endpoint".tr()), title: Text("server_endpoint".t(context: context)),
subtitle: localEndpointText.value.isEmpty subtitle: localEndpointText.value.isEmpty
? const Text("http://local-ip:2283") ? const Text("http://local-ip:2283")
: Text( : Text(
localEndpointText.value, localEndpointText.value,
style: context.textTheme.labelLarge?.copyWith( style: context.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.bold,
color: enabled ? context.primaryColor : context.colorScheme.onSurface.withAlpha(100), color: enabled ? context.primaryColor : context.colorScheme.onSurface.withAlpha(100),
fontFamily: 'GoogleSansCode', fontFamily: 'GoogleSansCode',
), ),
@ -190,7 +190,7 @@ class LocalNetworkPreference extends HookConsumerWidget {
height: 48, height: 48,
child: OutlinedButton.icon( child: OutlinedButton.icon(
icon: const Icon(Icons.wifi_find_rounded), icon: const Icon(Icons.wifi_find_rounded),
label: Text('use_current_connection'.tr().toUpperCase()), label: Text('use_current_connection'.t(context: context)),
onPressed: enabled ? autofillCurrentNetwork : null, onPressed: enabled ? autofillCurrentNetwork : null,
), ),
), ),

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store; import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart'; import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
import 'package:immich_mobile/providers/network.provider.dart'; import 'package:immich_mobile/providers/network.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
@ -10,6 +11,7 @@ import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/utils/url_helper.dart'; import 'package:immich_mobile/utils/url_helper.dart';
import 'package:immich_mobile/widgets/settings/networking_settings/external_network_preference.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/external_network_preference.dart';
import 'package:immich_mobile/widgets/settings/networking_settings/local_network_preference.dart'; import 'package:immich_mobile/widgets/settings/networking_settings/local_network_preference.dart';
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
class NetworkingSettings extends HookConsumerWidget { class NetworkingSettings extends HookConsumerWidget {
@ -87,13 +89,11 @@ class NetworkingSettings extends HookConsumerWidget {
return ListView( return ListView(
padding: const EdgeInsets.only(bottom: 96), padding: const EdgeInsets.only(bottom: 96),
children: <Widget>[ children: <Widget>[
Padding( const SizedBox(height: 8),
padding: const EdgeInsets.only(top: 8, left: 16, bottom: 8), SettingGroupTitle(
child: NetworkPreferenceTitle( title: "current_server_address".t(context: context),
title: "current_server_address".tr().toUpperCase(),
icon: (currentEndpoint?.startsWith('https') ?? false) ? Icons.https_outlined : Icons.http_outlined, icon: (currentEndpoint?.startsWith('https') ?? false) ? Icons.https_outlined : Icons.http_outlined,
), ),
),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8), padding: const EdgeInsets.symmetric(horizontal: 8),
child: Card( child: Card(
@ -108,12 +108,7 @@ class NetworkingSettings extends HookConsumerWidget {
: const Icon(Icons.circle_outlined), : const Icon(Icons.circle_outlined),
title: Text( title: Text(
currentEndpoint ?? "--", currentEndpoint ?? "--",
style: TextStyle( style: TextStyle(fontSize: 14, fontFamily: 'GoogleSansCode', color: context.primaryColor),
fontSize: 16,
fontFamily: 'GoogleSansCode',
fontWeight: FontWeight.bold,
color: context.primaryColor,
),
), ),
), ),
), ),
@ -128,14 +123,16 @@ class NetworkingSettings extends HookConsumerWidget {
title: "automatic_endpoint_switching_title".tr(), title: "automatic_endpoint_switching_title".tr(),
subtitle: "automatic_endpoint_switching_subtitle".tr(), subtitle: "automatic_endpoint_switching_subtitle".tr(),
), ),
Padding( const SizedBox(height: 8),
padding: const EdgeInsets.only(top: 8, left: 16, bottom: 16), SettingGroupTitle(
child: NetworkPreferenceTitle(title: "local_network".tr().toUpperCase(), icon: Icons.home_outlined), title: "local_network".t(context: context),
icon: Icons.home_outlined,
), ),
LocalNetworkPreference(enabled: featureEnabled.value), LocalNetworkPreference(enabled: featureEnabled.value),
Padding( const SizedBox(height: 16),
padding: const EdgeInsets.only(top: 32, left: 16, bottom: 16), SettingGroupTitle(
child: NetworkPreferenceTitle(title: "external_network".tr().toUpperCase(), icon: Icons.dns_outlined), title: "external_network".t(context: context),
icon: Icons.dns_outlined,
), ),
ExternalNetworkPreference(enabled: featureEnabled.value), ExternalNetworkPreference(enabled: featureEnabled.value),
], ],
@ -143,30 +140,6 @@ class NetworkingSettings extends HookConsumerWidget {
} }
} }
class NetworkPreferenceTitle extends StatelessWidget {
const NetworkPreferenceTitle({super.key, required this.icon, required this.title});
final IconData icon;
final String title;
@override
Widget build(BuildContext context) {
return Row(
children: [
Icon(icon, color: context.colorScheme.onSurface.withAlpha(150)),
const SizedBox(width: 8),
Text(
title,
style: context.textTheme.displaySmall?.copyWith(
color: context.colorScheme.onSurface.withAlpha(200),
fontWeight: FontWeight.w500,
),
),
],
);
}
}
class NetworkStatusIcon extends StatelessWidget { class NetworkStatusIcon extends StatelessWidget {
const NetworkStatusIcon({super.key, required this.status, this.enabled = true}) : super(); const NetworkStatusIcon({super.key, required this.status, this.enabled = true}) : super();
@ -175,10 +148,10 @@ class NetworkStatusIcon extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnimatedSwitcher(duration: const Duration(milliseconds: 200), child: _buildIcon(context)); return AnimatedSwitcher(duration: const Duration(milliseconds: 200), child: buildIcon(context));
} }
Widget _buildIcon(BuildContext context) => switch (status) { Widget buildIcon(BuildContext context) => switch (status) {
AuxCheckStatus.loading => Padding( AuxCheckStatus.loading => Padding(
padding: const EdgeInsets.only(left: 4.0), padding: const EdgeInsets.only(left: 4.0),
child: SizedBox( child: SizedBox(

View file

@ -1,9 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
@ -22,10 +22,13 @@ class HapticSetting extends HookConsumerWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SettingsSubTitle(title: "haptic_feedback_title".tr()), SettingGroupTitle(
title: "haptic_feedback_title".t(context: context),
icon: Icons.vibration_outlined,
),
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: isHapticFeedbackEnabled, valueNotifier: isHapticFeedbackEnabled,
title: 'haptic_feedback_switch'.tr(), title: 'enabled'.t(context: context),
onChanged: onHapticFeedbackChange, onChanged: onHapticFeedbackChange,
), ),
], ],

View file

@ -1,12 +1,12 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/providers/theme.provider.dart'; import 'package:immich_mobile/providers/theme.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/widgets/settings/preference_settings/primary_color_setting.dart'; import 'package:immich_mobile/widgets/settings/preference_settings/primary_color_setting.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart'; import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart'; import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
@ -74,23 +74,26 @@ class ThemeSetting extends HookConsumerWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SettingsSubTitle(title: "theme".tr()), SettingGroupTitle(
title: "theme".t(context: context),
icon: Icons.color_lens_outlined,
),
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: isSystemTheme, valueNotifier: isSystemTheme,
title: 'theme_setting_system_theme_switch'.tr(), title: 'theme_setting_system_theme_switch'.t(context: context),
onChanged: onSystemThemeChange, onChanged: onSystemThemeChange,
), ),
if (currentTheme.value != ThemeMode.system) if (currentTheme.value != ThemeMode.system)
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: isDarkTheme, valueNotifier: isDarkTheme,
title: 'map_settings_dark_mode'.tr(), title: 'map_settings_dark_mode'.t(context: context),
onChanged: onThemeChange, onChanged: onThemeChange,
), ),
const PrimaryColorSetting(), const PrimaryColorSetting(),
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: applyThemeToBackgroundProvider, valueNotifier: applyThemeToBackgroundProvider,
title: "theme_setting_colorful_interface_title".tr(), title: "theme_setting_colorful_interface_title".t(context: context),
subtitle: 'theme_setting_colorful_interface_subtitle'.tr(), subtitle: 'theme_setting_colorful_interface_subtitle'.t(context: context),
onChanged: onSurfaceColorSettingChange, onChanged: onSurfaceColorSettingChange,
), ),
], ],

View file

@ -0,0 +1,39 @@
import 'package:flutter/widgets.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart';
class SettingGroupTitle extends StatelessWidget {
final String title;
final String? subtitle;
final IconData? icon;
final EdgeInsetsGeometry? contentPadding;
const SettingGroupTitle({super.key, required this.title, this.icon, this.subtitle, this.contentPadding});
@override
Widget build(BuildContext context) {
return Padding(
padding: contentPadding ?? const EdgeInsets.only(left: 20.0, right: 20.0, bottom: 8.0),
child: Column(
children: [
Row(
children: [
if (icon != null) ...[
Icon(icon, color: context.colorScheme.onSurfaceSecondary, size: 20),
const SizedBox(width: 8),
],
Text(title, style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary)),
],
),
if (subtitle != null) ...[
const SizedBox(height: 8),
Text(
subtitle!,
style: context.textTheme.bodyMedium!.copyWith(color: context.colorScheme.onSurface.withAlpha(200)),
),
],
],
),
);
}
}

View file

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
class SettingListTile extends StatelessWidget {
final String title;
final String? subtitle;
final Widget? leading;
final Widget? trailing;
final VoidCallback? onTap;
final EdgeInsetsGeometry? contentPadding;
const SettingListTile({
required this.title,
this.subtitle,
this.leading,
this.trailing,
this.onTap,
this.contentPadding,
super.key,
});
@override
Widget build(BuildContext context) {
return ListTile(
title: Text(title, style: context.textTheme.bodyLarge!.copyWith(fontWeight: FontWeight.w500, height: 1.5)),
subtitle: subtitle != null
? Text(
subtitle!,
style: context.textTheme.bodyMedium!.copyWith(color: context.textTheme.bodyMedium!.color!.withAlpha(215)),
)
: null,
leading: leading,
trailing: trailing,
onTap: onTap,
contentPadding: contentPadding,
);
}
}

View file

@ -36,11 +36,8 @@ class SettingsCard extends StatelessWidget {
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Icon(icon, color: context.primaryColor), child: Icon(icon, color: context.primaryColor),
), ),
title: Text( title: Text(title, style: context.textTheme.titleMedium!.copyWith(color: context.primaryColor)),
title, subtitle: Text(subtitle, style: context.textTheme.bodyMedium),
style: context.textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600, color: context.primaryColor),
),
subtitle: Text(subtitle, style: context.textTheme.labelLarge),
onTap: () => context.pushRoute(settingRoute), onTap: () => context.pushRoute(settingRoute),
), ),
), ),

View file

@ -9,13 +9,11 @@ class SettingsSubPageScaffold extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListView.separated( return ListView.separated(
padding: const EdgeInsets.symmetric(vertical: 20), padding: const EdgeInsets.symmetric(vertical: 16),
itemCount: settings.length, itemCount: settings.length,
itemBuilder: (ctx, index) => settings[index], itemBuilder: (ctx, index) => settings[index],
separatorBuilder: (context, index) => showDivider separatorBuilder: (context, index) => showDivider
? const Column( ? const Column(children: [SizedBox(height: 5), Divider(height: 10), SizedBox(height: 15)])
children: [SizedBox(height: 5), Divider(height: 10, indent: 15, endIndent: 15), SizedBox(height: 15)],
)
: const SizedBox(height: 10), : const SizedBox(height: 10),
); );
} }