chore: bump dart sdk to 3.8 (#20355)

* chore: bump dart sdk to 3.8

* chore: make build

* make pigeon

* chore: format files

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong
2025-07-29 00:34:03 +05:30
committed by GitHub
parent 9b3718120b
commit e52b9d15b5
643 changed files with 32561 additions and 35292 deletions

View File

@@ -34,10 +34,7 @@ class AdvancedSettings extends HookConsumerWidget {
final logLevel = Level.LEVELS[levelId.value].name;
useValueChanged(
levelId.value,
(_, __) => LogService.I.setLogLevel(Level.LEVELS[levelId.value].toLogLevel()),
);
useValueChanged(levelId.value, (_, __) => LogService.I.setLogLevel(Level.LEVELS[levelId.value].toLogLevel()));
Future<bool> checkAndroidVersion() async {
if (Platform.isAndroid) {

View File

@@ -11,9 +11,7 @@ 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 {
const GroupSettings({
super.key,
});
const GroupSettings({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -21,10 +19,7 @@ class GroupSettings extends HookConsumerWidget {
final groupBy = GroupAssetsBy.values[groupByIndex.value];
Future<void> updateAppSettings(GroupAssetsBy groupBy) async {
await ref.watch(appSettingsServiceProvider).setSetting(
AppSettingsEnum.groupAssetsBy,
groupBy.index,
);
await ref.watch(appSettingsServiceProvider).setSetting(AppSettingsEnum.groupAssetsBy, groupBy.index);
ref.invalidate(appSettingsServiceProvider);
}
@@ -41,18 +36,9 @@ class GroupSettings extends HookConsumerWidget {
SettingsSubTitle(title: "asset_list_group_by_sub_title".tr()),
SettingsRadioListTile(
groups: [
SettingsRadioGroup(
title: 'asset_list_layout_settings_group_by_month_day'.tr(),
value: GroupAssetsBy.day,
),
SettingsRadioGroup(
title: 'month'.tr(),
value: GroupAssetsBy.month,
),
SettingsRadioGroup(
title: 'asset_list_layout_settings_group_automatically'.tr(),
value: GroupAssetsBy.auto,
),
SettingsRadioGroup(title: 'asset_list_layout_settings_group_by_month_day'.tr(), value: GroupAssetsBy.day),
SettingsRadioGroup(title: 'month'.tr(), value: GroupAssetsBy.month),
SettingsRadioGroup(title: 'asset_list_layout_settings_group_automatically'.tr(), value: GroupAssetsBy.auto),
],
groupBy: groupBy,
onRadioChanged: changeGroupValue,

View File

@@ -9,9 +9,7 @@ import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
class LayoutSettings extends HookConsumerWidget {
const LayoutSettings({
super.key,
});
const LayoutSettings({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {

View File

@@ -10,9 +10,7 @@ import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'asset_list_layout_settings.dart';
class AssetListSettings extends HookConsumerWidget {
const AssetListSettings({
super.key,
});
const AssetListSettings({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -28,9 +26,6 @@ class AssetListSettings extends HookConsumerWidget {
const GroupSettings(),
];
return SettingsSubPageScaffold(
settings: assetListSetting,
showDivider: true,
);
return SettingsSubPageScaffold(settings: assetListSetting, showDivider: true);
}
}

View File

@@ -4,20 +4,12 @@ import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
import 'video_viewer_settings.dart';
class AssetViewerSettings extends StatelessWidget {
const AssetViewerSettings({
super.key,
});
const AssetViewerSettings({super.key});
@override
Widget build(BuildContext context) {
final assetViewerSetting = [
const ImageViewerQualitySetting(),
const VideoViewerSettings(),
];
final assetViewerSetting = [const ImageViewerQualitySetting(), const VideoViewerSettings()];
return SettingsSubPageScaffold(
settings: assetViewerSetting,
showDivider: true,
);
return SettingsSubPageScaffold(settings: assetViewerSetting, showDivider: true);
}
}

View File

@@ -9,9 +9,7 @@ import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
class ImageViewerQualitySetting extends HookConsumerWidget {
const ImageViewerQualitySetting({
super.key,
});
const ImageViewerQualitySetting({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -24,10 +22,7 @@ class ImageViewerQualitySetting extends HookConsumerWidget {
SettingsSubTitle(title: "setting_image_viewer_title".tr()),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
title: Text(
'setting_image_viewer_help',
style: context.textTheme.bodyMedium,
).tr(),
title: Text('setting_image_viewer_help', style: context.textTheme.bodyMedium).tr(),
),
SettingsSwitchListTile(
valueNotifier: isPreview,

View File

@@ -8,9 +8,7 @@ import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
class VideoViewerSettings extends HookConsumerWidget {
const VideoViewerSettings({
super.key,
});
const VideoViewerSettings({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {

View File

@@ -24,12 +24,7 @@ class BackgroundBackupSettings extends ConsumerWidget {
void showErrorToUser(String msg) {
final snackBar = SnackBar(
content: Text(
msg.tr(),
style: context.textTheme.bodyLarge?.copyWith(
color: context.primaryColor,
),
),
content: Text(msg.tr(), style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor)),
backgroundColor: Colors.red,
);
context.scaffoldMessenger.showSnackBar(snackBar);
@@ -41,20 +36,14 @@ class BackgroundBackupSettings extends ConsumerWidget {
barrierDismissible: false,
builder: (BuildContext ctx) {
return AlertDialog(
title: const Text(
'backup_controller_page_background_battery_info_title',
).tr(),
title: const Text('backup_controller_page_background_battery_info_title').tr(),
content: SingleChildScrollView(
child: const Text(
'backup_controller_page_background_battery_info_message',
).tr(),
child: const Text('backup_controller_page_background_battery_info_message').tr(),
),
actions: [
ElevatedButton(
onPressed: () => launchUrl(
Uri.parse('https://dontkillmyapp.com'),
mode: LaunchMode.externalApplication,
),
onPressed: () =>
launchUrl(Uri.parse('https://dontkillmyapp.com'), mode: LaunchMode.externalApplication),
child: const Text(
"backup_controller_page_background_battery_info_link",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
@@ -79,7 +68,9 @@ class BackgroundBackupSettings extends ConsumerWidget {
title: 'backup_controller_page_background_is_off'.tr(),
subtileText: 'backup_controller_page_background_description'.tr(),
buttonText: 'backup_controller_page_background_turn_on'.tr(),
onButtonTap: () => ref.read(backupProvider.notifier).configureBackgroundBackup(
onButtonTap: () => ref
.read(backupProvider.notifier)
.configureBackgroundBackup(
enabled: true,
onError: showErrorToUser,
onBatteryInfo: showBatteryOptimizationInfoToUser,
@@ -90,10 +81,7 @@ class BackgroundBackupSettings extends ConsumerWidget {
return Column(
children: [
if (!Platform.isIOS || iosSettings?.appRefreshEnabled == true)
_BackgroundSettingsEnabled(
onError: showErrorToUser,
onBatteryInfo: showBatteryOptimizationInfoToUser,
),
_BackgroundSettingsEnabled(onError: showErrorToUser, onBatteryInfo: showBatteryOptimizationInfoToUser),
if (Platform.isIOS && iosSettings?.appRefreshEnabled != true) const _IOSBackgroundRefreshDisabled(),
if (Platform.isIOS && iosSettings != null) IosDebugInfoTile(settings: iosSettings),
],
@@ -120,10 +108,7 @@ class _BackgroundSettingsEnabled extends HookConsumerWidget {
final void Function(String msg) onError;
final void Function() onBatteryInfo;
const _BackgroundSettingsEnabled({
required this.onError,
required this.onBatteryInfo,
});
const _BackgroundSettingsEnabled({required this.onError, required this.onBatteryInfo});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -131,41 +116,45 @@ class _BackgroundSettingsEnabled extends HookConsumerWidget {
final isWifiRequiredNotifier = useValueNotifier(isWifiRequired);
useValueChanged(
isWifiRequired,
(_, __) => WidgetsBinding.instance.addPostFrameCallback(
(_) => isWifiRequiredNotifier.value = isWifiRequired,
),
(_, __) => WidgetsBinding.instance.addPostFrameCallback((_) => isWifiRequiredNotifier.value = isWifiRequired),
);
final isChargingRequired = ref.watch(backupProvider.select((s) => s.backupRequireCharging));
final isChargingRequiredNotifier = useValueNotifier(isChargingRequired);
useValueChanged(
isChargingRequired,
(_, __) => WidgetsBinding.instance.addPostFrameCallback(
(_) => isChargingRequiredNotifier.value = isChargingRequired,
),
(_, __) =>
WidgetsBinding.instance.addPostFrameCallback((_) => isChargingRequiredNotifier.value = isChargingRequired),
);
int backupDelayToSliderValue(int ms) => switch (ms) {
5000 => 0,
30000 => 1,
120000 => 2,
_ => 3,
};
5000 => 0,
30000 => 1,
120000 => 2,
_ => 3,
};
int backupDelayToMilliseconds(int v) => switch (v) { 0 => 5000, 1 => 30000, 2 => 120000, _ => 600000 };
int backupDelayToMilliseconds(int v) => switch (v) {
0 => 5000,
1 => 30000,
2 => 120000,
_ => 600000,
};
String formatBackupDelaySliderValue(int v) => switch (v) {
0 => 'setting_notifications_notify_seconds'.tr(namedArgs: {'count': '5'}),
1 => 'setting_notifications_notify_seconds'.tr(namedArgs: {'count': '30'}),
2 => 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '2'}),
_ => 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '10'}),
};
0 => 'setting_notifications_notify_seconds'.tr(namedArgs: {'count': '5'}),
1 => 'setting_notifications_notify_seconds'.tr(namedArgs: {'count': '30'}),
2 => 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '2'}),
_ => 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '10'}),
};
final backupTriggerDelay = ref.watch(backupProvider.select((s) => s.backupTriggerDelay));
final triggerDelay = useState(backupDelayToSliderValue(backupTriggerDelay));
useValueChanged(
triggerDelay.value,
(_, __) => ref.read(backupProvider.notifier).configureBackgroundBackup(
(_, __) => ref
.read(backupProvider.notifier)
.configureBackgroundBackup(
triggerDelay: backupDelayToMilliseconds(triggerDelay.value),
onError: onError,
onBatteryInfo: onBatteryInfo,
@@ -177,40 +166,32 @@ class _BackgroundSettingsEnabled extends HookConsumerWidget {
iconColor: context.primaryColor,
title: 'backup_controller_page_background_is_on'.tr(),
buttonText: 'backup_controller_page_background_turn_off'.tr(),
onButtonTap: () => ref.read(backupProvider.notifier).configureBackgroundBackup(
enabled: false,
onError: onError,
onBatteryInfo: onBatteryInfo,
),
onButtonTap: () => ref
.read(backupProvider.notifier)
.configureBackgroundBackup(enabled: false, onError: onError, onBatteryInfo: onBatteryInfo),
subtitle: Column(
children: [
SettingsSwitchListTile(
valueNotifier: isWifiRequiredNotifier,
title: 'backup_controller_page_background_wifi'.tr(),
icon: Icons.wifi,
onChanged: (enabled) => ref.read(backupProvider.notifier).configureBackgroundBackup(
requireWifi: enabled,
onError: onError,
onBatteryInfo: onBatteryInfo,
),
onChanged: (enabled) => ref
.read(backupProvider.notifier)
.configureBackgroundBackup(requireWifi: enabled, onError: onError, onBatteryInfo: onBatteryInfo),
),
SettingsSwitchListTile(
valueNotifier: isChargingRequiredNotifier,
title: 'backup_controller_page_background_charging'.tr(),
icon: Icons.charging_station,
onChanged: (enabled) => ref.read(backupProvider.notifier).configureBackgroundBackup(
requireCharging: enabled,
onError: onError,
onBatteryInfo: onBatteryInfo,
),
onChanged: (enabled) => ref
.read(backupProvider.notifier)
.configureBackgroundBackup(requireCharging: enabled, onError: onError, onBatteryInfo: onBatteryInfo),
),
if (Platform.isAndroid)
SettingsSliderListTile(
valueNotifier: triggerDelay,
text: 'backup_controller_page_background_delay'.tr(
namedArgs: {
'duration': formatBackupDelaySliderValue(triggerDelay.value),
},
namedArgs: {'duration': formatBackupDelaySliderValue(triggerDelay.value)},
),
maxValue: 3.0,
noDivisons: 3,

View File

@@ -15,9 +15,7 @@ import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
class BackupSettings extends HookConsumerWidget {
const BackupSettings({
super.key,
});
const BackupSettings({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -71,22 +69,14 @@ class BackupSettings extends HookConsumerWidget {
SettingsButtonListTile(
icon: Icons.photo_album_outlined,
title: 'sync_albums'.tr(),
subtitle: Text(
"sync_albums_manual_subtitle".tr(),
),
subtitle: Text("sync_albums_manual_subtitle".tr()),
buttonText: 'sync_albums'.tr(),
child: isAlbumSyncInProgress.value
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: syncAlbums,
child: Text('sync'.tr()),
),
: ElevatedButton(onPressed: syncAlbums, child: Text('sync'.tr())),
),
];
return SettingsSubPageScaffold(
settings: backupSettings,
showDivider: true,
);
return SettingsSubPageScaffold(settings: backupSettings, showDivider: true);
}
}

View File

@@ -17,9 +17,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
class BetaSyncSettings extends HookConsumerWidget {
const BetaSyncSettings({
super.key,
});
const BetaSyncSettings({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -35,17 +33,11 @@ class BetaSyncSettings extends HookConsumerWidget {
final memoryCount = memoryService.getCount();
final getLocalHashedCount = assetService.getLocalHashedCount();
return await Future.wait([
assetCounts,
localAlbumCounts,
remoteAlbumCounts,
memoryCount,
getLocalHashedCount,
]);
return await Future.wait([assetCounts, localAlbumCounts, remoteAlbumCounts, memoryCount, getLocalHashedCount]);
}
Future<void> resetDatabase() async {
// https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94
// https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94
final drift = ref.read(driftProvider);
final database = drift.attachedDatabase;
await database.exclusively(() async {
@@ -62,14 +54,10 @@ class BetaSyncSettings extends HookConsumerWidget {
database.resolvedEngine.executor,
drift_db.OpeningDetails(null, database.schemaVersion),
);
await database.customStatement(
'PRAGMA user_version = ${database.schemaVersion}',
);
await database.customStatement('PRAGMA user_version = ${database.schemaVersion}');
// Refresh all stream queries
database.notifyUpdates({
for (final table in database.allTables) drift_db.TableUpdate.onTable(table),
});
database.notifyUpdates({for (final table in database.allTables) drift_db.TableUpdate.onTable(table)});
});
}
@@ -83,28 +71,18 @@ class BetaSyncSettings extends HookConsumerWidget {
if (!await dbFile.exists()) {
if (context.mounted) {
context.scaffoldMessenger.showSnackBar(
SnackBar(
content: Text("Database file not found".t(context: context)),
),
SnackBar(content: Text("Database file not found".t(context: context))),
);
}
return;
}
final timestamp = DateTime.now().millisecondsSinceEpoch;
final exportFile = File(
path.join(
documentsDir.path,
'immich_export_$timestamp.sqlite',
),
);
final exportFile = File(path.join(documentsDir.path, 'immich_export_$timestamp.sqlite'));
await dbFile.copy(exportFile.path);
await Share.shareXFiles(
[XFile(exportFile.path)],
text: 'Immich Database Export',
);
await Share.shareXFiles([XFile(exportFile.path)], text: 'Immich Database Export');
Future.delayed(const Duration(seconds: 30), () async {
if (await exportFile.exists()) {
@@ -114,17 +92,13 @@ class BetaSyncSettings extends HookConsumerWidget {
if (context.mounted) {
context.scaffoldMessenger.showSnackBar(
SnackBar(
content: Text("Database exported successfully".t(context: context)),
),
SnackBar(content: Text("Database exported successfully".t(context: context))),
);
}
} catch (e) {
if (context.mounted) {
context.scaffoldMessenger.showSnackBar(
SnackBar(
content: Text("Failed to export database: $e".t(context: context)),
),
SnackBar(content: Text("Failed to export database: $e".t(context: context))),
);
}
}
@@ -225,27 +199,17 @@ class BetaSyncSettings extends HookConsumerWidget {
],
),
),
const Divider(
height: 1,
indent: 16,
endIndent: 16,
),
const Divider(height: 1, indent: 16, endIndent: 16),
const SizedBox(height: 24),
_SectionHeaderText(text: "jobs".t(context: context)),
ListTile(
title: Text(
"sync_local".t(context: context),
style: const TextStyle(
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
"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),
trailing: _SyncStatusIcon(
status: ref.watch(syncStatusProvider).localSyncStatus,
),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).localSyncStatus),
onTap: () {
ref.read(backgroundSyncProvider).syncLocal(full: true);
},
@@ -253,17 +217,11 @@ class BetaSyncSettings extends HookConsumerWidget {
ListTile(
title: Text(
"sync_remote".t(context: context),
style: const TextStyle(
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
"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),
trailing: _SyncStatusIcon(
status: ref.watch(syncStatusProvider).remoteSyncStatus,
),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).remoteSyncStatus),
onTap: () {
ref.read(backgroundSyncProvider).syncRemote();
},
@@ -271,64 +229,40 @@ class BetaSyncSettings extends HookConsumerWidget {
ListTile(
title: Text(
"hash_asset".t(context: context),
style: const TextStyle(
fontWeight: FontWeight.w500,
),
style: const TextStyle(fontWeight: FontWeight.w500),
),
leading: const Icon(Icons.tag),
subtitle: Text(
"tap_to_run_job".t(context: context),
),
trailing: _SyncStatusIcon(
status: ref.watch(syncStatusProvider).hashJobStatus,
),
subtitle: Text("tap_to_run_job".t(context: context)),
trailing: _SyncStatusIcon(status: ref.watch(syncStatusProvider).hashJobStatus),
onTap: () {
ref.read(backgroundSyncProvider).hashAssets();
},
),
const Divider(
height: 1,
indent: 16,
endIndent: 16,
),
const Divider(height: 1, indent: 16, endIndent: 16),
const SizedBox(height: 24),
_SectionHeaderText(text: "actions".t(context: context)),
ListTile(
title: Text(
"export_database".t(context: context),
style: const TextStyle(
fontWeight: FontWeight.w500,
),
),
subtitle: Text(
"export_database_description".t(context: context),
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text("export_database_description".t(context: context)),
leading: const Icon(Icons.download),
onTap: exportDatabase,
),
ListTile(
title: Text(
"reset_sqlite".t(context: context),
style: TextStyle(
color: context.colorScheme.error,
fontWeight: FontWeight.w500,
),
),
leading: Icon(
Icons.settings_backup_restore_rounded,
color: context.colorScheme.error,
style: TextStyle(color: context.colorScheme.error, fontWeight: FontWeight.w500),
),
leading: Icon(Icons.settings_backup_restore_rounded, color: context.colorScheme.error),
onTap: () async {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(
"reset_sqlite".t(context: context),
),
content: Text(
"reset_sqlite_confirmation".t(context: context),
),
title: Text("reset_sqlite".t(context: context)),
content: Text("reset_sqlite_confirmation".t(context: context)),
actions: [
TextButton(
onPressed: () => context.pop(),
@@ -339,18 +273,12 @@ class BetaSyncSettings extends HookConsumerWidget {
await resetDatabase();
context.pop();
context.scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(
"reset_sqlite_success".t(context: context),
),
),
SnackBar(content: Text("reset_sqlite_success".t(context: context))),
);
},
child: Text(
"confirm".t(context: context),
style: TextStyle(
color: context.colorScheme.error,
),
style: TextStyle(color: context.colorScheme.error),
),
),
],
@@ -370,31 +298,15 @@ class BetaSyncSettings extends HookConsumerWidget {
class _SyncStatusIcon extends StatelessWidget {
final SyncStatus status;
const _SyncStatusIcon({
required this.status,
});
const _SyncStatusIcon({required this.status});
@override
Widget build(BuildContext context) {
return switch (status) {
SyncStatus.idle => const Icon(
Icons.pause_circle_outline_rounded,
),
SyncStatus.syncing => const SizedBox(
height: 24,
width: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
),
),
SyncStatus.success => const Icon(
Icons.check_circle_outline,
color: Colors.green,
),
SyncStatus.error => Icon(
Icons.error_outline,
color: context.colorScheme.error,
),
SyncStatus.idle => const Icon(Icons.pause_circle_outline_rounded),
SyncStatus.syncing => const SizedBox(height: 24, width: 24, child: CircularProgressIndicator(strokeWidth: 2)),
SyncStatus.success => const Icon(Icons.check_circle_outline, color: Colors.green),
SyncStatus.error => Icon(Icons.error_outline, color: context.colorScheme.error),
};
}
}
@@ -402,9 +314,7 @@ class _SyncStatusIcon extends StatelessWidget {
class _SectionHeaderText extends StatelessWidget {
final String text;
const _SectionHeaderText({
required this.text,
});
const _SectionHeaderText({required this.text});
@override
Widget build(BuildContext context) {

View File

@@ -7,12 +7,7 @@ class EntitiyCountTile extends StatelessWidget {
final String label;
final IconData icon;
const EntitiyCountTile({
super.key,
required this.count,
required this.label,
required this.icon,
});
const EntitiyCountTile({super.key, required this.count, required this.label, required this.icon});
String zeroPadding(int number, int targetWidth) {
final numStr = number.toString();
@@ -31,10 +26,7 @@ class EntitiyCountTile extends StatelessWidget {
decoration: BoxDecoration(
color: context.colorScheme.surfaceContainerLow,
borderRadius: const BorderRadius.all(Radius.circular(16)),
border: Border.all(
width: 0.5,
color: context.colorScheme.outline.withAlpha(25),
),
border: Border.all(width: 0.5, color: context.colorScheme.outline.withAlpha(25)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
@@ -44,18 +36,11 @@ class EntitiyCountTile extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(
icon,
color: context.primaryColor,
),
Icon(icon, color: context.primaryColor),
const SizedBox(width: 8),
Text(
label,
style: TextStyle(
color: context.primaryColor,
fontWeight: FontWeight.bold,
fontSize: 16,
),
style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 16),
),
],
),
@@ -66,23 +51,15 @@ class EntitiyCountTile extends StatelessWidget {
final maxDigits = calculateMaxDigits(constraints.maxWidth);
return RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 18,
fontFamily: 'OverpassMono',
fontWeight: FontWeight.w600,
),
style: const TextStyle(fontSize: 18, fontFamily: 'OverpassMono', fontWeight: FontWeight.w600),
children: [
TextSpan(
text: zeroPadding(count, maxDigits),
style: TextStyle(
color: context.colorScheme.onSurfaceSecondary.withAlpha(75),
),
style: TextStyle(color: context.colorScheme.onSurfaceSecondary.withAlpha(75)),
),
TextSpan(
text: count.toString(),
style: TextStyle(
color: context.primaryColor,
),
style: TextStyle(color: context.primaryColor),
),
],
),

View File

@@ -13,9 +13,7 @@ import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
class BetaTimelineListTile extends ConsumerStatefulWidget {
const BetaTimelineListTile({
super.key,
});
const BetaTimelineListTile({super.key});
@override
ConsumerState<BetaTimelineListTile> createState() => _BetaTimelineListTileState();
@@ -30,31 +28,22 @@ class _BetaTimelineListTileState extends ConsumerState<BetaTimelineListTile> wit
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
);
_animationController = AnimationController(duration: const Duration(seconds: 3), vsync: this);
_rotationAnimation = Tween<double>(begin: 0, end: 2 * math.pi).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.linear,
),
);
_rotationAnimation = Tween<double>(
begin: 0,
end: 2 * math.pi,
).animate(CurvedAnimation(parent: _animationController, curve: Curves.linear));
_pulseAnimation = Tween<double>(begin: 1, end: 1.1).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
),
);
_pulseAnimation = Tween<double>(
begin: 1,
end: 1.1,
).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut));
_gradientAnimation = Tween<double>(begin: 0, end: 1).animate(
CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
),
);
_gradientAnimation = Tween<double>(
begin: 0,
end: 1,
).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut));
_animationController.repeat(reverse: true);
}
@@ -85,12 +74,8 @@ class _BetaTimelineListTileState extends ConsumerState<BetaTimelineListTile> wit
return AlertDialog(
title: value ? const Text("Enable Beta Timeline") : const Text("Disable Beta Timeline"),
content: value
? const Text(
"Are you sure you want to enable the beta timeline?",
)
: const Text(
"Are you sure you want to disable the beta timeline?",
),
? const Text("Are you sure you want to enable the beta timeline?")
: const Text("Are you sure you want to disable the beta timeline?"),
actions: [
TextButton(
onPressed: () {
@@ -98,27 +83,16 @@ class _BetaTimelineListTileState extends ConsumerState<BetaTimelineListTile> wit
},
child: Text(
"cancel".t(context: context),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: context.colorScheme.outline,
),
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500, color: context.colorScheme.outline),
),
),
ElevatedButton(
onPressed: () async {
Navigator.of(context).pop();
await ref.read(appSettingsServiceProvider).setSetting(
AppSettingsEnum.betaTimeline,
value,
);
context.router.replaceAll(
[ChangeExperienceRoute(switchingToBeta: value)],
);
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.betaTimeline, value);
context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: value)]);
},
child: Text(
"ok".t(context: context),
),
child: Text("ok".t(context: context)),
),
],
);
@@ -156,11 +130,7 @@ class _BetaTimelineListTileState extends ConsumerState<BetaTimelineListTile> wit
transform: GradientRotation(_rotationAnimation.value * 0.5),
),
boxShadow: [
BoxShadow(
color: context.primaryColor.withValues(alpha: 0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
BoxShadow(color: context.primaryColor.withValues(alpha: 0.1), blurRadius: 8, offset: const Offset(0, 2)),
],
),
child: Container(
@@ -193,11 +163,7 @@ class _BetaTimelineListTileState extends ConsumerState<BetaTimelineListTile> wit
],
),
),
child: Icon(
Icons.auto_awesome,
color: context.primaryColor,
size: 20,
),
child: Icon(Icons.auto_awesome, color: context.primaryColor, size: 20),
),
),
),
@@ -211,20 +177,13 @@ class _BetaTimelineListTileState extends ConsumerState<BetaTimelineListTile> wit
children: [
Text(
"advanced_settings_beta_timeline_title".t(context: context),
style: context.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
style: context.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
),
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
),
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(8),
),
borderRadius: const BorderRadius.all(Radius.circular(8)),
gradient: LinearGradient(
colors: [
context.primaryColor.withValues(alpha: 0.8),

View File

@@ -15,15 +15,11 @@ class CustomeProxyHeaderSettings extends StatelessWidget {
dense: true,
title: Text(
"headers_settings_tile_title".tr(),
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500),
),
subtitle: Text(
"headers_settings_tile_subtitle".tr(),
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceSecondary,
),
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
),
onTap: () => context.pushRoute(const HeaderSettingsRoute()),
);

View File

@@ -48,9 +48,7 @@ class LanguageSettings extends HookConsumerWidget {
filteredLocaleEntries.value = localeEntries;
} else {
filteredLocaleEntries.value = localeEntries
.where(
(entry) => entry.key.toLowerCase().contains(searchTerm.toLowerCase()),
)
.where((entry) => entry.key.toLowerCase().contains(searchTerm.toLowerCase()))
.toList();
}
});
@@ -61,17 +59,14 @@ class LanguageSettings extends HookConsumerWidget {
onSearch('');
}
useEffect(
() {
void searchListener() => onSearch(searchController.text);
searchController.addListener(searchListener);
return () {
searchController.removeListener(searchListener);
debounceTimer.value?.cancel();
};
},
[searchController],
);
useEffect(() {
void searchListener() => onSearch(searchController.text);
searchController.addListener(searchListener);
return () {
searchController.removeListener(searchListener);
debounceTimer.value?.cancel();
};
}, [searchController]);
return SafeArea(
child: Column(
@@ -111,11 +106,7 @@ class LanguageSettings extends HookConsumerWidget {
_LanguageApplyButton(
isDisabled: isButtonDisabled,
isLoading: isLoading.value,
onPressed: () => _applyLanguageChange(
context,
selectedLocale,
isLoading,
),
onPressed: () => _applyLanguageChange(context, selectedLocale, isLoading),
),
],
),
@@ -140,9 +131,7 @@ class _LanguageSearchBar extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(top: 16, bottom: 8, left: 50, right: 50),
decoration: BoxDecoration(
color: context.colorScheme.surface,
),
decoration: BoxDecoration(color: context.colorScheme.surface),
child: DecoratedBox(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(24)),
@@ -162,10 +151,7 @@ class _LanguageSearchBar extends StatelessWidget {
hintText: 'language_search_hint'.t(context: context),
prefixIcon: const Icon(Icons.search_rounded),
suffixIcon: controller.text.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear_rounded),
onPressed: onClear,
)
? IconButton(icon: const Icon(Icons.clear_rounded), onPressed: onClear)
: null,
controller: controller,
onChanged: onChanged,
@@ -186,24 +172,16 @@ class _LanguageNotFound extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.search_off_rounded,
size: 64,
color: context.colorScheme.onSurface.withValues(alpha: 0.4),
),
Icon(Icons.search_off_rounded, size: 64, color: context.colorScheme.onSurface.withValues(alpha: 0.4)),
const SizedBox(height: 8),
Text(
'language_no_results_title'.t(context: context),
style: context.textTheme.titleMedium?.copyWith(
color: context.colorScheme.onSurface,
),
style: context.textTheme.titleMedium?.copyWith(color: context.colorScheme.onSurface),
),
const SizedBox(height: 4),
Text(
'language_no_results_subtitle'.t(context: context),
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurface.withValues(alpha: 0.8),
),
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurface.withValues(alpha: 0.8)),
),
],
),
@@ -212,11 +190,7 @@ class _LanguageNotFound extends StatelessWidget {
}
class _LanguageApplyButton extends StatelessWidget {
const _LanguageApplyButton({
required this.isDisabled,
required this.isLoading,
required this.onPressed,
});
const _LanguageApplyButton({required this.isDisabled, required this.isLoading, required this.onPressed});
final bool isDisabled;
final bool isLoading;
@@ -225,9 +199,7 @@ class _LanguageApplyButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: BoxDecoration(
color: context.colorScheme.surface,
),
decoration: BoxDecoration(color: context.colorScheme.surface),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: SizedBox(
@@ -236,18 +208,10 @@ class _LanguageApplyButton extends StatelessWidget {
child: ElevatedButton(
onPressed: isDisabled ? null : onPressed,
child: isLoading
? const SizedBox.square(
dimension: 24,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
? const SizedBox.square(dimension: 24, child: CircularProgressIndicator(strokeWidth: 2))
: Text(
'setting_languages_apply'.t(context: context),
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16.0,
),
style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 16.0),
),
),
),
@@ -273,20 +237,12 @@ class _LanguageItem extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 4.0,
horizontal: 8.0,
),
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
child: DecoratedBox(
decoration: BoxDecoration(
color: context.colorScheme.surfaceContainerLowest.withValues(alpha: .6),
borderRadius: const BorderRadius.all(
Radius.circular(16.0),
),
border: Border.all(
color: context.colorScheme.outlineVariant.withValues(alpha: .4),
width: 1.0,
),
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
border: Border.all(color: context.colorScheme.outlineVariant.withValues(alpha: .4), width: 1.0),
),
child: ListTile(
title: Text(
@@ -296,22 +252,12 @@ class _LanguageItem extends StatelessWidget {
color: isSelected ? context.colorScheme.primary : context.colorScheme.onSurfaceVariant,
),
),
trailing: isSelected
? Icon(
Icons.check,
color: context.colorScheme.primary,
size: 20,
)
: null,
trailing: isSelected ? Icon(Icons.check, color: context.colorScheme.primary, size: 20) : null,
onTap: onTap,
selected: isSelected,
selectedTileColor: context.colorScheme.primary.withValues(alpha: .15),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0)),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0,
),
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16.0))),
contentPadding: const EdgeInsets.symmetric(horizontal: 16.0),
),
),
);

View File

@@ -14,13 +14,10 @@ class LocalStorageSettings extends HookConsumerWidget {
final isarDb = ref.watch(dbProvider);
final cacheItemCount = useState(0);
useEffect(
() {
cacheItemCount.value = isarDb.duplicatedAssets.countSync();
return null;
},
[],
);
useEffect(() {
cacheItemCount.value = isarDb.duplicatedAssets.countSync();
return null;
}, []);
void clearCache() async {
await isarDb.writeTxn(() => isarDb.duplicatedAssets.clear());
@@ -32,15 +29,11 @@ class LocalStorageSettings extends HookConsumerWidget {
dense: true,
title: Text(
"cache_settings_duplicated_assets_title",
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500),
).tr(namedArgs: {'count': "${cacheItemCount.value}"}),
subtitle: Text(
"cache_settings_duplicated_assets_subtitle",
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceSecondary,
),
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
).tr(),
trailing: TextButton(
onPressed: cacheItemCount.value > 0 ? clearCache : null,

View File

@@ -97,10 +97,7 @@ class EndpointInputState extends ConsumerState<EndpointInput> {
color: Colors.red,
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 16),
child: const Icon(
Icons.delete,
color: Colors.white,
),
child: const Icon(Icons.delete, color: Colors.white),
),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
@@ -120,27 +117,19 @@ class EndpointInputState extends ConsumerState<EndpointInput> {
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: validateUrl,
keyboardType: TextInputType.url,
style: const TextStyle(
fontFamily: 'Inconsolata',
fontWeight: FontWeight.w600,
fontSize: 14,
),
style: const TextStyle(fontFamily: 'Inconsolata', fontWeight: FontWeight.w600, fontSize: 14),
decoration: InputDecoration(
hintText: 'http(s)://immich.domain.com',
contentPadding: const EdgeInsets.all(16),
filled: true,
fillColor: context.colorScheme.surfaceContainer,
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
errorBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.red[300]!),
borderRadius: const BorderRadius.all(Radius.circular(16)),
),
disabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: context.isDarkTheme ? Colors.grey[900]! : Colors.grey[300]!,
),
borderSide: BorderSide(color: context.isDarkTheme ? Colors.grey[900]! : Colors.grey[300]!),
borderRadius: const BorderRadius.all(Radius.circular(16)),
),
),

View File

@@ -17,9 +17,7 @@ class ExternalNetworkPreference extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final entries = useState(
[const AuxilaryEndpoint(url: '', status: AuxCheckStatus.unknown)],
);
final entries = useState([const AuxilaryEndpoint(url: '', status: AuxCheckStatus.unknown)]);
final canSave = useState(false);
saveEndpointList() {
@@ -29,10 +27,7 @@ class ExternalNetworkPreference extends HookConsumerWidget {
final jsonString = jsonEncode(endpointList);
Store.put(
StoreKey.externalEndpointList,
jsonString,
);
Store.put(StoreKey.externalEndpointList, jsonString);
}
updateValidationStatus(String url, int index, AuxCheckStatus status) {
@@ -59,11 +54,7 @@ class ExternalNetworkPreference extends HookConsumerWidget {
saveEndpointList();
}
Widget proxyDecorator(
Widget child,
int index,
Animation<double> animation,
) {
Widget proxyDecorator(Widget child, int index, Animation<double> animation) {
return AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget? child) {
@@ -77,20 +68,17 @@ class ExternalNetworkPreference extends HookConsumerWidget {
);
}
useEffect(
() {
final jsonString = Store.tryGet(StoreKey.externalEndpointList);
useEffect(() {
final jsonString = Store.tryGet(StoreKey.externalEndpointList);
if (jsonString == null) {
return null;
}
final List<dynamic> jsonList = jsonDecode(jsonString);
entries.value = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList();
if (jsonString == null) {
return null;
},
const [],
);
}
final List<dynamic> jsonList = jsonDecode(jsonString);
entries.value = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList();
return null;
}, const []);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
@@ -99,21 +87,14 @@ class ExternalNetworkPreference extends HookConsumerWidget {
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16)),
color: context.colorScheme.surfaceContainerLow,
border: Border.all(
color: context.colorScheme.surfaceContainerHighest,
width: 1,
),
border: Border.all(color: context.colorScheme.surfaceContainerHighest, width: 1),
),
child: Stack(
children: [
Positioned(
bottom: -36,
right: -36,
child: Icon(
Icons.dns_rounded,
size: 120,
color: context.primaryColor.withValues(alpha: 0.05),
),
child: Icon(Icons.dns_rounded, size: 120, color: context.primaryColor.withValues(alpha: 0.05)),
),
ListView(
padding: const EdgeInsets.symmetric(vertical: 16.0),
@@ -121,14 +102,8 @@ class ExternalNetworkPreference extends HookConsumerWidget {
shrinkWrap: true,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 4.0,
horizontal: 24,
),
child: Text(
"external_network_sheet_info".tr(),
style: context.textTheme.bodyMedium,
),
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 24),
child: Text("external_network_sheet_info".tr(), style: context.textTheme.bodyMedium),
),
const SizedBox(height: 4),
Divider(color: context.colorScheme.surfaceContainerHighest),
@@ -165,10 +140,7 @@ class ExternalNetworkPreference extends HookConsumerWidget {
? () {
entries.value = [
...entries.value,
const AuxilaryEndpoint(
url: '',
status: AuxCheckStatus.unknown,
),
const AuxilaryEndpoint(url: '', status: AuxCheckStatus.unknown),
];
}
: null,

View File

@@ -7,19 +7,11 @@ import 'package:immich_mobile/providers/auth.provider.dart';
import 'package:immich_mobile/providers/network.provider.dart';
class LocalNetworkPreference extends HookConsumerWidget {
const LocalNetworkPreference({
super.key,
required this.enabled,
});
const LocalNetworkPreference({super.key, required this.enabled});
final bool enabled;
Future<String?> _showEditDialog(
BuildContext context,
String title,
String hintText,
String initialValue,
) {
Future<String?> _showEditDialog(BuildContext context, String title, String hintText, String initialValue) {
final controller = TextEditingController(text: initialValue);
return showDialog<String>(
@@ -29,23 +21,14 @@ class LocalNetworkPreference extends HookConsumerWidget {
content: TextField(
controller: controller,
autofocus: true,
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: hintText,
),
decoration: InputDecoration(border: const OutlineInputBorder(), hintText: hintText),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(
'cancel'.tr().toUpperCase(),
style: const TextStyle(color: Colors.red),
),
),
TextButton(
onPressed: () => Navigator.pop(context, controller.text),
child: Text('save'.tr().toUpperCase()),
child: Text('cancel'.tr().toUpperCase(), style: const TextStyle(color: Colors.red)),
),
TextButton(onPressed: () => Navigator.pop(context, controller.text), child: Text('save'.tr().toUpperCase())),
],
),
);
@@ -56,23 +39,20 @@ class LocalNetworkPreference extends HookConsumerWidget {
final wifiNameText = useState("");
final localEndpointText = useState("");
useEffect(
() {
final wifiName = ref.read(authProvider.notifier).getSavedWifiName();
final localEndpoint = ref.read(authProvider.notifier).getSavedLocalEndpoint();
useEffect(() {
final wifiName = ref.read(authProvider.notifier).getSavedWifiName();
final localEndpoint = ref.read(authProvider.notifier).getSavedLocalEndpoint();
if (wifiName != null) {
wifiNameText.value = wifiName;
}
if (wifiName != null) {
wifiNameText.value = wifiName;
}
if (localEndpoint != null) {
localEndpointText.value = localEndpoint;
}
if (localEndpoint != null) {
localEndpointText.value = localEndpoint;
}
return null;
},
[],
);
return null;
}, []);
saveWifiName(String wifiName) {
wifiNameText.value = wifiName;
@@ -85,12 +65,7 @@ class LocalNetworkPreference extends HookConsumerWidget {
}
handleEditWifiName() async {
final wifiName = await _showEditDialog(
context,
"wifi_name".tr(),
"your_wifi_name".tr(),
wifiNameText.value,
);
final wifiName = await _showEditDialog(context, "wifi_name".tr(), "your_wifi_name".tr(), wifiNameText.value);
if (wifiName != null) {
await saveWifiName(wifiName);
@@ -146,21 +121,14 @@ class LocalNetworkPreference extends HookConsumerWidget {
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16)),
color: context.colorScheme.surfaceContainerLow,
border: Border.all(
color: context.colorScheme.surfaceContainerHighest,
width: 1,
),
border: Border.all(color: context.colorScheme.surfaceContainerHighest, width: 1),
),
child: Stack(
children: [
Positioned(
bottom: -36,
right: -36,
child: Icon(
Icons.home_outlined,
size: 120,
color: context.primaryColor.withValues(alpha: 0.05),
),
child: Icon(Icons.home_outlined, size: 120, color: context.primaryColor.withValues(alpha: 0.05)),
),
ListView(
padding: const EdgeInsets.symmetric(vertical: 16.0),
@@ -168,19 +136,11 @@ class LocalNetworkPreference extends HookConsumerWidget {
shrinkWrap: true,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 4.0,
horizontal: 24,
),
child: Text(
"local_network_sheet_info".tr(),
style: context.textTheme.bodyMedium,
),
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 24),
child: Text("local_network_sheet_info".tr(), style: context.textTheme.bodyMedium),
),
const SizedBox(height: 4),
Divider(
color: context.colorScheme.surfaceContainerHighest,
),
Divider(color: context.colorScheme.surfaceContainerHighest),
ListTile(
enabled: enabled,
contentPadding: const EdgeInsets.only(left: 24, right: 8),
@@ -223,9 +183,7 @@ class LocalNetworkPreference extends HookConsumerWidget {
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24.0,
),
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: SizedBox(
height: 48,
child: OutlinedButton.icon(

View File

@@ -77,15 +77,12 @@ class NetworkingSettings extends HookConsumerWidget {
}
}
useEffect(
() {
if (featureEnabled.value == true) {
checkWifiReadPermission();
}
return null;
},
[featureEnabled.value],
);
useEffect(() {
if (featureEnabled.value == true) {
checkWifiReadPermission();
}
return null;
}, [featureEnabled.value]);
return ListView(
padding: const EdgeInsets.only(bottom: 96),
@@ -104,20 +101,12 @@ class NetworkingSettings extends HookConsumerWidget {
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: const BorderRadius.all(Radius.circular(16)),
side: BorderSide(
color: context.colorScheme.surfaceContainerHighest,
width: 1,
),
side: BorderSide(color: context.colorScheme.surfaceContainerHighest, width: 1),
),
child: ListTile(
leading: currentEndpoint != null
? const Icon(
Icons.check_circle_rounded,
color: Colors.green,
)
: const Icon(
Icons.circle_outlined,
),
? const Icon(Icons.check_circle_rounded, color: Colors.green)
: const Icon(Icons.circle_outlined),
title: Text(
currentEndpoint ?? "--",
style: TextStyle(
@@ -132,9 +121,7 @@ class NetworkingSettings extends HookConsumerWidget {
),
Padding(
padding: const EdgeInsets.only(top: 10.0),
child: Divider(
color: context.colorScheme.surfaceContainerHighest,
),
child: Divider(color: context.colorScheme.surfaceContainerHighest),
),
SettingsSwitchListTile(
enabled: true,
@@ -144,35 +131,21 @@ class NetworkingSettings extends HookConsumerWidget {
),
Padding(
padding: const EdgeInsets.only(top: 8, left: 16, bottom: 16),
child: NetworkPreferenceTitle(
title: "local_network".tr().toUpperCase(),
icon: Icons.home_outlined,
),
),
LocalNetworkPreference(
enabled: featureEnabled.value,
child: NetworkPreferenceTitle(title: "local_network".tr().toUpperCase(), icon: Icons.home_outlined),
),
LocalNetworkPreference(enabled: featureEnabled.value),
Padding(
padding: const EdgeInsets.only(top: 32, left: 16, bottom: 16),
child: NetworkPreferenceTitle(
title: "external_network".tr().toUpperCase(),
icon: Icons.dns_outlined,
),
),
ExternalNetworkPreference(
enabled: featureEnabled.value,
child: NetworkPreferenceTitle(title: "external_network".tr().toUpperCase(), icon: Icons.dns_outlined),
),
ExternalNetworkPreference(enabled: featureEnabled.value),
],
);
}
}
class NetworkPreferenceTitle extends StatelessWidget {
const NetworkPreferenceTitle({
super.key,
required this.icon,
required this.title,
});
const NetworkPreferenceTitle({super.key, required this.icon, required this.title});
final IconData icon;
final String title;
@@ -181,10 +154,7 @@ class NetworkPreferenceTitle extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
children: [
Icon(
icon,
color: context.colorScheme.onSurface.withAlpha(150),
),
Icon(icon, color: context.colorScheme.onSurface.withAlpha(150)),
const SizedBox(width: 8),
Text(
title,
@@ -199,58 +169,37 @@ class NetworkPreferenceTitle 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();
final AuxCheckStatus status;
final bool enabled;
@override
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) {
AuxCheckStatus.loading => Padding(
padding: const EdgeInsets.only(left: 4.0),
child: SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
color: context.primaryColor,
strokeWidth: 2,
key: const ValueKey('loading'),
),
AuxCheckStatus.loading => Padding(
padding: const EdgeInsets.only(left: 4.0),
child: SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(color: context.primaryColor, strokeWidth: 2, key: const ValueKey('loading')),
),
),
AuxCheckStatus.valid =>
enabled
? const Icon(Icons.check_circle_rounded, color: Colors.green, key: ValueKey('success'))
: Icon(
Icons.check_circle_rounded,
color: context.colorScheme.onSurface.withAlpha(100),
key: const ValueKey('success'),
),
),
AuxCheckStatus.valid => enabled
? const Icon(
Icons.check_circle_rounded,
color: Colors.green,
key: ValueKey('success'),
)
: Icon(
Icons.check_circle_rounded,
color: context.colorScheme.onSurface.withAlpha(100),
key: const ValueKey('success'),
),
AuxCheckStatus.error => enabled
? const Icon(
Icons.error_rounded,
color: Colors.red,
key: ValueKey('error'),
)
: const Icon(
Icons.error_rounded,
color: Colors.grey,
key: ValueKey('error'),
),
_ => const Icon(Icons.circle_outlined, key: ValueKey('unknown')),
};
AuxCheckStatus.error =>
enabled
? const Icon(Icons.error_rounded, color: Colors.red, key: ValueKey('error'))
: const Icon(Icons.error_rounded, color: Colors.grey, key: ValueKey('error')),
_ => const Icon(Icons.circle_outlined, key: ValueKey('unknown')),
};
}

View File

@@ -12,9 +12,7 @@ import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:permission_handler/permission_handler.dart';
class NotificationSetting extends HookConsumerWidget {
const NotificationSetting({
super.key,
});
const NotificationSetting({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -39,14 +37,8 @@ class NotificationSetting extends HookConsumerWidget {
builder: (ctx) => AlertDialog(
content: const Text('notification_permission_dialog_content').tr(),
actions: [
TextButton(
child: const Text('cancel').tr(),
onPressed: () => ctx.pop(),
),
TextButton(
onPressed: () => openAppNotificationSettings(ctx),
child: const Text('settings').tr(),
),
TextButton(child: const Text('cancel').tr(), onPressed: () => ctx.pop()),
TextButton(onPressed: () => openAppNotificationSettings(ctx), child: const Text('settings').tr()),
],
),
);
@@ -63,10 +55,10 @@ class NotificationSetting extends HookConsumerWidget {
buttonText: 'notification_permission_list_tile_enable_button'.tr(),
onButtonTap: () =>
ref.watch(notificationPermissionProvider.notifier).requestNotificationPermission().then((permission) {
if (permission == PermissionStatus.permanentlyDenied) {
showPermissionsDialog();
}
}),
if (permission == PermissionStatus.permanentlyDenied) {
showPermissionsDialog();
}
}),
),
SettingsSwitchListTile(
enabled: hasPermission,

View File

@@ -8,9 +8,7 @@ import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
class HapticSetting extends HookConsumerWidget {
const HapticSetting({
super.key,
});
const HapticSetting({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {

View File

@@ -4,20 +4,12 @@ import 'package:immich_mobile/widgets/settings/preference_settings/theme_setting
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
class PreferenceSetting extends StatelessWidget {
const PreferenceSetting({
super.key,
});
const PreferenceSetting({super.key});
@override
Widget build(BuildContext context) {
const preferenceSettings = [
ThemeSetting(),
HapticSetting(),
];
const preferenceSettings = [ThemeSetting(), HapticSetting()];
return const SettingsSubPageScaffold(
settings: preferenceSettings,
showDivider: true,
);
return const SettingsSubPageScaffold(settings: preferenceSettings, showDivider: true);
}
}

View File

@@ -12,9 +12,7 @@ import 'package:immich_mobile/theme/dynamic_theme.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
class PrimaryColorSetting extends HookConsumerWidget {
const PrimaryColorSetting({
super.key,
});
const PrimaryColorSetting({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -70,20 +68,14 @@ class PrimaryColorSetting extends HookConsumerWidget {
Container(
height: tileSize,
width: tileSize,
decoration: BoxDecoration(
color: bottomColor,
borderRadius: const BorderRadius.all(Radius.circular(100)),
),
decoration: BoxDecoration(color: bottomColor, borderRadius: const BorderRadius.all(Radius.circular(100))),
),
Container(
height: tileSize / 2,
width: tileSize,
decoration: BoxDecoration(
color: topColor,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(100),
topRight: Radius.circular(100),
),
borderRadius: const BorderRadius.only(topLeft: Radius.circular(100), topRight: Radius.circular(100)),
),
),
if (showSelector)
@@ -99,11 +91,7 @@ class PrimaryColorSetting extends HookConsumerWidget {
),
child: const Padding(
padding: EdgeInsets.all(3),
child: Icon(
Icons.check_rounded,
color: Colors.white,
size: 25,
),
child: Icon(Icons.check_rounded, color: Colors.white, size: 25),
),
),
),
@@ -118,10 +106,7 @@ class PrimaryColorSetting extends HookConsumerWidget {
children: [
Align(
alignment: Alignment.center,
child: Text(
"theme_setting_primary_color_title".tr(),
style: context.textTheme.titleLarge,
),
child: Text("theme_setting_primary_color_title".tr(), style: context.textTheme.titleLarge),
),
if (DynamicTheme.isAvailable)
Container(
@@ -132,15 +117,10 @@ class PrimaryColorSetting extends HookConsumerWidget {
dense: true,
activeColor: context.primaryColor,
tileColor: context.colorScheme.surfaceContainerHigh,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(15)),
),
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(15))),
title: Text(
'theme_setting_system_primary_color_title'.tr(),
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
height: 1.5,
),
style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500, height: 1.5),
),
value: systemPrimaryColorSetting.value,
onChanged: onUseSystemColorChange,
@@ -175,10 +155,7 @@ class PrimaryColorSetting extends HookConsumerWidget {
context: context,
isScrollControlled: true,
builder: (BuildContext ctx) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 0),
child: bottomSheetContent(),
);
return Padding(padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 0), child: bottomSheetContent());
},
),
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
@@ -190,9 +167,7 @@ class PrimaryColorSetting extends HookConsumerWidget {
children: [
Text(
"theme_setting_primary_color_title".tr(),
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500),
),
Text(
"theme_setting_primary_color_subtitle".tr(),

View File

@@ -11,9 +11,7 @@ import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
class ThemeSetting extends HookConsumerWidget {
const ThemeSetting({
super.key,
});
const ThemeSetting({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {

View File

@@ -31,12 +31,7 @@ class SettingsButtonListTile extends StatelessWidget {
horizontalTitleGap: 20,
isThreeLine: true,
leading: Icon(icon, color: iconColor),
title: Text(
title,
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
),
title: Text(title, style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -44,9 +39,7 @@ class SettingsButtonListTile extends StatelessWidget {
if (subtileText != null)
Text(
subtileText!,
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceSecondary,
),
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
),
if (subtitle != null) subtitle!,
const SizedBox(height: 6),

View File

@@ -19,21 +19,15 @@ class SettingsCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
),
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Card(
elevation: 0,
clipBehavior: Clip.antiAlias,
color: context.colorScheme.surfaceContainer,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
),
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
margin: const EdgeInsets.symmetric(vertical: 4.0),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0,
),
contentPadding: const EdgeInsets.symmetric(horizontal: 16.0),
leading: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16)),
@@ -44,15 +38,9 @@ class SettingsCard extends StatelessWidget {
),
title: Text(
title,
style: context.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.w600,
color: context.primaryColor,
),
),
subtitle: Text(
subtitle,
style: context.textTheme.labelLarge,
style: context.textTheme.titleMedium!.copyWith(fontWeight: FontWeight.w600, color: context.primaryColor),
),
subtitle: Text(subtitle, style: context.textTheme.labelLarge),
onTap: () => context.pushRoute(settingRoute),
),
),

View File

@@ -13,12 +13,7 @@ class SettingsRadioListTile<T> extends StatelessWidget {
final T groupBy;
final void Function(T?) onRadioChanged;
const SettingsRadioListTile({
super.key,
required this.groups,
required this.groupBy,
required this.onRadioChanged,
});
const SettingsRadioListTile({super.key, required this.groups, required this.groupBy, required this.onRadioChanged});
@override
Widget build(BuildContext context) {
@@ -29,12 +24,7 @@ class SettingsRadioListTile<T> extends StatelessWidget {
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
dense: true,
activeColor: context.primaryColor,
title: Text(
g.title,
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
),
title: Text(g.title, style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500)),
value: g.value,
groupValue: groupBy,
onChanged: onRadioChanged,

View File

@@ -28,12 +28,7 @@ class SettingsSliderListTile extends StatelessWidget {
return ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
dense: true,
title: Text(
text,
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
),
title: Text(text, style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500)),
subtitle: Slider(
value: valueNotifier.value.toDouble(),
onChanged: (double v) => valueNotifier.value = v.toInt(),

View File

@@ -4,11 +4,7 @@ class SettingsSubPageScaffold extends StatelessWidget {
final List<Widget> settings;
final bool showDivider;
const SettingsSubPageScaffold({
super.key,
required this.settings,
this.showDivider = false,
});
const SettingsSubPageScaffold({super.key, required this.settings, this.showDivider = false});
@override
Widget build(BuildContext context) {
@@ -18,11 +14,7 @@ class SettingsSubPageScaffold extends StatelessWidget {
itemBuilder: (ctx, index) => settings[index],
separatorBuilder: (context, index) => showDivider
? const Column(
children: [
SizedBox(height: 5),
Divider(height: 10, indent: 15, endIndent: 15),
SizedBox(height: 15),
],
children: [SizedBox(height: 5), Divider(height: 10, indent: 15, endIndent: 15), SizedBox(height: 15)],
)
: const SizedBox(height: 10),
);

View File

@@ -4,10 +4,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
class SettingsSubTitle extends StatelessWidget {
final String title;
const SettingsSubTitle({
super.key,
required this.title,
});
const SettingsSubTitle({super.key, required this.title});
@override
Widget build(BuildContext context) {
@@ -15,10 +12,7 @@ class SettingsSubTitle extends StatelessWidget {
padding: const EdgeInsets.only(left: 20),
child: Text(
title,
style: context.textTheme.bodyLarge?.copyWith(
color: context.primaryColor,
fontWeight: FontWeight.w700,
),
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor, fontWeight: FontWeight.w700),
),
);
}

View File

@@ -42,15 +42,11 @@ class SettingsSwitchListTile extends StatelessWidget {
onChanged: onSwitchChanged,
activeColor: enabled ? context.primaryColor : context.themeData.disabledColor,
dense: true,
secondary: icon != null
? Icon(
icon!,
color: valueNotifier.value ? context.primaryColor : null,
)
: null,
secondary: icon != null ? Icon(icon!, color: valueNotifier.value ? context.primaryColor : null) : null,
title: Text(
title,
style: titleStyle ??
style:
titleStyle ??
context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
color: enabled ? null : context.themeData.disabledColor,
@@ -60,7 +56,8 @@ class SettingsSwitchListTile extends StatelessWidget {
subtitle: subtitle != null
? Text(
subtitle!,
style: subtitleStyle ??
style:
subtitleStyle ??
context.textTheme.bodyMedium?.copyWith(
color: enabled ? context.colorScheme.onSurfaceSecondary : context.themeData.disabledColor,
),

View File

@@ -30,24 +30,15 @@ class _SslClientCertSettingsState extends State<SslClientCertSettings> {
contentPadding: const EdgeInsets.symmetric(horizontal: 20),
horizontalTitleGap: 20,
isThreeLine: true,
title: Text(
"client_cert_title".tr(),
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
),
title: Text("client_cert_title".tr(), style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500)),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"client_cert_subtitle".tr(),
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurfaceSecondary,
),
),
const SizedBox(
height: 6,
style: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
),
const SizedBox(height: 6),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
@@ -57,9 +48,7 @@ class _SslClientCertSettingsState extends State<SslClientCertSettings> {
onPressed: widget.isLoggedIn ? null : () => importCert(context),
child: Text("client_cert_import".tr()),
),
const SizedBox(
width: 15,
),
const SizedBox(width: 15),
ElevatedButton(
onPressed: widget.isLoggedIn || !isCertExist ? null : () async => await removeCert(context),
child: Text("remove".tr()),
@@ -76,39 +65,25 @@ class _SslClientCertSettingsState extends State<SslClientCertSettings> {
context: context,
builder: (ctx) => AlertDialog(
content: Text(message),
actions: [
TextButton(
onPressed: () => ctx.pop(),
child: Text("client_cert_dialog_msg_confirm".tr()),
),
],
actions: [TextButton(onPressed: () => ctx.pop(), child: Text("client_cert_dialog_msg_confirm".tr()))],
),
);
}
Future<void> storeCert(
BuildContext context,
Uint8List data,
String? password,
) async {
Future<void> storeCert(BuildContext context, Uint8List data, String? password) async {
if (password != null && password.isEmpty) {
password = null;
}
final cert = SSLClientCertStoreVal(data, password);
// Test whether the certificate is valid
final isCertValid = HttpSSLCertOverride.setClientCert(
SecurityContext(withTrustedRoots: true),
cert,
);
final isCertValid = HttpSSLCertOverride.setClientCert(SecurityContext(withTrustedRoots: true), cert);
if (!isCertValid) {
showMessage(context, "client_cert_invalid_msg".tr());
return;
}
await cert.save();
HttpSSLOptions.apply();
setState(
() => isCertExist = true,
);
setState(() => isCertExist = true);
showMessage(context, "client_cert_import_success_msg".tr());
}
@@ -122,9 +97,7 @@ class _SslClientCertSettingsState extends State<SslClientCertSettings> {
controller: password,
obscureText: true,
obscuringCharacter: "*",
decoration: InputDecoration(
hintText: "client_cert_enter_password".tr(),
),
decoration: InputDecoration(hintText: "client_cert_enter_password".tr()),
),
actions: [
TextButton(
@@ -139,10 +112,7 @@ class _SslClientCertSettingsState extends State<SslClientCertSettings> {
Future<void> importCert(BuildContext ctx) async {
FilePickerResult? res = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: [
'p12',
'pfx',
],
allowedExtensions: ['p12', 'pfx'],
);
if (res != null) {
File file = File(res.files.single.path!);
@@ -154,9 +124,7 @@ class _SslClientCertSettingsState extends State<SslClientCertSettings> {
Future<void> removeCert(BuildContext context) async {
await SSLClientCertStoreVal.delete();
HttpSSLOptions.apply();
setState(
() => isCertExist = false,
);
setState(() => isCertExist = false);
showMessage(context, "client_cert_remove_msg".tr());
}
}