mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +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/pref.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/object_extension.dart';
|
||||
import 'package:nc_photos/protected_page_handler.dart';
|
||||
import 'package:nc_photos/size.dart';
|
||||
import 'package:np_codegen/np_codegen.dart';
|
||||
|
@ -32,6 +32,13 @@ class PrefController {
|
|||
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>(
|
||||
controller: _languageController,
|
||||
setter: (pref, value) => pref.setLanguage(value.langId),
|
||||
|
@ -176,6 +183,20 @@ class PrefController {
|
|||
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>({
|
||||
required BehaviorSubject<T> controller,
|
||||
required Future<bool> Function(Pref pref, T value) setter,
|
||||
|
@ -217,6 +238,9 @@ class PrefController {
|
|||
late final _accountsController =
|
||||
BehaviorSubject.seeded(_c.pref.getAccounts3() ?? []);
|
||||
@npSubjectAccessor
|
||||
late final _currentAccountIndexController =
|
||||
BehaviorSubject.seeded(_c.pref.getCurrentAccountIndex());
|
||||
@npSubjectAccessor
|
||||
late final _languageController =
|
||||
BehaviorSubject.seeded(_langIdToAppLanguage(_c.pref.getLanguageOr(0)));
|
||||
@npSubjectAccessor
|
||||
|
@ -284,6 +308,25 @@ class PrefController {
|
|||
@npSubjectAccessor
|
||||
late final _isNewHttpEngineController =
|
||||
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
|
||||
|
|
|
@ -23,6 +23,13 @@ extension $PrefControllerNpSubjectAccessor on PrefController {
|
|||
Stream<List<Account>> get accountsNew => accounts.skip(1);
|
||||
Stream<List<Account>> get accountsChange => accounts.distinct().skip(1);
|
||||
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
|
||||
ValueStream<AppLanguage> get language => _languageController.stream;
|
||||
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 isNewHttpEngineChange => isNewHttpEngine.distinct().skip(1);
|
||||
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 {
|
||||
|
|
|
@ -101,6 +101,15 @@ extension on Pref {
|
|||
|
||||
Future<bool> setNewHttpEngine(bool 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) {
|
||||
|
|
|
@ -161,11 +161,6 @@ extension PrefExtension on Pref {
|
|||
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() =>
|
||||
provider.getBool(PrefKey.shouldProcessExifWifiOnly);
|
||||
bool shouldProcessExifWifiOnlyOr([bool def = true]) =>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
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/entity/pref.dart';
|
||||
import 'package:np_codegen/np_codegen.dart';
|
||||
import 'package:np_db/np_db.dart';
|
||||
|
||||
|
@ -8,9 +8,10 @@ part 'v46.g.dart';
|
|||
|
||||
@npLog
|
||||
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");
|
||||
final accounts = pref.getAccounts3Or([]);
|
||||
final accounts = prefController.accountsValue;
|
||||
await db.addAccounts(accounts.toDb());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:copy_with/copy_with.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:kiwi/kiwi.dart';
|
||||
import 'package:logging/logging.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/di_container.dart';
|
||||
import 'package:nc_photos/entity/pref.dart';
|
||||
import 'package:nc_photos/k.dart' as k;
|
||||
import 'package:nc_photos/mobile/android/activity.dart';
|
||||
|
@ -28,120 +29,105 @@ import 'package:np_platform_util/np_platform_util.dart';
|
|||
import 'package:to_string/to_string.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";
|
||||
|
||||
const Splash({super.key});
|
||||
|
||||
@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
|
||||
class _SplashState extends State<Splash> {
|
||||
@override
|
||||
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();
|
||||
}
|
||||
class _WrappedSplash extends StatelessWidget {
|
||||
const _WrappedSplash();
|
||||
|
||||
@override
|
||||
build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: PopScope(
|
||||
canPop: false,
|
||||
child: Builder(builder: (context) => _buildContent(context)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContent(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Center(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
Widget build(BuildContext context) {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
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());
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
_BlocListenerT<bool>(
|
||||
selector: (state) => state.isDone,
|
||||
listener: (context, isDone) {
|
||||
if (isDone) {
|
||||
_exit(context);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.cloud,
|
||||
size: 96,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.cloud,
|
||||
size: 96,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
L10n.global().appTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
L10n.global().appTitle,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
const Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 64,
|
||||
child: _UpgradeProgressView(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (_isUpgrading)
|
||||
BlocBuilder<_UpgradeCubit, _UpgradeState>(
|
||||
bloc: _upgradeCubit,
|
||||
builder: (context, state) {
|
||||
return Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 64,
|
||||
child: Column(
|
||||
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]");
|
||||
final account = Pref().getCurrentAccount();
|
||||
final account = context.read<PrefController>().currentAccountValue;
|
||||
if (isNeedSetup()) {
|
||||
Navigator.pushReplacementNamed(context, Setup.routeName);
|
||||
Navigator.of(context).pushReplacementNamed(Setup.routeName);
|
||||
} else if (account == null) {
|
||||
Navigator.pushReplacementNamed(context, SignIn.routeName);
|
||||
Navigator.of(context).pushReplacementNamed(SignIn.routeName);
|
||||
} else {
|
||||
Navigator.of(context)
|
||||
.pushReplacementNamedProtected(Home.routeName,
|
||||
|
@ -150,159 +136,28 @@ class _SplashState extends State<Splash> {
|
|||
if (getRawPlatform() == NpPlatform.android) {
|
||||
final initialRoute = await Activity.consumeInitialRoute();
|
||||
if (initialRoute != null) {
|
||||
unawaited(Navigator.pushNamed(context, initialRoute));
|
||||
unawaited(Navigator.of(context).pushNamed(initialRoute));
|
||||
}
|
||||
}
|
||||
}).onError<ProtectedPageAuthException>((_, __) async {
|
||||
_log.warning("[_exit] Auth failed");
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
_exit();
|
||||
if (context.mounted) {
|
||||
_exit(context);
|
||||
}
|
||||
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
|
||||
class _UpgradeState {
|
||||
const _UpgradeState(String text, int current, int count)
|
||||
: this._(text, current, count);
|
||||
// typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||
// typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||
typedef _BlocListenerT<T> = BlocListenerT<_Bloc, _State, T>;
|
||||
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||
|
||||
const _UpgradeState.intermediate([String? text])
|
||||
: this._(text ?? "Updating", 0, null);
|
||||
|
||||
const _UpgradeState._(this.text, this.current, this.count);
|
||||
|
||||
@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));
|
||||
extension on BuildContext {
|
||||
_Bloc get bloc => read<_Bloc>();
|
||||
// _State get state => bloc.state;
|
||||
void addEvent(_Event event) => bloc.add(event);
|
||||
}
|
||||
|
|
|
@ -2,24 +2,93 @@
|
|||
|
||||
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
|
||||
// **************************************************************************
|
||||
|
||||
extension _$_SplashStateNpLog on _SplashState {
|
||||
extension _$_WrappedSplashNpLog on _WrappedSplash {
|
||||
// ignore: unused_element
|
||||
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
|
||||
// **************************************************************************
|
||||
|
||||
extension _$_UpgradeStateToString on _UpgradeState {
|
||||
extension _$_StateToString on _State {
|
||||
String _$toString() {
|
||||
// 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