feat: add toggle to switch between Isar and Sqlite (#19953)
This commit is contained in:
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 =
|
||||
|
||||
@@ -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}) {
|
||||
|
||||
Reference in New Issue
Block a user