feat: add toggle to switch between Isar and Sqlite (#19953)

This commit is contained in:
shenlong
2025-07-17 21:42:29 +05:30
committed by GitHub
parent b256c51b6b
commit 531515daf9
27 changed files with 11016 additions and 160 deletions
@@ -0,0 +1,126 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/providers/album/album.provider.dart';
import 'package:immich_mobile/providers/asset.provider.dart';
import 'package:immich_mobile/providers/background_sync.provider.dart';
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/migration.dart';
import 'package:permission_handler/permission_handler.dart';
@RoutePage()
class ChangeExperiencePage extends ConsumerStatefulWidget {
final bool switchingToBeta;
const ChangeExperiencePage({super.key, required this.switchingToBeta});
@override
ConsumerState createState() => _ChangeExperiencePageState();
}
class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
bool hasMigrated = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => _handleMigration());
}
Future<void> _handleMigration() async {
if (widget.switchingToBeta) {
final assetNotifier = ref.read(assetProvider.notifier);
if (assetNotifier.mounted) {
assetNotifier.dispose();
}
final albumNotifier = ref.read(albumProvider.notifier);
if (albumNotifier.mounted) {
albumNotifier.dispose();
}
final permission = await ref
.read(galleryPermissionNotifier.notifier)
.requestGalleryPermission();
if (permission.isGranted) {
await ref.read(backgroundSyncProvider).syncLocal(full: true);
await migrateDeviceAssetToSqlite(
ref.read(isarProvider),
ref.read(driftProvider),
);
}
} else {
await ref.read(backgroundSyncProvider).cancel();
}
Future.delayed(const Duration(seconds: 3), () {
context.replaceRoute(
widget.switchingToBeta
? const TabShellRoute()
: const TabControllerRoute(),
);
});
if (mounted) {
setState(() {
HapticFeedback.heavyImpact();
hasMigrated = true;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedSwitcher(
duration: Durations.long4,
child: hasMigrated
? const Icon(
Icons.check_circle_rounded,
color: Colors.green,
size: 48.0,
)
: const SizedBox(
width: 50.0,
height: 50.0,
child: CircularProgressIndicator(),
),
),
const SizedBox(height: 16.0),
Center(
child: Column(
children: [
SizedBox(
width: 300.0,
child: AnimatedSwitcher(
duration: Durations.long4,
child: hasMigrated
? Text(
"Migration success. Navigating to the new timeline...",
style: context.textTheme.titleMedium,
textAlign: TextAlign.center,
)
: Text(
"Data migration in progress...\nPlease wait and don't close this page",
style: context.textTheme.titleMedium,
textAlign: TextAlign.center,
),
),
),
],
),
),
],
),
),
);
}
}
+51 -46
View File
@@ -8,6 +8,7 @@ import 'package:immich_mobile/widgets/settings/advanced_settings.dart';
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_settings.dart';
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/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';
import 'package:immich_mobile/widgets/settings/notification_setting.dart';
@@ -94,55 +95,59 @@ class _MobileLayout extends StatelessWidget {
const _MobileLayout();
@override
Widget build(BuildContext context) {
final List<Widget> settings = SettingSection.values
.map(
(setting) => Padding(
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)),
),
margin: const EdgeInsets.symmetric(vertical: 4.0),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0,
),
leading: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16)),
color: context.isDarkTheme
? Colors.black26
: Colors.white.withAlpha(100),
),
padding: const EdgeInsets.all(16.0),
child: Icon(setting.icon, color: context.primaryColor),
),
title: Text(
setting.title,
style: context.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.w600,
color: context.primaryColor,
),
).tr(),
subtitle: Text(
setting.subtitle,
style: context.textTheme.labelLarge,
).tr(),
onTap: () =>
context.pushRoute(SettingsSubRoute(section: setting)),
),
),
),
)
.toList();
return ListView(
physics: const ClampingScrollPhysics(),
padding: const EdgeInsets.symmetric(vertical: 10.0),
children: SettingSection.values
.map(
(setting) => Padding(
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)),
),
margin: const EdgeInsets.symmetric(vertical: 4.0),
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 16.0,
),
leading: Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(16)),
color: context.isDarkTheme
? Colors.black26
: Colors.white.withAlpha(100),
),
padding: const EdgeInsets.all(16.0),
child: Icon(setting.icon, color: context.primaryColor),
),
title: Text(
setting.title,
style: context.textTheme.titleMedium!.copyWith(
fontWeight: FontWeight.w600,
color: context.primaryColor,
),
).tr(),
subtitle: Text(
setting.subtitle,
style: context.textTheme.labelLarge,
).tr(),
onTap: () =>
context.pushRoute(SettingsSubRoute(section: setting)),
),
),
),
)
.toList(),
children: [
const BetaTimelineListTile(),
...settings,
],
);
}
}
@@ -73,7 +73,15 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
}
if (context.router.current.name == SplashScreenRoute.name) {
context.replaceRoute(const TabControllerRoute());
context.replaceRoute(
Store.isBetaTimelineEnabled
? const TabShellRoute()
: const TabControllerRoute(),
);
}
if (Store.isBetaTimelineEnabled) {
return;
}
final hasPermission =
+17 -2
View File
@@ -10,13 +10,28 @@ import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'
import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/utils/migration.dart';
@RoutePage()
class TabShellPage extends ConsumerWidget {
class TabShellPage extends ConsumerStatefulWidget {
const TabShellPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
ConsumerState<TabShellPage> createState() => _TabShellPageState();
}
class _TabShellPageState extends ConsumerState<TabShellPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
runNewSync(ref, full: true);
});
}
@override
Widget build(BuildContext context) {
final isScreenLandscape = context.orientation == Orientation.landscape;
Widget buildIcon({required Widget icon, required bool isProcessing}) {