Overhaul splash screen

This commit is contained in:
Ming Ming 2024-08-18 02:01:59 +08:00
parent e309e77eca
commit 3ab0829ba2
10 changed files with 471 additions and 248 deletions

View file

@ -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

View file

@ -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 {

View file

@ -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) {

View file

@ -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]) =>

View file

@ -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());
}

View file

@ -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);
}

View file

@ -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 {}";
}
}

View 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();
}

View 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();
}

View 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),
],
);
}
},
);
}
}