Files
PeachDeployments/lib/Peach/see/splash/SplashState.dart
alvocool 38881d1fe1 Add app source and tests
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-11 10:15:30 +03:00

169 lines
5.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:stacked/stacked.dart';
import '../../about/internal/application/DeploymentRecord.dart';
import '../../about/internal/application/NavigatorType.dart';
import '../../about/internal/application/ServerProfile.dart';
import '../../about/internal/application/TextType.dart';
import '../../configs/Navigator.dart';
import '../../configs/ThemeController.dart';
import '../../designs/text/Text.dart';
import '../../utils/Colors.dart';
import '../home/Home.dart';
import 'ConnectSplash.dart';
import 'Splash.dart';
import 'ViewSplash.dart';
class SplashState extends State<Splash>
with SingleTickerProviderStateMixin
implements ConnectSplash {
ViewSplash? _model;
late final AnimationController _pulse;
@override
void initState() {
super.initState();
_pulse = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 900),
)..repeat(reverse: true);
}
@override
void dispose() {
_pulse.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ViewModelBuilder<ViewSplash>.reactive(
viewModelBuilder: () => ViewSplash(context, this),
onViewModelReady: _onReady,
builder: (context, viewModel, child) => Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_brandMark(),
const SizedBox(height: 24),
text(
'Peach',
36,
TextType.Bold,
context: context,
letterSpacing: -0.8,
),
const SizedBox(height: 6),
text(
'DEPLOYMENT COCKPIT',
11.5,
TextType.Bold,
color: context.jp.textFaint,
letterSpacing: 4,
),
const SizedBox(height: 40),
SizedBox(
width: 150,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: LinearProgressIndicator(
minHeight: 3,
backgroundColor: context.jp.surface2,
),
),
),
],
),
),
),
);
}
void _onReady(ViewSplash model) {
_model = model;
// onViewModelReady fires mid-build; a synchronous setState from the
// first load would dirty this State while it is still being built.
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) _initiate();
});
}
Future<void> _initiate() async {
_model?.bootstrap();
}
// ── ConnectSplash ──────────────────────────────────────────────────────────
@override
void onBootstrapped(
List<ServerProfile> servers,
List<DeploymentRecord> history,
String themeMode,
) {
if (!mounted) return;
// Apply the stored theme before the dashboard paints, so it never flashes
// the default and then switches.
ThemeController.apply(themeMode);
PeachNavigation().navigateToPage(
NavigatorType.makeNewMain,
Home(servers: servers, history: history),
context,
);
}
// ── screen-only widgets ────────────────────────────────────────────────────
/// Layered logo: a soft pulsing halo behind a beveled brand tile.
Widget _brandMark() {
return SizedBox(
width: 132,
height: 132,
child: Stack(
alignment: Alignment.center,
children: [
ScaleTransition(
scale: Tween(
begin: 0.9,
end: 1.12,
).animate(CurvedAnimation(parent: _pulse, curve: Curves.easeInOut)),
child: Container(
width: 132,
height: 132,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
PeachTheme.seed.withValues(alpha: 0.28),
PeachTheme.seed.withValues(alpha: 0),
],
),
),
),
),
Container(
width: 90,
height: 90,
decoration: BoxDecoration(
gradient: PeachTheme.brandGradient,
borderRadius: BorderRadius.circular(26),
border: Border.all(
color: colorWhite.withValues(alpha: 0.18),
width: 1,
),
boxShadow: PeachTheme.glow(
PeachTheme.seed,
opacity: 0.5,
blur: 44,
spread: 0,
),
),
child: Icon(Icons.rocket_launch, color: colorWhite, size: 44),
),
],
),
);
}
}