caption change effect

This commit is contained in:
Thibault Deckers 2022-10-03 17:00:08 +02:00
parent b4fe7781dc
commit 6005f016e3
9 changed files with 142 additions and 14 deletions

View file

@ -96,6 +96,7 @@ class DurationsData {
// common animations // common animations
final Duration expansionTileAnimation; final Duration expansionTileAnimation;
final Duration formTransition; final Duration formTransition;
final Duration formTextStyleTransition;
final Duration chartTransition; final Duration chartTransition;
final Duration iconAnimation; final Duration iconAnimation;
final Duration staggeredAnimation; final Duration staggeredAnimation;
@ -112,6 +113,7 @@ class DurationsData {
const DurationsData({ const DurationsData({
this.expansionTileAnimation = const Duration(milliseconds: 200), this.expansionTileAnimation = const Duration(milliseconds: 200),
this.formTransition = const Duration(milliseconds: 200), this.formTransition = const Duration(milliseconds: 200),
this.formTextStyleTransition = const Duration(milliseconds: 800),
this.chartTransition = const Duration(milliseconds: 400), this.chartTransition = const Duration(milliseconds: 400),
this.iconAnimation = const Duration(milliseconds: 300), this.iconAnimation = const Duration(milliseconds: 300),
this.staggeredAnimation = const Duration(milliseconds: 375), this.staggeredAnimation = const Duration(milliseconds: 375),
@ -126,6 +128,7 @@ class DurationsData {
// as of Flutter v2.5.1, `ExpansionPanelList` throws if animation duration is zero // as of Flutter v2.5.1, `ExpansionPanelList` throws if animation duration is zero
expansionTileAnimation: const Duration(microseconds: 1), expansionTileAnimation: const Duration(microseconds: 1),
formTransition: Duration.zero, formTransition: Duration.zero,
formTextStyleTransition: Duration.zero,
chartTransition: Duration.zero, chartTransition: Duration.zero,
iconAnimation: Duration.zero, iconAnimation: Duration.zero,
staggeredAnimation: Duration.zero, staggeredAnimation: Duration.zero,

View file

@ -0,0 +1,86 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
class AnimatedText extends StatefulWidget {
final String data;
final TextStyle style, changedStyle;
final Curve curve;
final Duration duration;
const AnimatedText(
this.data, {
super.key,
required this.style,
required this.changedStyle,
this.curve = Curves.linear,
required this.duration,
});
@override
State<AnimatedText> createState() => _AnimatedTextState();
}
class _AnimatedTextState extends State<AnimatedText> with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final Animation<TextStyle> _style;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: widget.duration,
vsync: this,
)
..value = 1
..addListener(() => setState(() {}));
_style = _ShadowedTextStyleTween(begin: widget.changedStyle, end: widget.style).animate(CurvedAnimation(
parent: _controller,
curve: widget.curve,
));
}
@override
void didUpdateWidget(AnimatedText oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.data != widget.data) {
_controller
..value = 0
..forward();
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Text(
widget.data,
style: _style.value,
);
}
}
class _ShadowedTextStyleTween extends Tween<TextStyle> {
_ShadowedTextStyleTween({super.begin, super.end});
@override
TextStyle lerp(double t) {
final textStyle = TextStyle.lerp(begin, end, t)!;
final beginShadows = begin!.shadows;
final endShadows = end!.shadows;
if (beginShadows != null && endShadows != null && beginShadows.length == endShadows.length) {
return textStyle.copyWith(
shadows: beginShadows.mapIndexed((i, a) {
final b = endShadows[i];
return Shadow.lerp(a, b, t)!;
}).toList(),
);
} else {
return textStyle;
}
}
}

View file

@ -29,13 +29,13 @@ class _CircularIndicatorState extends State<CircularIndicator> {
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
Circle( _Circle(
radius: widget.radius, radius: widget.radius,
lineWidth: widget.lineWidth, lineWidth: widget.lineWidth,
percent: 1.0, percent: 1.0,
color: widget.background, color: widget.background,
), ),
Circle( _Circle(
radius: widget.radius, radius: widget.radius,
lineWidth: widget.lineWidth, lineWidth: widget.lineWidth,
percent: widget.percent, percent: widget.percent,
@ -48,11 +48,11 @@ class _CircularIndicatorState extends State<CircularIndicator> {
} }
} }
class Circle extends StatelessWidget { class _Circle extends StatelessWidget {
final double radius, lineWidth, percent; final double radius, lineWidth, percent;
final Color color; final Color color;
const Circle({ const _Circle({
super.key, super.key,
required this.radius, required this.radius,
required this.lineWidth, required this.lineWidth,

View file

@ -33,7 +33,7 @@ class _MultiCrossFaderState extends State<MultiCrossFader> {
@override @override
void didUpdateWidget(covariant MultiCrossFader oldWidget) { void didUpdateWidget(covariant MultiCrossFader oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (_first == oldWidget.child) { if (oldWidget.child == _first) {
_second = widget.child; _second = widget.child;
_fadeState = CrossFadeState.showSecond; _fadeState = CrossFadeState.showSecond;
} else { } else {

View file

@ -0,0 +1,42 @@
import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/basic/animated_text.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AvesCaption extends StatelessWidget {
final String data;
const AvesCaption(
this.data, {
super.key,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final subtitleStyle = theme.textTheme.caption!;
final subtitleChangeShadowColor = theme.colorScheme.onPrimary;
return AnimatedText(
// provide key to refresh on theme brightness change
key: ValueKey(subtitleChangeShadowColor),
data,
style: subtitleStyle.copyWith(
shadows: [
Shadow(
color: subtitleChangeShadowColor.withOpacity(0),
blurRadius: 0,
)
],
),
changedStyle: subtitleStyle.copyWith(
shadows: [
Shadow(
color: subtitleChangeShadowColor,
blurRadius: 3,
)
],
),
duration: context.read<DurationsData>().formTextStyleTransition,
);
}
}

View file

@ -38,7 +38,7 @@ class _SearchPageState extends State<SearchPage> {
@override @override
void didUpdateWidget(covariant SearchPage oldWidget) { void didUpdateWidget(covariant SearchPage oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (widget.delegate != oldWidget.delegate) { if (oldWidget.delegate != widget.delegate) {
_unregisterWidget(oldWidget); _unregisterWidget(oldWidget);
_registerWidget(widget); _registerWidget(widget);
} }

View file

@ -3,6 +3,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/theme/themes.dart'; import 'package:aves/theme/themes.dart';
import 'package:aves/widgets/common/basic/text_dropdown_button.dart'; import 'package:aves/widgets/common/basic/text_dropdown_button.dart';
import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/aves_caption.dart';
import 'package:aves/widgets/common/identity/highlight_title.dart'; import 'package:aves/widgets/common/identity/highlight_title.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -76,12 +77,7 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
_selectedSort = v; _selectedSort = v;
_reverseSort = false; _reverseSort = false;
}, },
bottom: _selectedSort != null bottom: _selectedSort != null ? AvesCaption(widget.sortOrder(_selectedSort as S, _reverseSort)) : null,
? Text(
widget.sortOrder(_selectedSort as S, _reverseSort),
style: Theme.of(context).textTheme.caption,
)
: null,
), ),
AnimatedSwitcher( AnimatedSwitcher(
duration: context.read<DurationsData>().formTransition, duration: context.read<DurationsData>().formTransition,

View file

@ -1,5 +1,6 @@
import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/settings/settings.dart';
import 'package:aves/theme/durations.dart'; import 'package:aves/theme/durations.dart';
import 'package:aves/widgets/common/identity/aves_caption.dart';
import 'package:aves/widgets/dialogs/aves_selection_dialog.dart'; import 'package:aves/widgets/dialogs/aves_selection_dialog.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -103,7 +104,7 @@ class SettingsSelectionListTile<T extends Enum> extends StatelessWidget {
selector: selector, selector: selector,
builder: (context, current, child) => ListTile( builder: (context, current, child) => ListTile(
title: Text(tileTitle), title: Text(tileTitle),
subtitle: Text(getName(context, current)), subtitle: AvesCaption(getName(context, current)),
onTap: () => showSelectionDialog<T>( onTap: () => showSelectionDialog<T>(
context: context, context: context,
builder: (context) => AvesSelectionDialog<T>( builder: (context) => AvesSelectionDialog<T>(

View file

@ -25,7 +25,7 @@ class _CrumbLineState extends State<CrumbLine> {
@override @override
void didUpdateWidget(covariant CrumbLine oldWidget) { void didUpdateWidget(covariant CrumbLine oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (widget.directory.relativeDir.length > oldWidget.directory.relativeDir.length) { if (oldWidget.directory.relativeDir.length < widget.directory.relativeDir.length) {
// scroll to show last crumb // scroll to show last crumb
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
final extent = _controller.position.maxScrollExtent; final extent = _controller.position.maxScrollExtent;