Animate the splash and fix the web preview hanging on the logo
The splash now raises the sun in the mark and over a painted valley, floats four feature chips around the mark, shimmers the wordmark, shows a three-beat "how it works" card, drifts light motes upward and walks a status line through the session check. A tap skips it. The web build hung on the native splash because the asset manifest could not be published: "assets/icons/splash_orb.png" is 27 characters, so its length prefix in AssetManifest.bin is an ESC byte. Renamed it to splash_mark.png and removed the unused anchor assets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KhQh614DMeYoQEH9SoMRUw
This commit is contained in:
@@ -26,6 +26,9 @@ class SceneArt extends StatelessWidget {
|
||||
/// Darkens the lower half so white type laid over it stays legible.
|
||||
final bool shade;
|
||||
|
||||
/// Lowers the sun behind the ridges; animate it for a sunrise.
|
||||
final double sunLift;
|
||||
|
||||
const SceneArt({
|
||||
super.key,
|
||||
required this.palette,
|
||||
@@ -34,6 +37,7 @@ class SceneArt extends StatelessWidget {
|
||||
this.figureScale = 0.5,
|
||||
this.figureAlignment = const Alignment(0.35, 0.15),
|
||||
this.shade = false,
|
||||
this.sunLift = 1,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -50,7 +54,8 @@ class SceneArt extends StatelessWidget {
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
CustomPaint(painter: ScenePainter(palette, seed)),
|
||||
CustomPaint(
|
||||
painter: ScenePainter(palette, seed, sunLift: sunLift)),
|
||||
if (figure != null)
|
||||
Align(
|
||||
alignment: figureAlignment,
|
||||
|
||||
@@ -11,7 +11,10 @@ class ScenePainter extends CustomPainter {
|
||||
|
||||
final int seed;
|
||||
|
||||
ScenePainter(this.palette, this.seed);
|
||||
/// 1 → the sun sits in its place; 0 → it is down behind the ridges.
|
||||
final double sunLift;
|
||||
|
||||
ScenePainter(this.palette, this.seed, {this.sunLift = 1});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
@@ -36,7 +39,7 @@ class ScenePainter extends CustomPainter {
|
||||
final double radius = math.min(w, h) * (0.13 + random.nextDouble() * 0.05);
|
||||
final Offset sun = Offset(
|
||||
w * (0.18 + random.nextDouble() * 0.64),
|
||||
h * (0.26 + random.nextDouble() * 0.12),
|
||||
h * (0.26 + random.nextDouble() * 0.12) + h * 0.5 * (1 - sunLift),
|
||||
);
|
||||
canvas.drawCircle(
|
||||
sun,
|
||||
@@ -109,6 +112,8 @@ class ScenePainter extends CustomPainter {
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant ScenePainter oldDelegate) {
|
||||
return oldDelegate.seed != seed || oldDelegate.palette != palette;
|
||||
return oldDelegate.seed != seed ||
|
||||
oldDelegate.palette != palette ||
|
||||
oldDelegate.sunLift != sunLift;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
import '../../about/internal/application/MeDescription.dart';
|
||||
import '../../about/internal/application/NavigatorType.dart';
|
||||
import '../../about/internal/application/RoutineKind.dart';
|
||||
import '../../about/internal/application/TextType.dart';
|
||||
import '../../configs/Navigator.dart';
|
||||
import '../../designs/AmbientBackground.dart';
|
||||
@@ -15,6 +17,7 @@ import '../../designs/Responsive.dart';
|
||||
import '../../designs/scene/SceneArt.dart';
|
||||
import '../../designs/text/Text.dart';
|
||||
import '../../utils/Colors.dart';
|
||||
import '../../utils/CommonUtils.dart';
|
||||
import '../home/Home.dart';
|
||||
import '../login/Login.dart';
|
||||
import 'ConnectSplash.dart';
|
||||
@@ -24,6 +27,28 @@ import 'ViewSplash.dart';
|
||||
class SplashState extends State<Splash> implements ConnectSplash {
|
||||
ViewSplash? _model;
|
||||
|
||||
Timer? _statusTimer;
|
||||
|
||||
int _status = 0;
|
||||
|
||||
/// Set once the session check has been asked for, so a tap and the timer
|
||||
/// never both fire it.
|
||||
bool _launched = false;
|
||||
|
||||
/// What the loader says while it fills — the app's three jobs, in order.
|
||||
static const List<String> _statuses = <String>[
|
||||
"Loading your plan",
|
||||
"Checking what's overdue",
|
||||
"Lining up today's routines",
|
||||
"Ready",
|
||||
];
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_statusTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ViewModelBuilder<ViewSplash>.reactive(
|
||||
@@ -51,136 +76,329 @@ class SplashState extends State<Splash> implements ConnectSplash {
|
||||
}
|
||||
|
||||
Future<void> _initiate() async {
|
||||
// Long enough for the mark to land and take one breath, then the session
|
||||
// decides where we land.
|
||||
await Future.delayed(const Duration(milliseconds: 2200));
|
||||
_statusTimer = Timer.periodic(
|
||||
const Duration(milliseconds: 1000), _onStatusTick);
|
||||
|
||||
// Long enough to watch the sun come up and read the screen once; a tap
|
||||
// skips straight on.
|
||||
await Future.delayed(const Duration(milliseconds: 4200));
|
||||
_launch();
|
||||
}
|
||||
|
||||
void _onStatusTick(Timer timer) {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_status = (_status + 1).clamp(0, _statuses.length - 1);
|
||||
});
|
||||
}
|
||||
|
||||
void _onSkip() {
|
||||
_launch();
|
||||
}
|
||||
|
||||
void _launch() {
|
||||
if (_launched || !mounted) {
|
||||
return;
|
||||
}
|
||||
_launched = true;
|
||||
_statusTimer?.cancel();
|
||||
_model?.initialize();
|
||||
}
|
||||
|
||||
String _greeting() {
|
||||
final int hour = DateTime.now().hour;
|
||||
if (hour < 5) {
|
||||
return "Still up";
|
||||
}
|
||||
if (hour < 12) {
|
||||
return "Good morning";
|
||||
}
|
||||
if (hour < 18) {
|
||||
return "Good afternoon";
|
||||
}
|
||||
return "Good evening";
|
||||
}
|
||||
|
||||
Widget _mobileView(BoxConstraints constraints) {
|
||||
return AmbientBackground(
|
||||
child: Stack(
|
||||
children: [
|
||||
// The landscape rising from the bottom edge — the same painted world
|
||||
// the routine cards live in.
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: constraints.maxHeight * 0.34,
|
||||
child: ShaderMask(
|
||||
shaderCallback: (Rect bounds) => const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: <Color>[Color(0x00000000), Color(0xFF000000)],
|
||||
stops: <double>[0.0, 0.45],
|
||||
).createShader(bounds),
|
||||
blendMode: BlendMode.dstIn,
|
||||
child: const SceneArt(palette: sceneWorkout, seed: 11),
|
||||
)
|
||||
.animate()
|
||||
.fadeIn(duration: 1200.ms, curve: Curves.easeOut)
|
||||
.slideY(begin: 0.25, end: 0, duration: 1400.ms,
|
||||
curve: Curves.easeOutCubic),
|
||||
),
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(28, 18, 28, 32),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
text("Grounded", 17, TextType.Regular, color: colorInk),
|
||||
text("Daily discipline", 17, TextType.Regular,
|
||||
color: colorInkSoft),
|
||||
],
|
||||
).animate().fadeIn(duration: 600.ms, delay: 200.ms),
|
||||
const Spacer(flex: 3),
|
||||
Center(child: _mark()),
|
||||
const SizedBox(height: 40),
|
||||
_wordmark(),
|
||||
const Spacer(flex: 4),
|
||||
_loader(),
|
||||
],
|
||||
return GestureDetector(
|
||||
onTap: _onSkip,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: AmbientBackground(
|
||||
child: Stack(
|
||||
children: [
|
||||
_landscape(constraints),
|
||||
..._motes(constraints),
|
||||
SafeArea(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 14, 24, 22),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_topRow(),
|
||||
const Spacer(flex: 2),
|
||||
_hero(),
|
||||
const SizedBox(height: 26),
|
||||
_wordmark(),
|
||||
const Spacer(flex: 2),
|
||||
_howItWorks(),
|
||||
const Spacer(flex: 1),
|
||||
_loader(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The mark on glass, inside rings that breathe outward.
|
||||
Widget _mark() {
|
||||
/// The painted valley rising from the bottom edge while its sun comes up.
|
||||
Widget _landscape(BoxConstraints constraints) {
|
||||
return Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: constraints.maxHeight * 0.42,
|
||||
child: ShaderMask(
|
||||
shaderCallback: (Rect bounds) => const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: <Color>[Color(0x00000000), Color(0xFF000000)],
|
||||
stops: <double>[0.0, 0.4],
|
||||
).createShader(bounds),
|
||||
blendMode: BlendMode.dstIn,
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween<double>(begin: 0, end: 1),
|
||||
duration: const Duration(milliseconds: 3200),
|
||||
curve: Curves.easeOutCubic,
|
||||
builder: (context, lift, child) => SceneArt(
|
||||
palette: sceneWorkout,
|
||||
seed: 11,
|
||||
sunLift: lift,
|
||||
),
|
||||
),
|
||||
)
|
||||
.animate()
|
||||
.fadeIn(duration: 900.ms)
|
||||
.slideY(begin: 0.3, end: 0, duration: 1400.ms,
|
||||
curve: Curves.easeOutCubic),
|
||||
);
|
||||
}
|
||||
|
||||
/// Specks of light drifting up out of the valley.
|
||||
List<Widget> _motes(BoxConstraints constraints) {
|
||||
const List<double> xs = <double>[0.12, 0.27, 0.41, 0.58, 0.7, 0.84, 0.93];
|
||||
|
||||
return List<Widget>.generate(xs.length, (index) {
|
||||
final double size = 4.0 + (index % 3) * 2;
|
||||
|
||||
return Positioned(
|
||||
left: constraints.maxWidth * xs[index],
|
||||
bottom: constraints.maxHeight * (0.08 + (index % 4) * 0.05),
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: colorWhite,
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: colorWhite.withValues(alpha: 0.8), blurRadius: 8),
|
||||
],
|
||||
),
|
||||
)
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.fadeIn(duration: 600.ms, delay: (index * 260).ms)
|
||||
.moveY(
|
||||
begin: 0,
|
||||
end: -140 - index * 12.0,
|
||||
duration: (2600 + index * 240).ms,
|
||||
delay: (index * 260).ms,
|
||||
curve: Curves.easeOut,
|
||||
)
|
||||
.fadeOut(
|
||||
duration: 900.ms, delay: (2000 + index * 240).ms),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _topRow() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const GroundedMark(size: 26),
|
||||
const SizedBox(width: 8),
|
||||
text("Grounded", 16, TextType.Medium, color: colorInk),
|
||||
],
|
||||
),
|
||||
GlassSurface(
|
||||
radius: BorderRadius.circular(99),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 7),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(CupertinoIcons.sun_max_fill,
|
||||
size: 13, color: colorStandingWarned),
|
||||
const SizedBox(width: 6),
|
||||
text(_greeting(), 12, TextType.Medium, color: colorInk),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
).animate().fadeIn(duration: 600.ms, delay: 150.ms).slideY(
|
||||
begin: -0.4, end: 0, duration: 600.ms, delay: 150.ms);
|
||||
}
|
||||
|
||||
/// The mark in breathing rings, with the four things the app holds you to
|
||||
/// floating around it.
|
||||
Widget _hero() {
|
||||
return SizedBox(
|
||||
width: 230,
|
||||
height: 230,
|
||||
height: 270,
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
for (int ring = 0; ring < 3; ring++)
|
||||
Container(
|
||||
width: 120,
|
||||
height: 120,
|
||||
width: 140,
|
||||
height: 140,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: colorWhite.withValues(alpha: 0.9), width: 1.4),
|
||||
color: colorWhite.withValues(alpha: 0.95), width: 1.4),
|
||||
),
|
||||
)
|
||||
.animate(onPlay: (controller) => controller.repeat())
|
||||
.scale(
|
||||
begin: const Offset(1, 1),
|
||||
end: const Offset(1.9, 1.9),
|
||||
duration: 2400.ms,
|
||||
delay: (ring * 800).ms,
|
||||
end: const Offset(1.85, 1.85),
|
||||
duration: 2700.ms,
|
||||
delay: (ring * 900).ms,
|
||||
curve: Curves.easeOutCubic,
|
||||
)
|
||||
.fadeOut(duration: 2400.ms, delay: (ring * 800).ms),
|
||||
GlassSurface(
|
||||
radius: BorderRadius.circular(44),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: colorPrimary.withValues(alpha: 0.35),
|
||||
blurRadius: 40,
|
||||
offset: const Offset(0, 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
// The sun rises out of the ground line, then the arcs light.
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween<double>(begin: 0, end: 1),
|
||||
duration: const Duration(milliseconds: 1700),
|
||||
builder: (context, t, child) => GroundedMark(
|
||||
size: 132,
|
||||
rise: Curves.easeOutCubic
|
||||
.transform((t / 0.6).clamp(0.0, 1.0)),
|
||||
arcs: Curves.easeOut
|
||||
.transform(((t - 0.45) / 0.55).clamp(0.0, 1.0)),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.animate()
|
||||
.scale(
|
||||
begin: const Offset(0.6, 0.6),
|
||||
end: const Offset(1, 1),
|
||||
duration: 900.ms,
|
||||
curve: Curves.easeOutBack,
|
||||
)
|
||||
.fadeIn(duration: 500.ms),
|
||||
.fadeOut(duration: 2700.ms, delay: (ring * 900).ms),
|
||||
_mark(),
|
||||
_chip(const Alignment(-1, -1), sceneWorkout,
|
||||
kindIcon(RoutineKind.Workout), "Workout routines", 0),
|
||||
_chip(const Alignment(1, -0.62), sceneMeditation,
|
||||
kindIcon(RoutineKind.Meditation), "Breathing sessions", 1),
|
||||
_chip(const Alignment(-1, 0.62), sceneMustDo,
|
||||
kindIcon(RoutineKind.MustDo), "Must-do chains", 2),
|
||||
_chip(const Alignment(1, 1), goalScene(goalAccents[4]),
|
||||
CupertinoIcons.flag_fill, "Goals with deadlines", 3),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Two-tone, like the greeting on the home screen.
|
||||
Widget _mark() {
|
||||
return GlassSurface(
|
||||
radius: BorderRadius.circular(46),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(34),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: colorPrimary.withValues(alpha: 0.4),
|
||||
blurRadius: 40,
|
||||
offset: const Offset(0, 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
// The sun rises out of the ground line, then the arcs light.
|
||||
child: TweenAnimationBuilder<double>(
|
||||
tween: Tween<double>(begin: 0, end: 1),
|
||||
duration: const Duration(milliseconds: 1800),
|
||||
builder: (context, t, child) => GroundedMark(
|
||||
size: 128,
|
||||
rise: Curves.easeOutCubic.transform((t / 0.6).clamp(0.0, 1.0)),
|
||||
arcs: Curves.easeOut
|
||||
.transform(((t - 0.45) / 0.55).clamp(0.0, 1.0)),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.animate()
|
||||
.scale(
|
||||
begin: const Offset(0.6, 0.6),
|
||||
end: const Offset(1, 1),
|
||||
duration: 900.ms,
|
||||
curve: Curves.easeOutBack,
|
||||
)
|
||||
.fadeIn(duration: 500.ms);
|
||||
}
|
||||
|
||||
/// A frosted chip with a painted thumbnail. It drifts in, then bobs.
|
||||
Widget _chip(Alignment alignment, List<Color> palette, IconData icon,
|
||||
String label, int index) {
|
||||
return Align(
|
||||
alignment: alignment,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color(0x142A3A30),
|
||||
blurRadius: 16,
|
||||
offset: Offset(0, 6),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: GlassSurface(
|
||||
radius: BorderRadius.circular(99),
|
||||
tint: colorGlassStrong,
|
||||
padding: const EdgeInsets.fromLTRB(4, 4, 12, 4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 28,
|
||||
height: 28,
|
||||
child: ClipOval(
|
||||
child: SceneArt(
|
||||
palette: palette,
|
||||
seed: 5 + index * 9,
|
||||
figure: icon,
|
||||
figureScale: 0.52,
|
||||
figureAlignment: const Alignment(0, 0.2),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
text(label, 12, TextType.Medium, color: colorInk),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.animate()
|
||||
.fadeIn(duration: 500.ms, delay: (900 + index * 180).ms)
|
||||
.slideX(
|
||||
begin: alignment.x < 0 ? -0.3 : 0.3,
|
||||
end: 0,
|
||||
duration: 600.ms,
|
||||
delay: (900 + index * 180).ms,
|
||||
curve: Curves.easeOutCubic,
|
||||
)
|
||||
.then()
|
||||
.animate(onPlay: (controller) => controller.repeat(reverse: true))
|
||||
.moveY(
|
||||
begin: -3,
|
||||
end: 3,
|
||||
duration: (1800 + index * 260).ms,
|
||||
curve: Curves.easeInOut,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Two tones, like the greeting on the home screen; the name catches the
|
||||
/// light once as it lands.
|
||||
Widget _wordmark() {
|
||||
return Column(
|
||||
children: [
|
||||
@@ -189,9 +407,11 @@ class SplashState extends State<Splash> implements ConnectSplash {
|
||||
.animate()
|
||||
.fadeIn(duration: 700.ms, delay: 350.ms)
|
||||
.slideY(begin: 0.3, end: 0, duration: 700.ms, delay: 350.ms,
|
||||
curve: Curves.easeOutCubic),
|
||||
const SizedBox(height: 10),
|
||||
text("A to-do app that\ndoesn't believe you.", 22, TextType.Light,
|
||||
curve: Curves.easeOutCubic)
|
||||
.then(delay: 300.ms)
|
||||
.shimmer(duration: 1200.ms, color: colorLime),
|
||||
const SizedBox(height: 8),
|
||||
text("A to-do app that\ndoesn't believe you.", 21, TextType.Regular,
|
||||
color: colorInkSoft, align: TextAlign.center, height: 1.3)
|
||||
.animate()
|
||||
.fadeIn(duration: 700.ms, delay: 650.ms)
|
||||
@@ -201,7 +421,80 @@ class SplashState extends State<Splash> implements ConnectSplash {
|
||||
);
|
||||
}
|
||||
|
||||
/// A glass capsule that fills while the session resolves.
|
||||
/// The whole idea in three beats, on glass over the valley.
|
||||
Widget _howItWorks() {
|
||||
return GlassSurface(
|
||||
radius: BorderRadius.circular(24),
|
||||
tint: colorGlassStrong,
|
||||
padding: const EdgeInsets.fromLTRB(14, 14, 14, 14),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: _beat(CupertinoIcons.hand_raised_fill, "Commit",
|
||||
"Say it and set a window", 0)),
|
||||
_beatArrow(0),
|
||||
Expanded(
|
||||
child: _beat(CupertinoIcons.timer, "Do it",
|
||||
"Inside the window, with proof", 1)),
|
||||
_beatArrow(1),
|
||||
Expanded(
|
||||
child: _beat(CupertinoIcons.exclamationmark_triangle_fill,
|
||||
"Or owe it", "Misses turn into debt", 2)),
|
||||
],
|
||||
),
|
||||
).animate().fadeIn(duration: 600.ms, delay: 1500.ms).slideY(
|
||||
begin: 0.25, end: 0, duration: 700.ms, delay: 1500.ms,
|
||||
curve: Curves.easeOutCubic);
|
||||
}
|
||||
|
||||
Widget _beat(IconData icon, String title, String caption, int index) {
|
||||
final List<Color> tones = <Color>[
|
||||
colorPrimary,
|
||||
colorKindMeditation,
|
||||
colorStandingGrounded,
|
||||
];
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: tones[index].withValues(alpha: 0.12),
|
||||
),
|
||||
child: Icon(icon, size: 16, color: tones[index]),
|
||||
)
|
||||
.animate()
|
||||
.scale(
|
||||
begin: const Offset(0.4, 0.4),
|
||||
end: const Offset(1, 1),
|
||||
duration: 500.ms,
|
||||
delay: (1700 + index * 250).ms,
|
||||
curve: Curves.easeOutBack,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
text(title, 13, TextType.Medium, color: colorInk),
|
||||
const SizedBox(height: 2),
|
||||
text(caption, 10, TextType.Regular,
|
||||
color: colorInkSoft, align: TextAlign.center, maxLines: 2),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _beatArrow(int index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Icon(CupertinoIcons.chevron_right, size: 12, color: colorInkSoft)
|
||||
.animate()
|
||||
.fadeIn(duration: 400.ms, delay: (1900 + index * 250).ms),
|
||||
);
|
||||
}
|
||||
|
||||
/// A glass capsule that fills while the session resolves, and a status
|
||||
/// line that walks through what is being checked.
|
||||
Widget _loader() {
|
||||
return Column(
|
||||
children: [
|
||||
@@ -209,33 +502,50 @@ class SplashState extends State<Splash> implements ConnectSplash {
|
||||
radius: BorderRadius.circular(99),
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: SizedBox(
|
||||
width: 140,
|
||||
width: 150,
|
||||
height: 6,
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
width: 140,
|
||||
width: 150,
|
||||
decoration: BoxDecoration(
|
||||
color: colorNight,
|
||||
borderRadius: BorderRadius.circular(99),
|
||||
),
|
||||
)
|
||||
.animate()
|
||||
.scaleX(
|
||||
).animate().scaleX(
|
||||
begin: 0.04,
|
||||
end: 1,
|
||||
alignment: Alignment.centerLeft,
|
||||
duration: 2000.ms,
|
||||
duration: 4000.ms,
|
||||
curve: Curves.easeInOutCubic,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
text("Checking what you promised", 12, TextType.Regular,
|
||||
color: colorWhite.withValues(alpha: 0.9)),
|
||||
const SizedBox(height: 10),
|
||||
AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
transitionBuilder: (child, animation) => FadeTransition(
|
||||
opacity: animation,
|
||||
child: SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(0, 0.4),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
child: KeyedSubtree(
|
||||
key: ValueKey<int>(_status),
|
||||
child: text(_statuses[_status], 12, TextType.Medium,
|
||||
color: colorWhite),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
text("Tap anywhere to skip", 10, TextType.Regular,
|
||||
color: colorWhite.withValues(alpha: 0.7)),
|
||||
],
|
||||
).animate().fadeIn(duration: 500.ms, delay: 900.ms);
|
||||
).animate().fadeIn(duration: 500.ms, delay: 600.ms);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -3,7 +3,7 @@ const String imagePath = "assets/images";
|
||||
const String iconPath = "assets/icons";
|
||||
|
||||
/// The mark on a night disc, as the native splash shows it.
|
||||
const String splashMark = "$iconPath/splash_orb.png";
|
||||
const String splashMark = "$iconPath/splash_mark.png";
|
||||
|
||||
const String logoMark = "$iconPath/icon.png";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user