Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -597,8 +597,6 @@
"backup_setting_subtitle": "Manage background and foreground upload settings",
"backup_settings_subtitle": "Manage upload settings",
"backward": "Backward",
"beta_sync": "Beta Sync Status",
"beta_sync_subtitle": "Manage the new sync system",
"biometric_auth_enabled": "Biometric authentication enabled",
"biometric_locked_out": "You are locked out of biometric authentication",
"biometric_no_options": "No biometric options available",
Expand Down Expand Up @@ -1923,6 +1921,8 @@
"sync_albums_manual_subtitle": "Sync all uploaded videos and photos to the selected backup albums",
"sync_local": "Sync Local",
"sync_remote": "Sync Remote",
"sync_status": "Sync Status",
"sync_status_subtitle": "View and manage the sync system",
"sync_upload_album_setting_subtitle": "Create and upload your photos and videos to the selected albums on Immich",
"tag": "Tag",
"tag_assets": "Tag assets",
Expand Down
3 changes: 2 additions & 1 deletion mobile/lib/domain/models/store.model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ enum StoreKey<T> {
betaTimeline<bool>._(1002),
enableBackup<bool>._(1003),
useWifiForUploadVideos<bool>._(1004),
useWifiForUploadPhotos<bool>._(1005);
useWifiForUploadPhotos<bool>._(1005),
needBetaMigration<bool>._(1006);

const StoreKey._(this.id);
final int id;
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/domain/services/store.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class StoreService {
_cache.clear();
}

bool get isBetaTimelineEnabled => tryGet(StoreKey.betaTimeline) ?? false;
bool get isBetaTimelineEnabled => tryGet(StoreKey.betaTimeline) ?? true;
}

class StoreKeyNotFoundException implements Exception {
Expand Down
12 changes: 6 additions & 6 deletions mobile/lib/pages/common/settings.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_se
import 'package:immich_mobile/widgets/settings/asset_viewer_settings/asset_viewer_settings.dart';
import 'package:immich_mobile/widgets/settings/backup_settings/backup_settings.dart';
import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart';
import 'package:immich_mobile/widgets/settings/beta_sync_settings/beta_sync_settings.dart';
import 'package:immich_mobile/widgets/settings/beta_sync_settings/sync_status_and_actions.dart';
import 'package:immich_mobile/widgets/settings/beta_timeline_list_tile.dart';
import 'package:immich_mobile/widgets/settings/language_settings.dart';
import 'package:immich_mobile/widgets/settings/networking_settings/networking_settings.dart';
Expand All @@ -20,7 +20,7 @@ import 'package:immich_mobile/widgets/settings/preference_settings/preference_se
import 'package:immich_mobile/widgets/settings/settings_card.dart';

enum SettingSection {
beta('beta_sync', Icons.sync_outlined, "beta_sync_subtitle"),
beta('sync_status', Icons.sync_outlined, "sync_status_subtitle"),
advanced('advanced', Icons.build_outlined, "advanced_settings_tile_subtitle"),
assetViewer('asset_viewer_settings_title', Icons.image_outlined, "asset_viewer_settings_subtitle"),
backup('backup', Icons.cloud_upload_outlined, "backup_settings_subtitle"),
Expand Down Expand Up @@ -76,9 +76,9 @@ class _MobileLayout extends StatelessWidget {
if (Store.isBetaTimelineEnabled)
SettingsCard(
icon: Icons.sync_outlined,
title: 'beta_sync'.tr(),
subtitle: 'beta_sync_subtitle'.tr(),
settingRoute: const BetaSyncSettingsRoute(),
title: 'sync_status'.tr(),
subtitle: 'sync_status_subtitle'.tr(),
settingRoute: const SyncStatusRoute(),
),
]
: [
Expand Down Expand Up @@ -143,7 +143,7 @@ class _BetaLandscapeToggle extends HookWidget {
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(height: 100, child: BetaTimelineListTile()),
if (Store.isBetaTimelineEnabled) const Expanded(child: BetaSyncSettings()),
if (Store.isBetaTimelineEnabled) const Expanded(child: SyncStatusAndActions()),
],
);
}
Expand Down
9 changes: 9 additions & 0 deletions mobile/lib/pages/common/splash_screen.page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,16 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
return;
}

// clean install - change the default of the flag
// current install not using beta timeline
if (context.router.current.name == SplashScreenRoute.name) {
final needBetaMigration = Store.get(StoreKey.needBetaMigration, false);
if (needBetaMigration) {
await Store.put(StoreKey.needBetaMigration, false);
context.router.replaceAll([ChangeExperienceRoute(switchingToBeta: true)]);
return;
}

context.replaceRoute(Store.isBetaTimelineEnabled ? const TabShellRoute() : const TabControllerRoute());
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/widgets/settings/beta_sync_settings/beta_sync_settings.dart';
import 'package:immich_mobile/widgets/settings/beta_sync_settings/sync_status_and_actions.dart';

@RoutePage()
class BetaSyncSettingsPage extends StatelessWidget {
const BetaSyncSettingsPage({super.key});
class SyncStatusPage extends StatelessWidget {
const SyncStatusPage({super.key});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
title: const Text("beta_sync").t(context: context),
title: const Text("sync_status").t(context: context),
leading: IconButton(
onPressed: () => context.maybePop(true),
splashRadius: 24,
icon: const Icon(Icons.arrow_back_ios_rounded),
),
),
body: const BetaSyncSettings(),
body: const SyncStatusAndActions(),
);
}
}
4 changes: 2 additions & 2 deletions mobile/lib/routing/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ import 'package:immich_mobile/pages/search/map/map_location_picker.page.dart';
import 'package:immich_mobile/pages/search/person_result.page.dart';
import 'package:immich_mobile/pages/search/recently_taken.page.dart';
import 'package:immich_mobile/pages/search/search.page.dart';
import 'package:immich_mobile/pages/settings/beta_sync_settings.page.dart';
import 'package:immich_mobile/pages/settings/sync_status.page.dart';
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart';
Expand Down Expand Up @@ -332,7 +332,7 @@ class AppRouter extends RootStackRouter {
AutoRoute(page: ChangeExperienceRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: DriftPartnerRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: DriftUploadDetailRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: BetaSyncSettingsRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: SyncStatusRoute.page, guards: [_duplicateGuard]),
AutoRoute(page: DriftPeopleCollectionRoute.page, guards: [_authGuard, _duplicateGuard]),
AutoRoute(page: DriftPersonRoute.page, guards: [_authGuard]),
AutoRoute(page: DriftBackupOptionsRoute.page, guards: [_authGuard, _duplicateGuard]),
Expand Down
32 changes: 16 additions & 16 deletions mobile/lib/routing/router.gr.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion mobile/lib/services/app_settings.service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ enum AppSettingsEnum<T> {
syncAlbums<bool>(StoreKey.syncAlbums, null, false),
autoEndpointSwitching<bool>(StoreKey.autoEndpointSwitching, null, false),
photoManagerCustomFilter<bool>(StoreKey.photoManagerCustomFilter, null, true),
betaTimeline<bool>(StoreKey.betaTimeline, null, false),
betaTimeline<bool>(StoreKey.betaTimeline, null, true),
enableBackup<bool>(StoreKey.enableBackup, null, false),
useCellularForUploadVideos<bool>(StoreKey.useWifiForUploadVideos, null, false),
useCellularForUploadPhotos<bool>(StoreKey.useWifiForUploadPhotos, null, false),
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/utils/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ abstract final class Bootstrap {
}

static Future<void> initDomain(Isar db, Drift drift, DriftLogger logDb, {bool shouldBufferLogs = true}) async {
final isBeta = await IsarStoreRepository(db).tryGet(StoreKey.betaTimeline) ?? false;
final isBeta = await IsarStoreRepository(db).tryGet(StoreKey.betaTimeline) ?? true;
final IStoreRepository storeRepo = isBeta ? DriftStoreRepository(drift) : IsarStoreRepository(db);

await StoreService.init(storeRepository: storeRepo);
Expand Down
72 changes: 70 additions & 2 deletions mobile/lib/utils/migration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,11 @@ import 'package:logging/logging.dart';
// ignore: import_rule_photo_manager
import 'package:photo_manager/photo_manager.dart';

const int targetVersion = 14;
const int targetVersion = 15;

Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
final hasVersion = Store.tryGet(StoreKey.version) != null;
final int version = Store.get(StoreKey.version, targetVersion);

if (version < 9) {
await Store.put(StoreKey.version, targetVersion);
final value = await db.storeValues.get(StoreKey.currentUser.id);
Expand Down Expand Up @@ -68,6 +67,22 @@ Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
await Store.populateCache();
}

// Handle migration only for this version
// TODO: remove when old timeline is removed
if (version == 15) {
final isBeta = Store.tryGet(StoreKey.betaTimeline);
final isNewInstallation = await _isNewInstallation(db, drift);

// For new installations, no migration needed
// For existing installations, only migrate if beta timeline is not enabled (null or false)
if (isNewInstallation || isBeta == true) {
await Store.put(StoreKey.needBetaMigration, false);
} else {
await resetDriftDatabase(drift);
await Store.put(StoreKey.needBetaMigration, true);
}
}

if (targetVersion >= 12) {
await Store.put(StoreKey.version, targetVersion);
return;
Expand All @@ -80,6 +95,35 @@ Future<void> migrateDatabaseIfNeeded(Isar db, Drift drift) async {
}
}

Future<bool> _isNewInstallation(Isar db, Drift drift) async {
try {
final isarUserCount = await db.users.count();
if (isarUserCount > 0) {
return false;
}

final isarAssetCount = await db.assets.count();
if (isarAssetCount > 0) {
return false;
}

final driftStoreCount = await drift.storeEntity.select().get().then((list) => list.length);
if (driftStoreCount > 0) {
return false;
}

final driftAssetCount = await drift.localAssetEntity.select().get().then((list) => list.length);
if (driftAssetCount > 0) {
return false;
}

return true;
} catch (error) {
debugPrint("[MIGRATION] Error checking if new installation: $error");
return false;
}
}

Future<void> _migrateTo(Isar db, int version) async {
await Store.delete(StoreKey.assetETag);
await db.writeTxn(() async {
Expand Down Expand Up @@ -284,3 +328,27 @@ Future<List<void>> runNewSync(WidgetRef ref, {bool full = false}) {
}),
]);
}

Future<void> resetDriftDatabase(Drift drift) async {
// https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94
final database = drift.attachedDatabase;
await database.exclusively(() async {
// https://stackoverflow.com/a/65743498/25690041
await database.customStatement('PRAGMA writable_schema = 1;');
await database.customStatement('DELETE FROM sqlite_master;');
await database.customStatement('VACUUM;');
await database.customStatement('PRAGMA writable_schema = 0;');
await database.customStatement('PRAGMA integrity_check');

await database.customStatement('PRAGMA user_version = 0');
await database.beforeOpen(
// ignore: invalid_use_of_internal_member
database.resolvedEngine.executor,
OpeningDetails(null, database.schemaVersion),
);
await database.customStatement('PRAGMA user_version = ${database.schemaVersion}');

// Refresh all stream queries
database.notifyUpdates({for (final table in database.allTables) TableUpdate.onTable(table)});
});
}
Loading