2026-07-30 12:39:24 -07:00
|
|
|
import 'dart:async';
|
2024-03-12 14:56:08 +00:00
|
|
|
import 'dart:io';
|
2025-02-28 01:48:49 +05:30
|
|
|
|
2025-04-25 05:39:50 +05:30
|
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
2024-03-12 14:56:08 +00:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
2025-02-28 01:48:49 +05:30
|
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
|
|
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
2025-08-28 13:30:15 -04:00
|
|
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
2026-08-11 14:41:12 +05:30
|
|
|
import 'package:immich_mobile/generated/translations.g.dart';
|
2026-01-24 14:34:29 -05:00
|
|
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
2025-08-28 13:30:15 -04:00
|
|
|
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
2026-07-30 01:56:46 -07:00
|
|
|
import 'package:immich_mobile/providers/infrastructure/settings.provider.dart';
|
2026-05-22 15:10:11 +03:00
|
|
|
import 'package:immich_mobile/repositories/permission.repository.dart';
|
2025-02-28 01:48:49 +05:30
|
|
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
2026-01-24 14:34:29 -05:00
|
|
|
import 'package:immich_mobile/utils/bytes_units.dart';
|
2025-02-28 01:48:49 +05:30
|
|
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
2025-10-20 15:20:49 -04:00
|
|
|
import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart';
|
2025-11-10 18:20:51 +02:00
|
|
|
import 'package:immich_mobile/widgets/settings/settings_action_tile.dart';
|
2024-05-06 23:04:21 -05:00
|
|
|
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
|
|
|
|
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
|
|
|
|
|
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
2024-07-26 14:59:02 +01:00
|
|
|
import 'package:immich_mobile/widgets/settings/ssl_client_cert_settings.dart';
|
2024-03-12 14:56:08 +00:00
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
|
|
|
|
|
|
class AdvancedSettings extends HookConsumerWidget {
|
|
|
|
|
const AdvancedSettings({super.key});
|
2025-11-10 18:20:51 +02:00
|
|
|
|
2024-03-12 14:56:08 +00:00
|
|
|
@override
|
|
|
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
2025-07-25 08:07:22 +05:30
|
|
|
final advancedTroubleshooting = useAppSettingsState(AppSettingsEnum.advancedTroubleshooting);
|
|
|
|
|
final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
|
2025-11-10 18:20:51 +02:00
|
|
|
final isManageMediaSupported = useState(false);
|
|
|
|
|
final manageMediaAndroidPermission = useState(false);
|
2026-08-05 01:26:49 +05:30
|
|
|
final levelId = useState<int>(ref.watch(appConfigProvider).logLevel.index);
|
|
|
|
|
final preferRemote = useState(ref.watch(appConfigProvider).image.preferRemote);
|
2026-05-13 04:50:35 +07:00
|
|
|
useValueChanged(
|
|
|
|
|
preferRemote.value,
|
2026-08-15 16:55:45 +05:30
|
|
|
(_, _) => unawaited(ref.read(settingsProvider).write(.imagePreferRemote, preferRemote.value)),
|
2026-05-13 04:50:35 +07:00
|
|
|
);
|
2025-08-28 13:30:15 -04:00
|
|
|
final readonlyModeEnabled = useAppSettingsState(AppSettingsEnum.readonlyModeEnabled);
|
2024-03-12 14:56:08 +00:00
|
|
|
|
|
|
|
|
final logLevel = Level.LEVELS[levelId.value].name;
|
|
|
|
|
|
2026-07-30 12:39:24 -07:00
|
|
|
useValueChanged(
|
|
|
|
|
levelId.value,
|
2026-08-15 16:55:45 +05:30
|
|
|
(_, _) => unawaited(LogService.I.setLogLevel(Level.LEVELS[levelId.value].toLogLevel())),
|
2026-07-30 12:39:24 -07:00
|
|
|
);
|
2024-03-12 14:56:08 +00:00
|
|
|
|
2025-04-25 05:39:50 +05:30
|
|
|
Future<bool> checkAndroidVersion() async {
|
|
|
|
|
if (Platform.isAndroid) {
|
2026-07-30 01:56:46 -07:00
|
|
|
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
|
|
|
|
final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
|
|
|
|
final int sdkVersion = androidInfo.version.sdkInt;
|
2025-04-25 05:39:50 +05:30
|
|
|
return sdkVersion >= 31;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-10 18:20:51 +02:00
|
|
|
useEffect(() {
|
2026-07-30 12:39:24 -07:00
|
|
|
unawaited(() async {
|
2025-11-10 18:20:51 +02:00
|
|
|
isManageMediaSupported.value = await checkAndroidVersion();
|
2026-08-05 01:26:49 +05:30
|
|
|
if (isManageMediaSupported.value && context.mounted) {
|
2026-05-22 15:10:11 +03:00
|
|
|
manageMediaAndroidPermission.value = await ref.read(permissionRepositoryProvider).hasManageMediaPermission();
|
2025-11-10 18:20:51 +02:00
|
|
|
}
|
2026-07-30 12:39:24 -07:00
|
|
|
}());
|
2025-11-10 18:20:51 +02:00
|
|
|
return null;
|
|
|
|
|
}, []);
|
|
|
|
|
|
2024-03-12 14:56:08 +00:00
|
|
|
final advancedSettings = [
|
|
|
|
|
SettingsSwitchListTile(
|
|
|
|
|
enabled: true,
|
|
|
|
|
valueNotifier: advancedTroubleshooting,
|
2026-08-11 14:41:12 +05:30
|
|
|
title: context.t.advanced_settings_troubleshooting_title,
|
|
|
|
|
subtitle: context.t.advanced_settings_troubleshooting_subtitle,
|
2024-03-12 14:56:08 +00:00
|
|
|
),
|
2025-11-10 18:20:51 +02:00
|
|
|
if (isManageMediaSupported.value)
|
|
|
|
|
Column(
|
|
|
|
|
children: [
|
|
|
|
|
SettingsSwitchListTile(
|
2025-04-25 05:39:50 +05:30
|
|
|
enabled: true,
|
|
|
|
|
valueNotifier: manageLocalMediaAndroid,
|
2026-08-11 14:41:12 +05:30
|
|
|
title: context.t.advanced_settings_sync_remote_deletions_title,
|
|
|
|
|
subtitle: context.t.advanced_settings_sync_remote_deletions_subtitle,
|
2025-04-25 05:39:50 +05:30
|
|
|
onChanged: (value) async {
|
|
|
|
|
if (value) {
|
2026-05-22 15:10:11 +03:00
|
|
|
final result = await ref.read(permissionRepositoryProvider).requestManageMediaPermission();
|
2025-04-25 05:39:50 +05:30
|
|
|
manageLocalMediaAndroid.value = result;
|
2025-11-10 18:20:51 +02:00
|
|
|
manageMediaAndroidPermission.value = result;
|
2025-04-25 05:39:50 +05:30
|
|
|
}
|
|
|
|
|
},
|
2025-11-10 18:20:51 +02:00
|
|
|
),
|
|
|
|
|
SettingsActionTile(
|
2026-08-11 14:41:12 +05:30
|
|
|
title: context.t.manage_media_access_title,
|
|
|
|
|
statusText: manageMediaAndroidPermission.value ? context.t.allowed : context.t.not_allowed,
|
|
|
|
|
subtitle: context.t.manage_media_access_rationale,
|
2025-11-10 18:20:51 +02:00
|
|
|
statusColor: manageLocalMediaAndroid.value && !manageMediaAndroidPermission.value
|
|
|
|
|
? const Color.fromARGB(255, 243, 188, 106)
|
|
|
|
|
: null,
|
|
|
|
|
onActionTap: () async {
|
2026-05-22 15:10:11 +03:00
|
|
|
final result = await ref.read(permissionRepositoryProvider).manageMediaPermission();
|
2025-11-10 18:20:51 +02:00
|
|
|
manageMediaAndroidPermission.value = result;
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
2024-03-12 14:56:08 +00:00
|
|
|
SettingsSliderListTile(
|
2026-08-11 14:41:12 +05:30
|
|
|
text: context.t.advanced_settings_log_level_title(level: logLevel),
|
2024-03-12 14:56:08 +00:00
|
|
|
valueNotifier: levelId,
|
|
|
|
|
maxValue: 8,
|
|
|
|
|
minValue: 1,
|
|
|
|
|
noDivisons: 7,
|
|
|
|
|
label: logLevel,
|
|
|
|
|
),
|
|
|
|
|
SettingsSwitchListTile(
|
|
|
|
|
valueNotifier: preferRemote,
|
2026-08-11 14:41:12 +05:30
|
|
|
title: context.t.advanced_settings_prefer_remote_title,
|
|
|
|
|
subtitle: context.t.advanced_settings_prefer_remote_subtitle,
|
2024-03-12 14:56:08 +00:00
|
|
|
),
|
2025-10-20 15:20:49 -04:00
|
|
|
const CustomProxyHeaderSettings(),
|
2026-03-05 12:04:45 -05:00
|
|
|
const SslClientCertSettings(),
|
2026-04-15 23:00:27 +05:30
|
|
|
SettingsSwitchListTile(
|
|
|
|
|
valueNotifier: readonlyModeEnabled,
|
2026-08-11 14:41:12 +05:30
|
|
|
title: context.t.advanced_settings_readonly_mode_title,
|
|
|
|
|
subtitle: context.t.advanced_settings_readonly_mode_subtitle,
|
2026-04-15 23:00:27 +05:30
|
|
|
onChanged: (value) {
|
|
|
|
|
readonlyModeEnabled.value = value;
|
|
|
|
|
ref.read(readonlyModeProvider.notifier).setReadonlyMode(value);
|
|
|
|
|
context.scaffoldMessenger.showSnackBar(
|
|
|
|
|
SnackBar(
|
|
|
|
|
duration: const Duration(seconds: 2),
|
|
|
|
|
content: Text(
|
2026-08-11 14:41:12 +05:30
|
|
|
value ? context.t.readonly_mode_enabled : context.t.readonly_mode_disabled,
|
2026-04-15 23:00:27 +05:30
|
|
|
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor),
|
2025-08-28 13:30:15 -04:00
|
|
|
),
|
2026-04-15 23:00:27 +05:30
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2026-01-24 14:34:29 -05:00
|
|
|
ListTile(
|
2026-08-11 14:41:12 +05:30
|
|
|
title: Text(context.t.advanced_settings_clear_image_cache, style: const TextStyle(fontWeight: FontWeight.w500)),
|
2026-01-24 14:34:29 -05:00
|
|
|
leading: const Icon(Icons.playlist_remove_rounded),
|
|
|
|
|
onTap: () async {
|
|
|
|
|
final int clearedBytes;
|
|
|
|
|
try {
|
|
|
|
|
clearedBytes = await remoteImageApi.clearCache();
|
|
|
|
|
} catch (e) {
|
2026-07-31 01:40:36 +05:30
|
|
|
if (!context.mounted) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 14:34:29 -05:00
|
|
|
context.scaffoldMessenger.showSnackBar(
|
|
|
|
|
SnackBar(
|
|
|
|
|
duration: const Duration(seconds: 2),
|
|
|
|
|
content: Text(
|
2026-08-11 14:41:12 +05:30
|
|
|
context.t.advanced_settings_clear_image_cache_error,
|
2026-01-24 14:34:29 -05:00
|
|
|
style: context.textTheme.bodyLarge?.copyWith(color: context.themeData.colorScheme.error),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-31 01:40:36 +05:30
|
|
|
if (clearedBytes < 0 || !context.mounted) {
|
2026-01-24 14:34:29 -05:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// iOS always returns a small non-zero value
|
|
|
|
|
final clearedMB = clearedBytes < (256 * 1024) ? "0 MiB" : formatHumanReadableBytes(clearedBytes, 2);
|
|
|
|
|
context.scaffoldMessenger.showSnackBar(
|
|
|
|
|
SnackBar(
|
|
|
|
|
duration: const Duration(seconds: 2),
|
|
|
|
|
content: Text(
|
2026-08-11 14:41:12 +05:30
|
|
|
context.t.advanced_settings_clear_image_cache_success(size: clearedMB),
|
2026-01-24 14:34:29 -05:00
|
|
|
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2026-01-24 10:40:34 -06:00
|
|
|
const SizedBox(height: 60),
|
2024-03-12 14:56:08 +00:00
|
|
|
];
|
|
|
|
|
|
|
|
|
|
return SettingsSubPageScaffold(settings: advancedSettings);
|
|
|
|
|
}
|
|
|
|
|
}
|