mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-02 06:46:22 +01:00
Overhaul splash screen
This commit is contained in:
parent
e309e77eca
commit
3ab0829ba2
10 changed files with 471 additions and 248 deletions
|
@ -9,8 +9,8 @@ import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/collection/util.dart';
|
import 'package:nc_photos/entity/collection/util.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/json_util.dart';
|
import 'package:nc_photos/json_util.dart';
|
||||||
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/language_util.dart';
|
import 'package:nc_photos/language_util.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:nc_photos/protected_page_handler.dart';
|
import 'package:nc_photos/protected_page_handler.dart';
|
||||||
import 'package:nc_photos/size.dart';
|
import 'package:nc_photos/size.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
@ -32,6 +32,13 @@ class PrefController {
|
||||||
value: value,
|
value: value,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<bool> setCurrentAccountIndex(int value) => _setOrRemove<int>(
|
||||||
|
controller: _currentAccountIndexController,
|
||||||
|
setter: (pref, value) => pref.setCurrentAccountIndex(value),
|
||||||
|
remover: (pref) => pref.setCurrentAccountIndex(null),
|
||||||
|
value: value,
|
||||||
|
);
|
||||||
|
|
||||||
Future<bool> setAppLanguage(AppLanguage value) => _set<AppLanguage>(
|
Future<bool> setAppLanguage(AppLanguage value) => _set<AppLanguage>(
|
||||||
controller: _languageController,
|
controller: _languageController,
|
||||||
setter: (pref, value) => pref.setLanguage(value.langId),
|
setter: (pref, value) => pref.setLanguage(value.langId),
|
||||||
|
@ -176,6 +183,20 @@ class PrefController {
|
||||||
value: value,
|
value: value,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Future<bool> setFirstRunTime(DateTime? value) => _setOrRemove<DateTime>(
|
||||||
|
controller: _firstRunTimeController,
|
||||||
|
setter: (pref, value) =>
|
||||||
|
pref.setFirstRunTime(value.millisecondsSinceEpoch),
|
||||||
|
remover: (pref) => pref.setFirstRunTime(null),
|
||||||
|
value: value,
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<bool> setLastVersion(int value) => _set<int>(
|
||||||
|
controller: _lastVersionController,
|
||||||
|
setter: (pref, value) => pref.setLastVersion(value),
|
||||||
|
value: value,
|
||||||
|
);
|
||||||
|
|
||||||
Future<bool> _set<T>({
|
Future<bool> _set<T>({
|
||||||
required BehaviorSubject<T> controller,
|
required BehaviorSubject<T> controller,
|
||||||
required Future<bool> Function(Pref pref, T value) setter,
|
required Future<bool> Function(Pref pref, T value) setter,
|
||||||
|
@ -217,6 +238,9 @@ class PrefController {
|
||||||
late final _accountsController =
|
late final _accountsController =
|
||||||
BehaviorSubject.seeded(_c.pref.getAccounts3() ?? []);
|
BehaviorSubject.seeded(_c.pref.getAccounts3() ?? []);
|
||||||
@npSubjectAccessor
|
@npSubjectAccessor
|
||||||
|
late final _currentAccountIndexController =
|
||||||
|
BehaviorSubject.seeded(_c.pref.getCurrentAccountIndex());
|
||||||
|
@npSubjectAccessor
|
||||||
late final _languageController =
|
late final _languageController =
|
||||||
BehaviorSubject.seeded(_langIdToAppLanguage(_c.pref.getLanguageOr(0)));
|
BehaviorSubject.seeded(_langIdToAppLanguage(_c.pref.getLanguageOr(0)));
|
||||||
@npSubjectAccessor
|
@npSubjectAccessor
|
||||||
|
@ -284,6 +308,25 @@ class PrefController {
|
||||||
@npSubjectAccessor
|
@npSubjectAccessor
|
||||||
late final _isNewHttpEngineController =
|
late final _isNewHttpEngineController =
|
||||||
BehaviorSubject.seeded(_c.pref.isNewHttpEngine() ?? false);
|
BehaviorSubject.seeded(_c.pref.isNewHttpEngine() ?? false);
|
||||||
|
@npSubjectAccessor
|
||||||
|
late final _firstRunTimeController = BehaviorSubject.seeded(_c.pref
|
||||||
|
.getFirstRunTime()
|
||||||
|
?.let((v) => DateTime.fromMillisecondsSinceEpoch(v).toUtc()));
|
||||||
|
@npSubjectAccessor
|
||||||
|
late final _lastVersionController =
|
||||||
|
BehaviorSubject.seeded(_c.pref.getLastVersion() ??
|
||||||
|
// v6 is the last version without saving the version number in pref
|
||||||
|
(_c.pref.getSetupProgress() == null ? k.version : 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PrefControllerExtension on PrefController {
|
||||||
|
Account? get currentAccountValue {
|
||||||
|
try {
|
||||||
|
return accountsValue[currentAccountIndexValue!];
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@npSubjectAccessor
|
@npSubjectAccessor
|
||||||
|
|
|
@ -23,6 +23,13 @@ extension $PrefControllerNpSubjectAccessor on PrefController {
|
||||||
Stream<List<Account>> get accountsNew => accounts.skip(1);
|
Stream<List<Account>> get accountsNew => accounts.skip(1);
|
||||||
Stream<List<Account>> get accountsChange => accounts.distinct().skip(1);
|
Stream<List<Account>> get accountsChange => accounts.distinct().skip(1);
|
||||||
List<Account> get accountsValue => _accountsController.value;
|
List<Account> get accountsValue => _accountsController.value;
|
||||||
|
// _currentAccountIndexController
|
||||||
|
ValueStream<int?> get currentAccountIndex =>
|
||||||
|
_currentAccountIndexController.stream;
|
||||||
|
Stream<int?> get currentAccountIndexNew => currentAccountIndex.skip(1);
|
||||||
|
Stream<int?> get currentAccountIndexChange =>
|
||||||
|
currentAccountIndex.distinct().skip(1);
|
||||||
|
int? get currentAccountIndexValue => _currentAccountIndexController.value;
|
||||||
// _languageController
|
// _languageController
|
||||||
ValueStream<AppLanguage> get language => _languageController.stream;
|
ValueStream<AppLanguage> get language => _languageController.stream;
|
||||||
Stream<AppLanguage> get languageNew => language.skip(1);
|
Stream<AppLanguage> get languageNew => language.skip(1);
|
||||||
|
@ -171,6 +178,16 @@ extension $PrefControllerNpSubjectAccessor on PrefController {
|
||||||
Stream<bool> get isNewHttpEngineNew => isNewHttpEngine.skip(1);
|
Stream<bool> get isNewHttpEngineNew => isNewHttpEngine.skip(1);
|
||||||
Stream<bool> get isNewHttpEngineChange => isNewHttpEngine.distinct().skip(1);
|
Stream<bool> get isNewHttpEngineChange => isNewHttpEngine.distinct().skip(1);
|
||||||
bool get isNewHttpEngineValue => _isNewHttpEngineController.value;
|
bool get isNewHttpEngineValue => _isNewHttpEngineController.value;
|
||||||
|
// _firstRunTimeController
|
||||||
|
ValueStream<DateTime?> get firstRunTime => _firstRunTimeController.stream;
|
||||||
|
Stream<DateTime?> get firstRunTimeNew => firstRunTime.skip(1);
|
||||||
|
Stream<DateTime?> get firstRunTimeChange => firstRunTime.distinct().skip(1);
|
||||||
|
DateTime? get firstRunTimeValue => _firstRunTimeController.value;
|
||||||
|
// _lastVersionController
|
||||||
|
ValueStream<int> get lastVersion => _lastVersionController.stream;
|
||||||
|
Stream<int> get lastVersionNew => lastVersion.skip(1);
|
||||||
|
Stream<int> get lastVersionChange => lastVersion.distinct().skip(1);
|
||||||
|
int get lastVersionValue => _lastVersionController.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension $SecurePrefControllerNpSubjectAccessor on SecurePrefController {
|
extension $SecurePrefControllerNpSubjectAccessor on SecurePrefController {
|
||||||
|
|
|
@ -101,6 +101,15 @@ extension on Pref {
|
||||||
|
|
||||||
Future<bool> setNewHttpEngine(bool value) =>
|
Future<bool> setNewHttpEngine(bool value) =>
|
||||||
provider.setBool(PrefKey.isNewHttpEngine, value);
|
provider.setBool(PrefKey.isNewHttpEngine, value);
|
||||||
|
|
||||||
|
int? getFirstRunTime() => provider.getInt(PrefKey.firstRunTime);
|
||||||
|
Future<bool> setFirstRunTime(int? value) {
|
||||||
|
if (value == null) {
|
||||||
|
return provider.remove(PrefKey.firstRunTime);
|
||||||
|
} else {
|
||||||
|
return provider.setInt(PrefKey.firstRunTime, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MapCoord? _tryMapCoordFromJson(dynamic json) {
|
MapCoord? _tryMapCoordFromJson(dynamic json) {
|
||||||
|
|
|
@ -161,11 +161,6 @@ extension PrefExtension on Pref {
|
||||||
value,
|
value,
|
||||||
(key, value) => provider.setBool(key, value));
|
(key, value) => provider.setBool(key, value));
|
||||||
|
|
||||||
int? getFirstRunTime() => provider.getInt(PrefKey.firstRunTime);
|
|
||||||
int getFirstRunTimeOr(int def) => getFirstRunTime() ?? def;
|
|
||||||
Future<bool> setFirstRunTime(int value) => _set<int>(
|
|
||||||
PrefKey.firstRunTime, value, (key, value) => provider.setInt(key, value));
|
|
||||||
|
|
||||||
bool? shouldProcessExifWifiOnly() =>
|
bool? shouldProcessExifWifiOnly() =>
|
||||||
provider.getBool(PrefKey.shouldProcessExifWifiOnly);
|
provider.getBool(PrefKey.shouldProcessExifWifiOnly);
|
||||||
bool shouldProcessExifWifiOnlyOr([bool def = true]) =>
|
bool shouldProcessExifWifiOnlyOr([bool def = true]) =>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/controller/pref_controller.dart';
|
||||||
import 'package:nc_photos/db/entity_converter.dart';
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_db/np_db.dart';
|
import 'package:np_db/np_db.dart';
|
||||||
|
|
||||||
|
@ -8,9 +8,10 @@ part 'v46.g.dart';
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class CompatV46 {
|
class CompatV46 {
|
||||||
static Future<void> insertDbAccounts(Pref pref, NpDb db) async {
|
static Future<void> insertDbAccounts(
|
||||||
|
PrefController prefController, NpDb db) async {
|
||||||
_log.info("[insertDbAccounts] Insert current accounts to Sqlite database");
|
_log.info("[insertDbAccounts] Insert current accounts to Sqlite database");
|
||||||
final accounts = pref.getAccounts3Or([]);
|
final accounts = prefController.accountsValue;
|
||||||
await db.addAccounts(accounts.toDb());
|
await db.addAccounts(accounts.toDb());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:copy_with/copy_with.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/bloc_util.dart';
|
||||||
|
import 'package:nc_photos/controller/pref_controller.dart';
|
||||||
import 'package:nc_photos/db/entity_converter.dart';
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/mobile/android/activity.dart';
|
import 'package:nc_photos/mobile/android/activity.dart';
|
||||||
|
@ -28,63 +29,68 @@ import 'package:np_platform_util/np_platform_util.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
part 'splash.g.dart';
|
part 'splash.g.dart';
|
||||||
|
part 'splash/bloc.dart';
|
||||||
|
part 'splash/state_event.dart';
|
||||||
|
part 'splash/view.dart';
|
||||||
|
|
||||||
class Splash extends StatefulWidget {
|
class Splash extends StatelessWidget {
|
||||||
static const routeName = "/splash";
|
static const routeName = "/splash";
|
||||||
|
|
||||||
const Splash({super.key});
|
const Splash({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
createState() => _SplashState();
|
Widget build(BuildContext context) {
|
||||||
|
return BlocProvider(
|
||||||
|
create: (context) => _Bloc(
|
||||||
|
prefController: context.read(),
|
||||||
|
npDb: context.read(),
|
||||||
|
)..add(const _Init()),
|
||||||
|
child: const _WrappedSplash(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class _SplashState extends State<Splash> {
|
class _WrappedSplash extends StatelessWidget {
|
||||||
@override
|
const _WrappedSplash();
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
_doWork();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _doWork() async {
|
|
||||||
if (!await Permission.hasPostNotifications()) {
|
|
||||||
await requestPostNotificationsForResult();
|
|
||||||
}
|
|
||||||
if (Pref().getFirstRunTime() == null) {
|
|
||||||
await Pref().setFirstRunTime(clock.now().millisecondsSinceEpoch);
|
|
||||||
}
|
|
||||||
if (_shouldUpgrade()) {
|
|
||||||
setState(() {
|
|
||||||
_isUpgrading = true;
|
|
||||||
});
|
|
||||||
await _handleUpgrade();
|
|
||||||
setState(() {
|
|
||||||
_isUpgrading = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return PopScope(
|
||||||
body: PopScope(
|
|
||||||
canPop: false,
|
canPop: false,
|
||||||
child: Builder(builder: (context) => _buildContent(context)),
|
child: Scaffold(
|
||||||
),
|
body: MultiBlocListener(
|
||||||
);
|
listeners: [
|
||||||
|
_BlocListenerT<int?>(
|
||||||
|
selector: (state) => state.changelogFromVersion,
|
||||||
|
listener: (context, changelogFromVersion) {
|
||||||
|
if (changelogFromVersion != null) {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushNamed(Changelog.routeName,
|
||||||
|
arguments: ChangelogArguments(changelogFromVersion))
|
||||||
|
.whenComplete(() {
|
||||||
|
if (context.mounted) {
|
||||||
|
context.addEvent(const _ChangelogDismissed());
|
||||||
}
|
}
|
||||||
|
});
|
||||||
Widget _buildContent(BuildContext context) {
|
}
|
||||||
return Padding(
|
},
|
||||||
|
),
|
||||||
|
_BlocListenerT<bool>(
|
||||||
|
selector: (state) => state.isDone,
|
||||||
|
listener: (context, isDone) {
|
||||||
|
if (isDone) {
|
||||||
|
_exit(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Column(
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
|
@ -101,47 +107,27 @@ class _SplashState extends State<Splash> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
const Positioned(
|
||||||
if (_isUpgrading)
|
|
||||||
BlocBuilder<_UpgradeCubit, _UpgradeState>(
|
|
||||||
bloc: _upgradeCubit,
|
|
||||||
builder: (context, state) {
|
|
||||||
return Positioned(
|
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: 64,
|
bottom: 64,
|
||||||
child: Column(
|
child: _UpgradeProgressView(),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(state.text),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
if (state.count == null)
|
|
||||||
const SizedBox(
|
|
||||||
width: 24,
|
|
||||||
height: 24,
|
|
||||||
child: AppIntermediateCircularProgressIndicator(),
|
|
||||||
)
|
|
||||||
else
|
|
||||||
LinearProgressIndicator(
|
|
||||||
value: state.current / state.count!,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _exit() {
|
void _exit(BuildContext context) {
|
||||||
_log.info("[_exit]");
|
_log.info("[_exit]");
|
||||||
final account = Pref().getCurrentAccount();
|
final account = context.read<PrefController>().currentAccountValue;
|
||||||
if (isNeedSetup()) {
|
if (isNeedSetup()) {
|
||||||
Navigator.pushReplacementNamed(context, Setup.routeName);
|
Navigator.of(context).pushReplacementNamed(Setup.routeName);
|
||||||
} else if (account == null) {
|
} else if (account == null) {
|
||||||
Navigator.pushReplacementNamed(context, SignIn.routeName);
|
Navigator.of(context).pushReplacementNamed(SignIn.routeName);
|
||||||
} else {
|
} else {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.pushReplacementNamedProtected(Home.routeName,
|
.pushReplacementNamedProtected(Home.routeName,
|
||||||
|
@ -150,159 +136,28 @@ class _SplashState extends State<Splash> {
|
||||||
if (getRawPlatform() == NpPlatform.android) {
|
if (getRawPlatform() == NpPlatform.android) {
|
||||||
final initialRoute = await Activity.consumeInitialRoute();
|
final initialRoute = await Activity.consumeInitialRoute();
|
||||||
if (initialRoute != null) {
|
if (initialRoute != null) {
|
||||||
unawaited(Navigator.pushNamed(context, initialRoute));
|
unawaited(Navigator.of(context).pushNamed(initialRoute));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).onError<ProtectedPageAuthException>((_, __) async {
|
}).onError<ProtectedPageAuthException>((_, __) async {
|
||||||
_log.warning("[_exit] Auth failed");
|
_log.warning("[_exit] Auth failed");
|
||||||
await Future.delayed(const Duration(seconds: 2));
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
_exit();
|
if (context.mounted) {
|
||||||
|
_exit(context);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldUpgrade() {
|
|
||||||
final lastVersion = Pref().getLastVersionOr(k.version);
|
|
||||||
return lastVersion < k.version;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _handleUpgrade() async {
|
|
||||||
try {
|
|
||||||
final lastVersion = Pref().getLastVersionOr(k.version);
|
|
||||||
unawaited(_showChangelogIfAvailable(lastVersion));
|
|
||||||
// begin upgrade while showing the changelog
|
|
||||||
try {
|
|
||||||
_log.info("[_handleUpgrade] Upgrade: $lastVersion -> ${k.version}");
|
|
||||||
await _upgrade(lastVersion);
|
|
||||||
_log.info("[_handleUpgrade] Upgrade done");
|
|
||||||
} finally {
|
|
||||||
// ensure user has closed the changelog
|
|
||||||
await _changelogCompleter.future;
|
|
||||||
}
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout("[_handleUpgrade] Failed while upgrade", e, stackTrace);
|
|
||||||
} finally {
|
|
||||||
await Pref().setLastVersion(k.version);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _upgrade(int lastVersion) async {
|
|
||||||
if (lastVersion < 290) {
|
|
||||||
await _upgrade29(lastVersion);
|
|
||||||
}
|
|
||||||
if (lastVersion < 460) {
|
|
||||||
await _upgrade46(lastVersion);
|
|
||||||
}
|
|
||||||
if (lastVersion < 550) {
|
|
||||||
await _upgrade55(lastVersion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _upgrade29(int lastVersion) async {
|
|
||||||
try {
|
|
||||||
_log.info("[_upgrade29] clearDefaultCache");
|
|
||||||
await CompatV29.clearDefaultCache();
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout("[_upgrade29] Failed while clearDefaultCache", e, stackTrace);
|
|
||||||
// just leave the cache then
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _upgrade46(int lastVersion) async {
|
|
||||||
try {
|
|
||||||
_log.info("[_upgrade46] insertDbAccounts");
|
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
|
||||||
await CompatV46.insertDbAccounts(c.pref, context.read());
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout("[_upgrade46] Failed while clearDefaultCache", e, stackTrace);
|
|
||||||
unawaited(Pref().setAccounts3(null));
|
|
||||||
unawaited(Pref().setCurrentAccountIndex(null));
|
|
||||||
await showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text("Error"),
|
|
||||||
content: const Text(
|
|
||||||
"Failed upgrading app, please sign in to your servers again"),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
|
||||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _upgrade55(int lastVersion) async {
|
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
|
||||||
try {
|
|
||||||
_log.info("[_upgrade55] migrate DB");
|
|
||||||
await CompatV55.migrateDb(
|
|
||||||
c.npDb,
|
|
||||||
onProgress: (current, count) {
|
|
||||||
_upgradeCubit.setState(
|
|
||||||
L10n.global().migrateDatabaseProcessingNotification,
|
|
||||||
current,
|
|
||||||
count,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout("[_upgrade55] Failed while migrateDb", e, stackTrace);
|
|
||||||
final accounts = Pref().getAccounts3Or([]);
|
|
||||||
await context.read<NpDb>().clearAndInitWithAccounts(accounts.toDb());
|
|
||||||
}
|
|
||||||
_upgradeCubit.setIntermediate();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _showChangelogIfAvailable(int lastVersion) async {
|
|
||||||
if (Changelog.hasContent(lastVersion)) {
|
|
||||||
try {
|
|
||||||
await Navigator.of(context).pushNamed(Changelog.routeName,
|
|
||||||
arguments: ChangelogArguments(lastVersion));
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.severe(
|
|
||||||
"[_showChangelogIfAvailable] Uncaught exception", e, stackTrace);
|
|
||||||
} finally {
|
|
||||||
_changelogCompleter.complete();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_changelogCompleter.complete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final _changelogCompleter = Completer();
|
|
||||||
var _isUpgrading = false;
|
|
||||||
late final _upgradeCubit = _UpgradeCubit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@toString
|
// typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||||
class _UpgradeState {
|
// typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||||
const _UpgradeState(String text, int current, int count)
|
typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
||||||
: this._(text, current, count);
|
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||||
|
|
||||||
const _UpgradeState.intermediate([String? text])
|
extension on BuildContext {
|
||||||
: this._(text ?? "Updating", 0, null);
|
_Bloc get bloc => read<_Bloc>();
|
||||||
|
// _State get state => bloc.state;
|
||||||
const _UpgradeState._(this.text, this.current, this.count);
|
void addEvent(_Event event) => bloc.add(event);
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => _$toString();
|
|
||||||
|
|
||||||
final String text;
|
|
||||||
final int current;
|
|
||||||
final int? count;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _UpgradeCubit extends Cubit<_UpgradeState> {
|
|
||||||
_UpgradeCubit() : super(const _UpgradeState.intermediate());
|
|
||||||
|
|
||||||
void setIntermediate() => emit(const _UpgradeState.intermediate());
|
|
||||||
|
|
||||||
void setState(String text, int current, int count) =>
|
|
||||||
emit(_UpgradeState(text, current, count));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,24 +2,93 @@
|
||||||
|
|
||||||
part of 'splash.dart';
|
part of 'splash.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithLintRuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// ignore_for_file: library_private_types_in_public_api, duplicate_ignore
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
abstract class $_StateCopyWithWorker {
|
||||||
|
_State call(
|
||||||
|
{int? changelogFromVersion,
|
||||||
|
double? upgradeProgress,
|
||||||
|
String? upgradeText,
|
||||||
|
bool? isDone});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
|
_$_StateCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_State call(
|
||||||
|
{dynamic changelogFromVersion = copyWithNull,
|
||||||
|
dynamic upgradeProgress = copyWithNull,
|
||||||
|
dynamic upgradeText = copyWithNull,
|
||||||
|
dynamic isDone}) {
|
||||||
|
return _State(
|
||||||
|
changelogFromVersion: changelogFromVersion == copyWithNull
|
||||||
|
? that.changelogFromVersion
|
||||||
|
: changelogFromVersion as int?,
|
||||||
|
upgradeProgress: upgradeProgress == copyWithNull
|
||||||
|
? that.upgradeProgress
|
||||||
|
: upgradeProgress as double?,
|
||||||
|
upgradeText: upgradeText == copyWithNull
|
||||||
|
? that.upgradeText
|
||||||
|
: upgradeText as String?,
|
||||||
|
isDone: isDone as bool? ?? that.isDone);
|
||||||
|
}
|
||||||
|
|
||||||
|
final _State that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $_StateCopyWith on _State {
|
||||||
|
$_StateCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$_StateCopyWithWorker get _$copyWith => _$_StateCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// NpLogGenerator
|
// NpLogGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
extension _$_SplashStateNpLog on _SplashState {
|
extension _$_WrappedSplashNpLog on _WrappedSplash {
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
Logger get _log => log;
|
Logger get _log => log;
|
||||||
|
|
||||||
static final log = Logger("widget.splash._SplashState");
|
static final log = Logger("widget.splash._WrappedSplash");
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_BlocNpLog on _Bloc {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log = Logger("widget.splash._Bloc");
|
||||||
}
|
}
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// ToStringGenerator
|
// ToStringGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
extension _$_UpgradeStateToString on _UpgradeState {
|
extension _$_StateToString on _State {
|
||||||
String _$toString() {
|
String _$toString() {
|
||||||
// ignore: unnecessary_string_interpolations
|
// ignore: unnecessary_string_interpolations
|
||||||
return "_UpgradeState {text: $text, current: $current, count: $count}";
|
return "_State {changelogFromVersion: $changelogFromVersion, upgradeProgress: ${upgradeProgress == null ? null : "${upgradeProgress!.toStringAsFixed(3)}"}, upgradeText: $upgradeText, isDone: $isDone}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_InitToString on _Init {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_Init {}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_ChangelogDismissedToString on _ChangelogDismissed {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_ChangelogDismissed {}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
157
app/lib/widget/splash/bloc.dart
Normal file
157
app/lib/widget/splash/bloc.dart
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
part of '../splash.dart';
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
|
_Bloc({
|
||||||
|
required this.prefController,
|
||||||
|
required this.npDb,
|
||||||
|
}) : super(_State.init()) {
|
||||||
|
on<_Init>(_onInit);
|
||||||
|
on<_ChangelogDismissed>(_onChangelogDismissed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
for (final s in _subscriptions) {
|
||||||
|
s.cancel();
|
||||||
|
}
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get tag => _log.fullName;
|
||||||
|
|
||||||
|
Future<void> _onInit(_Init ev, Emitter<_State> emit) async {
|
||||||
|
_log.info(ev);
|
||||||
|
await Future.wait([
|
||||||
|
_initNotification(),
|
||||||
|
_initFirstRun(),
|
||||||
|
_migrateApp(emit),
|
||||||
|
]);
|
||||||
|
emit(state.copyWith(isDone: true));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onChangelogDismissed(_ChangelogDismissed ev, Emitter<_State> emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
_changelogCompleter.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initNotification() async {
|
||||||
|
if (!await Permission.hasPostNotifications()) {
|
||||||
|
await requestPostNotificationsForResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initFirstRun() async {
|
||||||
|
if (prefController.firstRunTimeValue == null) {
|
||||||
|
await prefController.setFirstRunTime(clock.now().toUtc());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _migrateApp(Emitter<_State> emit) async {
|
||||||
|
if (_shouldUpgrade()) {
|
||||||
|
await _handleUpgrade(emit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldUpgrade() {
|
||||||
|
final lastVersion = prefController.lastVersionValue;
|
||||||
|
return lastVersion < k.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _handleUpgrade(Emitter<_State> emit) async {
|
||||||
|
try {
|
||||||
|
final lastVersion = prefController.lastVersionValue;
|
||||||
|
unawaited(_showChangelogIfAvailable(lastVersion, emit));
|
||||||
|
// begin upgrade while showing the changelog
|
||||||
|
try {
|
||||||
|
_log.info("[_handleUpgrade] Upgrade: $lastVersion -> ${k.version}");
|
||||||
|
await _upgrade(lastVersion, emit);
|
||||||
|
await Future.delayed(Duration(seconds: 5));
|
||||||
|
_log.info("[_handleUpgrade] Upgrade done");
|
||||||
|
} finally {
|
||||||
|
// ensure user has closed the changelog
|
||||||
|
await _changelogCompleter.future;
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.shout("[_handleUpgrade] Failed while upgrade", e, stackTrace);
|
||||||
|
} finally {
|
||||||
|
await prefController.setLastVersion(k.version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _showChangelogIfAvailable(
|
||||||
|
int lastVersion, Emitter<_State> emit) async {
|
||||||
|
if (Changelog.hasContent(lastVersion)) {
|
||||||
|
emit(state.copyWith(changelogFromVersion: lastVersion));
|
||||||
|
} else {
|
||||||
|
_changelogCompleter.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _upgrade(int lastVersion, Emitter<_State> emit) async {
|
||||||
|
if (lastVersion < 290) {
|
||||||
|
await _upgrade29(lastVersion);
|
||||||
|
}
|
||||||
|
if (lastVersion < 460) {
|
||||||
|
await _upgrade46(lastVersion);
|
||||||
|
}
|
||||||
|
if (lastVersion < 550) {
|
||||||
|
await _upgrade55(lastVersion, emit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _upgrade29(int lastVersion) async {
|
||||||
|
try {
|
||||||
|
_log.info("[_upgrade29] clearDefaultCache");
|
||||||
|
await CompatV29.clearDefaultCache();
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.shout("[_upgrade29] Failed while clearDefaultCache", e, stackTrace);
|
||||||
|
// just leave the cache then
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _upgrade46(int lastVersion) async {
|
||||||
|
try {
|
||||||
|
_log.info("[_upgrade46] insertDbAccounts");
|
||||||
|
await CompatV46.insertDbAccounts(prefController, npDb);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.shout("[_upgrade46] Failed while clearDefaultCache", e, stackTrace);
|
||||||
|
unawaited(Pref().setAccounts3(null));
|
||||||
|
unawaited(Pref().setCurrentAccountIndex(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _upgrade55(int lastVersion, Emitter<_State> emit) async {
|
||||||
|
try {
|
||||||
|
_log.info("[_upgrade55] migrate DB");
|
||||||
|
await CompatV55.migrateDb(
|
||||||
|
npDb,
|
||||||
|
onProgress: (current, count) {
|
||||||
|
if (!isClosed) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
upgradeProgress: current / count,
|
||||||
|
upgradeText: L10n.global().migrateDatabaseProcessingNotification,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.shout("[_upgrade55] Failed while migrateDb", e, stackTrace);
|
||||||
|
final accounts = prefController.accountsValue;
|
||||||
|
await npDb.clearAndInitWithAccounts(accounts.toDb());
|
||||||
|
}
|
||||||
|
if (!isClosed) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
upgradeProgress: null,
|
||||||
|
upgradeText: null,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final PrefController prefController;
|
||||||
|
final NpDb npDb;
|
||||||
|
|
||||||
|
final _subscriptions = <StreamSubscription>[];
|
||||||
|
final _changelogCompleter = Completer();
|
||||||
|
}
|
42
app/lib/widget/splash/state_event.dart
Normal file
42
app/lib/widget/splash/state_event.dart
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
part of '../splash.dart';
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class _State {
|
||||||
|
const _State({
|
||||||
|
this.changelogFromVersion,
|
||||||
|
this.upgradeProgress,
|
||||||
|
this.upgradeText,
|
||||||
|
required this.isDone,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _State.init() => const _State(
|
||||||
|
isDone: false,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final int? changelogFromVersion;
|
||||||
|
final double? upgradeProgress;
|
||||||
|
final String? upgradeText;
|
||||||
|
final bool isDone;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Event {}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _Init implements _Event {
|
||||||
|
const _Init();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _ChangelogDismissed implements _Event {
|
||||||
|
const _ChangelogDismissed();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
}
|
35
app/lib/widget/splash/view.dart
Normal file
35
app/lib/widget/splash/view.dart
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
part of '../splash.dart';
|
||||||
|
|
||||||
|
class _UpgradeProgressView extends StatelessWidget {
|
||||||
|
const _UpgradeProgressView();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return _BlocSelector<double?>(
|
||||||
|
selector: (state) => state.upgradeProgress,
|
||||||
|
builder: (context, upgradeProgress) {
|
||||||
|
if (upgradeProgress == null) {
|
||||||
|
return const Center(
|
||||||
|
child: SizedBox.square(
|
||||||
|
dimension: 24,
|
||||||
|
child: AppIntermediateCircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_BlocSelector<String?>(
|
||||||
|
selector: (state) => state.upgradeText,
|
||||||
|
builder: (context, upgradeText) =>
|
||||||
|
Text(upgradeText ?? "Updating"),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
LinearProgressIndicator(value: upgradeProgress),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue