implemented post-login splash screen with Lottie animations and integrated background data prefetching during the login transition
This commit is contained in:
+56
-39
@@ -13,6 +13,7 @@ import '../../theming/light_app_theme.dart';
|
||||
import '../../utils/haptics.dart';
|
||||
import '../pages/settings/widgets/endpoint_picker.dart';
|
||||
import 'login_controller.dart';
|
||||
import 'post_login_splash.dart';
|
||||
import 'widgets/login_branding.dart';
|
||||
import 'widgets/login_card.dart';
|
||||
|
||||
@@ -23,73 +24,89 @@ class Login extends StatefulWidget {
|
||||
State<Login> createState() => _LoginState();
|
||||
}
|
||||
|
||||
class _LoginState extends State<Login> {
|
||||
class _LoginState extends State<Login> with SingleTickerProviderStateMixin {
|
||||
static const _marianumRed = LightAppTheme.marianumRed;
|
||||
|
||||
final LoginController _controller = LoginController();
|
||||
late final AnimationController _fade = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 450),
|
||||
value: 1,
|
||||
);
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
precacheImage(const AssetImage('assets/logo/icon.png'), context);
|
||||
unawaited(PostLoginSplash.precache());
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_fade.dispose();
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onLoginSuccess() {
|
||||
Haptics.heavyAccent();
|
||||
context.read<AccountBloc>().setStatus(AccountStatus.loggedIn);
|
||||
// Re-register the periodic refresh (cancelAll runs on logout) and kick
|
||||
// off an immediate one-off so the widget populates within seconds
|
||||
// instead of waiting up to 30 minutes for the next periodic slot.
|
||||
unawaited(WidgetBackgroundTask.initialize());
|
||||
unawaited(WidgetBackgroundTask.requestImmediateRefresh());
|
||||
// Fade the login content out before handing over to the post-login splash.
|
||||
// Both share the red backdrop, so this reads as one continuous transition
|
||||
// instead of an abrupt swap.
|
||||
_fade.reverse().whenComplete(() {
|
||||
if (!mounted) return;
|
||||
context.read<AccountBloc>().setStatus(AccountStatus.loggedIn);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
backgroundColor: _marianumRed,
|
||||
body: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
maxWidth: 420,
|
||||
),
|
||||
// spaceBetween statt Spacer-in-IntrinsicHeight: bei jeder
|
||||
// Inhaltsänderung im unteren Block (z.B. EndpointLink mit
|
||||
// dynamischem Label) würde IntrinsicHeight sonst die Column
|
||||
// an die intrinsic-Höhe pinnen und ein paar Pixel Overflow
|
||||
// produzieren. spaceBetween fügt nur den verbleibenden Gap
|
||||
// ein und schrumpft sauber auf 0, wenn der Inhalt zu hoch
|
||||
// wird — dann übernimmt der äußere ScrollView.
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
const LoginHeader(),
|
||||
const SizedBox(height: 28),
|
||||
LoginCard(
|
||||
controller: _controller,
|
||||
onSuccess: _onLoginSuccess,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
const LoginDisclaimer(),
|
||||
],
|
||||
),
|
||||
const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [_EndpointLink(), LoginFooter()],
|
||||
),
|
||||
],
|
||||
body: FadeTransition(
|
||||
opacity: _fade,
|
||||
child: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) => SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
maxWidth: 420,
|
||||
),
|
||||
// spaceBetween statt Spacer-in-IntrinsicHeight: bei jeder
|
||||
// Inhaltsänderung im unteren Block (z.B. EndpointLink mit
|
||||
// dynamischem Label) würde IntrinsicHeight sonst die Column
|
||||
// an die intrinsic-Höhe pinnen und ein paar Pixel Overflow
|
||||
// produzieren. spaceBetween fügt nur den verbleibenden Gap
|
||||
// ein und schrumpft sauber auf 0, wenn der Inhalt zu hoch
|
||||
// wird — dann übernimmt der äußere ScrollView.
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
const LoginHeader(),
|
||||
const SizedBox(height: 28),
|
||||
LoginCard(
|
||||
controller: _controller,
|
||||
onSuccess: _onLoginSuccess,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
const LoginDisclaimer(),
|
||||
],
|
||||
),
|
||||
const Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [_EndpointLink(), LoginFooter()],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user