mirror of
https://github.com/immich-app/immich.git
synced 2025-11-25 15:55:17 -05:00
* feature(mobile, beta, Android): handle remote asset trash/restore events and rescan media - Handle move to trash and restore from trash for remote assets on Android - Trigger MediaScannerConnection to rescan affected media files * feature(mobile, beta, Android): fix rescan * fix imports * fix checking conditions * refactor naming * fix line breaks * refactor code rollback changes in BackgroundServicePlugin * refactor code (use separate TrashService) * refactor code * parallelize restoreFromTrash calls with Future.wait format trash.provider.dart * try to re-format trash.provider.dart * re-format trash.provider.dart * rename TrashService to TrashSyncService to avoid duplicated names revert changes in original trash.provider.dart * refactor code (minor nitpicks) * process restoreFromTrash sequentially instead of Future.wait * group local assets by checksum before moving to trash delete LocalAssetEntity records when moved to trash refactor code * fix format * use checksum for asset restoration refactro code * fix format * sync trash only for backup-selected assets * feat(db): add local_trashed_asset table and integrate with restoration flow - Add new `local_trashed_asset` table to store metadata of trashed assets - Save trashed asset info into `local_trashed_asset` before deletion - Use `local_trashed_asset` as source for asset restoration - Implement file restoration by `mediaId` * resolve merge conflicts * fix index creating on migration * rework trashed assets handling - add new table trashed_local_asset - mirror trashed assets data in trashed_local_asset. - compute checksums for assets trashed out-of-app. - restore assets present in trashed_local_asset and non-trashed in remote_asset. - simplify moving-to-trash logic based on remote_asset events. * resolve merge conflicts use updated approach for calculating checksums * use CurrentPlatform instead _platform fix mocks * revert redundant changes * Include trashed items in getMediaChanges Process trashed items delta during incremental sync * fix merge conflicts * fix format * trashed_local_asset table mirror of local_asset table structure trashed_local_asset<->local_asset transfer data on move to trash or restore refactor code * refactor and format code * refactor TrashedAsset model fix missed data transfering * refactor code remove unused model * fix label * fix merge conflicts * optimize, refactor code remove redundant code and checking getTrashedAssetsForAlbum for iOS tests for hash trashed assets * format code * fix migration fix tests * fix generated file * reuse exist checksums on trash data update handle restoration errors fix import * format code * sync_stream.service depend on repos refactor assets restoration update dependencies in tests * remove trashed asset model remove trash_sync.service refactor DriftTrashedLocalAssetRepository, LocalSyncService * rework fetching trashed assets data on native side optimize handling trashed assets in local sync service refactor code * update NativeSyncApi on iOS side remove unused code * optimize sync trashed assets call in full sync mode refactor code * fix format * remove albumIds from getTrashedAssets params fix upsert in trashed local asset repo refactor code * fix getTrashedAssets params * fix(trash-sync): clean up NativeSyncApiImplBase and correct applyDelta * refactor(trash-sync): optimize performance and fix minor issues * refactor(trash-sync): add missed index * feat(trash-sync): remove sinceLastCheckpoint param from getTrashedAssets * fix(trash-sync): fix target table * fix(trash-sync): remove unused extension * fix(trash-sync): remove unused code * fix(trash-sync): refactor code * fix(trash-sync): reformat file * fix(trash_sync): refactor code * fix(trash_sync): improve moving to trash * refactor(trash_sync): integrate MANAGE_MEDIA permission request into login flow and advanced settings * refactor(trash_sync): add additional checking for experimental trash sync flag and MANAGE_MEDIA permission. * refactor(trash_sync): resolve merge conflicts * refactor(trash_sync): fix format * resolve merge conflicts add await for alert dialog add missed request * refactor(trash_sync): rework MANAGE_MEDIA info widget show rationale text in permission request alert dialog refactor setting getter * fix(trash_sync): restore missing text values * fix(trash_sync): format file * fix(trash_sync): check backup enabled and remove remote asset existence check * fix(trash_sync): remove checking backup enabled test(trash_sync): cover sync-stream trash/restore paths and dedupe mocks * test(trash_sync): cover trash/restore flows for local_sync_service * chore(e2e): restore test-assets submodule pointer --------- Co-authored-by: Peter Ombodi <peter.ombodi@gmail.com> Co-authored-by: Alex <alex.tran1502@gmail.com>
161 lines
7.1 KiB
Dart
161 lines
7.1 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:device_info_plus/device_info_plus.dart';
|
|
import 'package:easy_localization/easy_localization.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
|
import 'package:immich_mobile/entities/store.entity.dart';
|
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
|
import 'package:immich_mobile/providers/user.provider.dart';
|
|
import 'package:immich_mobile/repositories/local_files_manager.repository.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/http_ssl_options.dart';
|
|
import 'package:immich_mobile/widgets/settings/beta_timeline_list_tile.dart';
|
|
import 'package:immich_mobile/widgets/settings/custom_proxy_headers_settings/custom_proxy_headers_settings.dart';
|
|
import 'package:immich_mobile/widgets/settings/local_storage_settings.dart';
|
|
import 'package:immich_mobile/widgets/settings/settings_action_tile.dart';
|
|
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';
|
|
import 'package:immich_mobile/widgets/settings/ssl_client_cert_settings.dart';
|
|
import 'package:logging/logging.dart';
|
|
|
|
class AdvancedSettings extends HookConsumerWidget {
|
|
const AdvancedSettings({super.key});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
bool isLoggedIn = ref.read(currentUserProvider) != null;
|
|
|
|
final advancedTroubleshooting = useAppSettingsState(AppSettingsEnum.advancedTroubleshooting);
|
|
final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
|
|
final isManageMediaSupported = useState(false);
|
|
final manageMediaAndroidPermission = useState(false);
|
|
final levelId = useAppSettingsState(AppSettingsEnum.logLevel);
|
|
final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage);
|
|
final allowSelfSignedSSLCert = useAppSettingsState(AppSettingsEnum.allowSelfSignedSSLCert);
|
|
final useAlternatePMFilter = useAppSettingsState(AppSettingsEnum.photoManagerCustomFilter);
|
|
final readonlyModeEnabled = useAppSettingsState(AppSettingsEnum.readonlyModeEnabled);
|
|
|
|
final logLevel = Level.LEVELS[levelId.value].name;
|
|
|
|
useValueChanged(levelId.value, (_, __) => LogService.I.setLogLevel(Level.LEVELS[levelId.value].toLogLevel()));
|
|
|
|
Future<bool> checkAndroidVersion() async {
|
|
if (Platform.isAndroid) {
|
|
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
|
|
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
|
|
int sdkVersion = androidInfo.version.sdkInt;
|
|
return sdkVersion >= 31;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
useEffect(() {
|
|
() async {
|
|
isManageMediaSupported.value = await checkAndroidVersion();
|
|
if (isManageMediaSupported.value) {
|
|
manageMediaAndroidPermission.value = await ref
|
|
.read(localFilesManagerRepositoryProvider)
|
|
.hasManageMediaPermission();
|
|
}
|
|
}();
|
|
return null;
|
|
}, []);
|
|
|
|
final advancedSettings = [
|
|
SettingsSwitchListTile(
|
|
enabled: true,
|
|
valueNotifier: advancedTroubleshooting,
|
|
title: "advanced_settings_troubleshooting_title".tr(),
|
|
subtitle: "advanced_settings_troubleshooting_subtitle".tr(),
|
|
),
|
|
if (isManageMediaSupported.value)
|
|
Column(
|
|
children: [
|
|
SettingsSwitchListTile(
|
|
enabled: true,
|
|
valueNotifier: manageLocalMediaAndroid,
|
|
title: "advanced_settings_sync_remote_deletions_title".tr(),
|
|
subtitle: "advanced_settings_sync_remote_deletions_subtitle".tr(),
|
|
onChanged: (value) async {
|
|
if (value) {
|
|
final result = await ref.read(localFilesManagerRepositoryProvider).requestManageMediaPermission();
|
|
manageLocalMediaAndroid.value = result;
|
|
manageMediaAndroidPermission.value = result;
|
|
}
|
|
},
|
|
),
|
|
SettingsActionTile(
|
|
title: "manage_media_access_title".tr(),
|
|
statusText: manageMediaAndroidPermission.value ? "allowed".tr() : "not_allowed".tr(),
|
|
subtitle: "manage_media_access_rationale".tr(),
|
|
statusColor: manageLocalMediaAndroid.value && !manageMediaAndroidPermission.value
|
|
? const Color.fromARGB(255, 243, 188, 106)
|
|
: null,
|
|
onActionTap: () async {
|
|
final result = await ref.read(localFilesManagerRepositoryProvider).manageMediaPermission();
|
|
manageMediaAndroidPermission.value = result;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
SettingsSliderListTile(
|
|
text: "advanced_settings_log_level_title".tr(namedArgs: {'level': logLevel}),
|
|
valueNotifier: levelId,
|
|
maxValue: 8,
|
|
minValue: 1,
|
|
noDivisons: 7,
|
|
label: logLevel,
|
|
),
|
|
SettingsSwitchListTile(
|
|
valueNotifier: preferRemote,
|
|
title: "advanced_settings_prefer_remote_title".tr(),
|
|
subtitle: "advanced_settings_prefer_remote_subtitle".tr(),
|
|
),
|
|
if (!Store.isBetaTimelineEnabled) const LocalStorageSettings(),
|
|
SettingsSwitchListTile(
|
|
enabled: !isLoggedIn,
|
|
valueNotifier: allowSelfSignedSSLCert,
|
|
title: "advanced_settings_self_signed_ssl_title".tr(),
|
|
subtitle: "advanced_settings_self_signed_ssl_subtitle".tr(),
|
|
onChanged: HttpSSLOptions.applyFromSettings,
|
|
),
|
|
const CustomProxyHeaderSettings(),
|
|
SslClientCertSettings(isLoggedIn: ref.read(currentUserProvider) != null),
|
|
if (!Store.isBetaTimelineEnabled)
|
|
SettingsSwitchListTile(
|
|
valueNotifier: useAlternatePMFilter,
|
|
title: "advanced_settings_enable_alternate_media_filter_title".tr(),
|
|
subtitle: "advanced_settings_enable_alternate_media_filter_subtitle".tr(),
|
|
),
|
|
const BetaTimelineListTile(),
|
|
if (Store.isBetaTimelineEnabled)
|
|
SettingsSwitchListTile(
|
|
valueNotifier: readonlyModeEnabled,
|
|
title: "advanced_settings_readonly_mode_title".tr(),
|
|
subtitle: "advanced_settings_readonly_mode_subtitle".tr(),
|
|
onChanged: (value) {
|
|
readonlyModeEnabled.value = value;
|
|
ref.read(readonlyModeProvider.notifier).setReadonlyMode(value);
|
|
context.scaffoldMessenger.showSnackBar(
|
|
SnackBar(
|
|
duration: const Duration(seconds: 2),
|
|
content: Text(
|
|
(value ? "readonly_mode_enabled" : "readonly_mode_disabled").tr(),
|
|
style: context.textTheme.bodyLarge?.copyWith(color: context.primaryColor),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
];
|
|
|
|
return SettingsSubPageScaffold(settings: advancedSettings);
|
|
}
|
|
}
|