chore: bring flutter client, docs, deploy and tools into monorepo

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 03:22:24 +02:00
co-authored by Claude Opus 4.8
parent 8c0519d125
commit 4b80c7dbb0
189 changed files with 19124 additions and 0 deletions
@@ -0,0 +1,345 @@
import 'package:flutter/material.dart';
/// Explicit animation example using AnimationController
///
/// This example demonstrates:
/// - AnimationController setup and lifecycle
/// - Tween interpolation
/// - AnimatedWidget pattern for reusable animations
/// - AnimatedBuilder pattern for complex widgets
/// - Animation status monitoring
void main() => runApp(const ExplicitAnimationApp());
class ExplicitAnimationApp extends StatelessWidget {
const ExplicitAnimationApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Explicit Animation',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const LogoAnimationDemo(),
);
}
}
/// Basic explicit animation with addListener and setState
class BasicExplicitAnimation extends StatefulWidget {
const BasicExplicitAnimation({super.key});
@override
State<BasicExplicitAnimation> createState() => _BasicExplicitAnimationState();
}
class _BasicExplicitAnimationState extends State<BasicExplicitAnimation>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 300).animate(_controller)
..addListener(() {
setState(() {});
});
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Basic Explicit')),
body: Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: _animation.value,
width: _animation.value,
child: const FlutterLogo(),
),
),
);
}
}
/// AnimatedWidget pattern - best for reusable animated widgets
class AnimatedLogo extends AnimatedWidget {
const AnimatedLogo({super.key, required Animation<double> animation})
: super(listenable: animation);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: 10),
height: animation.value,
width: animation.value,
child: const FlutterLogo(),
),
);
}
}
class AnimatedWidgetDemo extends StatefulWidget {
const AnimatedWidgetDemo({super.key});
@override
State<AnimatedWidgetDemo> createState() => _AnimatedWidgetDemoState();
}
class _AnimatedWidgetDemoState extends State<AnimatedWidgetDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 300).animate(_controller);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Animated Widget')),
body: AnimatedLogo(animation: _animation),
);
}
}
/// AnimatedBuilder pattern - best for complex widgets
class LogoWidget extends StatelessWidget {
const LogoWidget({super.key});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 10),
child: const FlutterLogo(),
);
}
}
class GrowTransition extends StatelessWidget {
const GrowTransition({
required this.child,
required this.animation,
super.key,
});
final Widget child;
final Animation<double> animation;
@override
Widget build(BuildContext context) {
return Center(
child: AnimatedBuilder(
animation: animation,
builder: (context, child) {
return SizedBox(
height: animation.value,
width: animation.value,
child: child,
);
},
child: child,
),
);
}
}
class AnimatedBuilderDemo extends StatefulWidget {
const AnimatedBuilderDemo({super.key});
@override
State<AnimatedBuilderDemo> createState() => _AnimatedBuilderDemoState();
}
class _AnimatedBuilderDemoState extends State<AnimatedBuilderDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween<double>(begin: 0, end: 300).animate(_controller);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Animated Builder')),
body: GrowTransition(animation: _animation, child: const LogoWidget()),
);
}
}
/// Multiple simultaneous animations with status monitoring
class MultiPropertyAnimation extends AnimatedWidget {
const MultiPropertyAnimation({
super.key,
required Animation<double> animation,
}) : super(listenable: animation);
static final _opacityTween = Tween<double>(begin: 0.1, end: 1);
static final _sizeTween = Tween<double>(begin: 0, end: 300);
static final _colorTween = ColorTween(begin: Colors.red, end: Colors.blue);
@override
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Center(
child: Opacity(
opacity: _opacityTween.evaluate(animation),
child: Container(
height: _sizeTween.evaluate(animation),
width: _sizeTween.evaluate(animation),
decoration: BoxDecoration(
color: _colorTween.evaluate(animation),
borderRadius: BorderRadius.circular(12),
),
child: const FlutterLogo(),
),
),
);
}
}
class MultiPropertyDemo extends StatefulWidget {
const MultiPropertyDemo({super.key});
@override
State<MultiPropertyDemo> createState() => _MultiPropertyDemoState();
}
class _MultiPropertyDemoState extends State<MultiPropertyDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = CurvedAnimation(parent: _controller, curve: Curves.easeIn)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reverse();
} else if (status == AnimationStatus.dismissed) {
_controller.forward();
}
});
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Multi Property')),
body: MultiPropertyAnimation(animation: _animation),
);
}
}
/// Main demo widget showing all examples
class LogoAnimationDemo extends StatelessWidget {
const LogoAnimationDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Explicit Animations')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => const BasicExplicitAnimation(),
),
),
child: const Text('Basic Explicit'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => const AnimatedWidgetDemo(),
),
),
child: const Text('Animated Widget'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => const AnimatedBuilderDemo(),
),
),
child: const Text('Animated Builder'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => const MultiPropertyDemo(),
),
),
child: const Text('Multi Property'),
),
],
),
),
);
}
}
@@ -0,0 +1,223 @@
import 'package:flutter/material.dart';
/// Hero animation example for shared element transitions
///
/// This example demonstrates:
/// - Basic hero animation with matching tags
/// - Custom PhotoHero widget
/// - Navigation between screens with hero transition
/// - MaterialRectCenterArcTween for aspect ratio preservation
void main() => runApp(const HeroAnimationApp());
class HeroAnimationApp extends StatelessWidget {
const HeroAnimationApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hero Animation',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const GalleryScreen(),
);
}
}
/// Custom hero widget for images
class PhotoHero extends StatelessWidget {
const PhotoHero({
super.key,
required this.photo,
this.onTap,
required this.width,
});
final String photo;
final VoidCallback? onTap;
final double width;
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
child: Hero(
tag: photo,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
child: Image.asset(
photo,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
return Container(
width: width,
height: width,
color: Colors.grey,
child: const Icon(Icons.broken_image),
);
},
),
),
),
),
);
}
}
/// Gallery screen showing multiple hero images
class GalleryScreen extends StatelessWidget {
const GalleryScreen({super.key});
static const List<String> _photos = [
'images/photo1.png',
'images/photo2.png',
'images/photo3.png',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Hero Gallery')),
body: GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: _photos.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: PhotoHero(
photo: _photos[index],
width: 100,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) => DetailScreen(photo: _photos[index]),
),
);
},
),
);
},
),
);
}
}
/// Detail screen showing hero image in fullscreen
class DetailScreen extends StatelessWidget {
const DetailScreen({super.key, required this.photo});
final String photo;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Detail')),
body: Center(
child: PhotoHero(
photo: photo,
width: MediaQuery.of(context).size.width - 32,
onTap: () => Navigator.of(context).pop(),
),
),
);
}
}
/// Example with circular hero (using placeholder instead of asset)
class CircularHeroExample extends StatefulWidget {
const CircularHeroExample({super.key});
@override
State<CircularHeroExample> createState() => _CircularHeroExampleState();
}
class _CircularHeroExampleState extends State<CircularHeroExample> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Circular Hero')),
body: ListView(
children: List.generate(3, (index) {
final tag = 'circle-$index';
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Hero(
tag: tag,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (context) =>
CircularDetailScreen(tag: tag, index: index),
),
);
},
child: Container(
width: 80,
height: 80,
decoration: const BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle,
),
),
),
),
),
),
);
}),
),
);
}
}
class CircularDetailScreen extends StatelessWidget {
const CircularDetailScreen({
super.key,
required this.tag,
required this.index,
});
final String tag;
final int index;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Circular Detail')),
body: Center(
child: Hero(
tag: tag,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => Navigator.of(context).pop(),
child: Container(
width: 300,
height: 300,
decoration: BoxDecoration(
color: Colors.blue.withValues(alpha: 0.8),
borderRadius: BorderRadius.circular(150),
),
child: Center(
child: Text(
'Image $index',
style: const TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
),
),
),
);
}
}
@@ -0,0 +1,149 @@
import 'package:flutter/material.dart';
/// Implicit animation example showing basic fade animation
///
/// This example demonstrates:
/// - AnimatedOpacity for fade in/out
/// - State-driven animation triggers
/// - Simple, declarative animation code
void main() => runApp(const ImplicitAnimationApp());
class ImplicitAnimationApp extends StatelessWidget {
const ImplicitAnimationApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Implicit Animation',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const FadeExample(),
);
}
}
class FadeExample extends StatefulWidget {
const FadeExample({super.key});
@override
State<FadeExample> createState() => _FadeExampleState();
}
class _FadeExampleState extends State<FadeExample> {
bool _visible = true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Implicit Fade')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedOpacity(
opacity: _visible ? 1.0 : 0.0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
child: const FlutterLogo(size: 100),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() => _visible = !_visible),
child: Text(_visible ? 'Fade Out' : 'Fade In'),
),
],
),
),
);
}
}
/// AnimatedContainer example - animating multiple properties
class AnimatedContainerExample extends StatefulWidget {
const AnimatedContainerExample({super.key});
@override
State<AnimatedContainerExample> createState() =>
_AnimatedContainerExampleState();
}
class _AnimatedContainerExampleState extends State<AnimatedContainerExample> {
bool _expanded = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Animated Container')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _expanded ? 200 : 100,
height: _expanded ? 200 : 100,
decoration: BoxDecoration(
color: _expanded ? Colors.blue : Colors.red,
borderRadius: BorderRadius.circular(_expanded ? 20 : 8),
),
child: const FlutterLogo(),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() => _expanded = !_expanded),
child: Text(_expanded ? 'Shrink' : 'Expand'),
),
],
),
),
);
}
}
/// TweenAnimationBuilder example - custom tween without boilerplate
class TweenAnimationBuilderExample extends StatefulWidget {
const TweenAnimationBuilderExample({super.key});
@override
State<TweenAnimationBuilderExample> createState() =>
_TweenAnimationBuilderExampleState();
}
class _TweenAnimationBuilderExampleState
extends State<TweenAnimationBuilderExample> {
bool _animated = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Tween Animation Builder')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0, end: 1),
duration: const Duration(seconds: 1),
curve: Curves.easeInOut,
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.scale(scale: value, child: child),
);
},
child: const FlutterLogo(size: 100),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () => setState(() => _animated = !_animated),
child: const Text('Animate'),
),
],
),
),
);
}
}
@@ -0,0 +1,239 @@
import 'package:flutter/material.dart';
/// Staggered animation example with multiple property animations
///
/// This example demonstrates:
/// - Single AnimationController driving multiple animations
/// - Interval-based timing for sequential/overlapping animations
/// - Multiple tweens for different properties
/// - Status monitoring for animation loops
void main() => runApp(const StaggeredAnimationApp());
class StaggeredAnimationApp extends StatelessWidget {
const StaggeredAnimationApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Staggered Animation',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: const StaggeredDemo(),
);
}
}
/// Staggered animation with fade, scale, and color changes
class StaggeredAnimation extends StatelessWidget {
StaggeredAnimation({super.key, required this.animation});
late final Animation<double> opacity = Tween<double>(begin: 0.0, end: 1.0)
.animate(
CurvedAnimation(
parent: animation,
curve: const Interval(0.0, 0.1, curve: Curves.ease),
),
);
late final Animation<double> width = Tween<double>(begin: 50.0, end: 150.0)
.animate(
CurvedAnimation(
parent: animation,
curve: const Interval(0.125, 0.25, curve: Curves.ease),
),
);
late final Animation<double> height = Tween<double>(begin: 50.0, end: 150.0)
.animate(
CurvedAnimation(
parent: animation,
curve: const Interval(0.25, 0.375, curve: Curves.ease),
),
);
late final Animation<BorderRadius?> borderRadius =
BorderRadiusTween(
begin: BorderRadius.circular(4),
end: BorderRadius.circular(75),
).animate(
CurvedAnimation(
parent: animation,
curve: const Interval(0.375, 0.5, curve: Curves.ease),
),
);
late final Animation<Color?> color =
ColorTween(begin: Colors.red, end: Colors.orange).animate(
CurvedAnimation(
parent: animation,
curve: const Interval(0.5, 0.625, curve: Curves.ease),
),
);
final Animation<double> animation;
Widget _buildAnimation(BuildContext context, Widget? child) {
return Center(
child: Opacity(
opacity: opacity.value,
child: Container(
width: width.value,
height: height.value,
decoration: BoxDecoration(
color: color.value,
border: Border.all(color: Colors.indigo[300]!, width: 3),
borderRadius: borderRadius.value,
),
),
),
);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(animation: animation, builder: _buildAnimation);
}
}
class StaggeredDemo extends StatefulWidget {
const StaggeredDemo({super.key});
@override
State<StaggeredDemo> createState() => _StaggeredDemoState();
}
class _StaggeredDemoState extends State<StaggeredDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 2000),
vsync: this,
);
}
Future<void> _playAnimation() async {
try {
await _controller.forward().orCancel;
await _controller.reverse().orCancel;
} on TickerCanceled {
// Animation was canceled
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Staggered Animation')),
body: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: _playAnimation,
child: Center(
child: Container(
width: 300,
height: 300,
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.1),
border: Border.all(color: Colors.black.withValues(alpha: 0.5)),
),
child: StaggeredAnimation(animation: _controller.view),
),
),
),
);
}
}
/// Staggered menu animation example
class StaggeredMenuExample extends StatefulWidget {
const StaggeredMenuExample({super.key});
@override
State<StaggeredMenuExample> createState() => _StaggeredMenuExampleState();
}
class _StaggeredMenuExampleState extends State<StaggeredMenuExample>
with SingleTickerProviderStateMixin {
static const _menuTitles = [
'Declarative Style',
'Premade Widgets',
'Stateful Hot Reload',
'Native Performance',
'Great Community',
];
static const _initialDelayTime = Duration(milliseconds: 50);
static const _itemSlideTime = Duration(milliseconds: 250);
static const _staggerTime = Duration(milliseconds: 50);
late AnimationController _controller;
final _animationDuration =
_initialDelayTime + (_staggerTime * _menuTitles.length) + _itemSlideTime;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: _animationDuration,
vsync: this,
);
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Staggered Menu')),
body: ListView.builder(
itemCount: _menuTitles.length,
itemBuilder: (context, index) {
final start =
_initialDelayTime.inMilliseconds.toDouble() +
(_staggerTime.inMilliseconds.toDouble() * index);
final end = start + _itemSlideTime.inMilliseconds.toDouble();
final intervalStart =
start / _animationDuration.inMilliseconds.toDouble();
final intervalEnd =
end / _animationDuration.inMilliseconds.toDouble();
final animation = CurvedAnimation(
parent: _controller,
curve: Interval(intervalStart, intervalEnd, curve: Curves.easeOut),
);
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(animation),
child: FadeTransition(opacity: animation, child: child),
);
},
child: ListTile(title: Text(_menuTitles[index])),
);
},
),
);
}
}