overlay text diff animation for rating/tags
This commit is contained in:
parent
9208d66e22
commit
829ec201eb
16 changed files with 1809 additions and 23 deletions
|
@ -44,7 +44,7 @@ abstract class AvesColorsData {
|
||||||
Color fromString(String string) {
|
Color fromString(String string) {
|
||||||
var color = _stringColors[string];
|
var color = _stringColors[string];
|
||||||
if (color == null) {
|
if (color == null) {
|
||||||
final hash = string.codeUnits.fold<int>(0, (prev, el) => prev = el + ((prev << 5) - prev));
|
final hash = string.codeUnits.fold<int>(0, (prev, v) => prev = v + ((prev << 5) - prev));
|
||||||
final hue = (hash % 360).toDouble();
|
final hue = (hash % 360).toDouble();
|
||||||
color = fromHue(hue);
|
color = fromHue(hue);
|
||||||
_stringColors[string] = color;
|
_stringColors[string] = color;
|
||||||
|
|
|
@ -98,6 +98,7 @@ class DurationsData {
|
||||||
final Duration expansionTileAnimation;
|
final Duration expansionTileAnimation;
|
||||||
final Duration formTransition;
|
final Duration formTransition;
|
||||||
final Duration formTextStyleTransition;
|
final Duration formTextStyleTransition;
|
||||||
|
final Duration textDiffAnimation;
|
||||||
final Duration chartTransition;
|
final Duration chartTransition;
|
||||||
final Duration iconAnimation;
|
final Duration iconAnimation;
|
||||||
final Duration staggeredAnimation;
|
final Duration staggeredAnimation;
|
||||||
|
@ -116,6 +117,7 @@ class 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.formTextStyleTransition = const Duration(milliseconds: 800),
|
||||||
|
this.textDiffAnimation = const Duration(milliseconds: 150),
|
||||||
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),
|
||||||
|
@ -132,6 +134,7 @@ class DurationsData {
|
||||||
expansionTileAnimation: const Duration(microseconds: 1),
|
expansionTileAnimation: const Duration(microseconds: 1),
|
||||||
formTransition: Duration.zero,
|
formTransition: Duration.zero,
|
||||||
formTextStyleTransition: Duration.zero,
|
formTextStyleTransition: Duration.zero,
|
||||||
|
textDiffAnimation: Duration.zero,
|
||||||
chartTransition: Duration.zero,
|
chartTransition: Duration.zero,
|
||||||
iconAnimation: Duration.zero,
|
iconAnimation: Duration.zero,
|
||||||
staggeredAnimation: Duration.zero,
|
staggeredAnimation: Duration.zero,
|
||||||
|
|
1495
lib/utils/diff_match.dart
Normal file
1495
lib/utils/diff_match.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/basic/animated_text.dart';
|
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -8,8 +8,8 @@ import 'package:aves/services/accessibility_service.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/action_mixins/overlay_snack_bar.dart';
|
import 'package:aves/widgets/common/action_mixins/overlay_snack_bar.dart';
|
||||||
import 'package:aves/widgets/common/basic/animated_text.dart';
|
|
||||||
import 'package:aves/widgets/common/basic/circle.dart';
|
import 'package:aves/widgets/common/basic/circle.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
import 'package:aves/widgets/viewer/entry_viewer_page.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -364,7 +364,7 @@ class _FeedbackMessageState extends State<_FeedbackMessage> with SingleTickerPro
|
||||||
// progress color is provided by the caller,
|
// progress color is provided by the caller,
|
||||||
// because we cannot use the app context theme here
|
// because we cannot use the app context theme here
|
||||||
foreground: widget.progressColor,
|
foreground: widget.progressColor,
|
||||||
center: AnimatedText(
|
center: ChangeHighlightText(
|
||||||
'${(remainingDurationMillis / 1000).ceil()}',
|
'${(remainingDurationMillis / 1000).ceil()}',
|
||||||
style: contentTextStyle.copyWith(
|
style: contentTextStyle.copyWith(
|
||||||
shadows: [
|
shadows: [
|
||||||
|
|
276
lib/widgets/common/basic/text/animated_diff.dart
Normal file
276
lib/widgets/common/basic/text/animated_diff.dart
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:aves/utils/diff_match.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
class AnimatedDiffText extends StatefulWidget {
|
||||||
|
final String text;
|
||||||
|
final TextStyle? textStyle;
|
||||||
|
final StrutStyle? strutStyle;
|
||||||
|
final Curve curve;
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
|
const AnimatedDiffText(
|
||||||
|
this.text, {
|
||||||
|
super.key,
|
||||||
|
this.textStyle,
|
||||||
|
this.strutStyle,
|
||||||
|
this.curve = Curves.easeInOutCubic,
|
||||||
|
required this.duration,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AnimatedDiffText> createState() => _AnimatedDiffTextState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AnimatedDiffTextState extends State<AnimatedDiffText> with SingleTickerProviderStateMixin {
|
||||||
|
late final AnimationController _controller;
|
||||||
|
late final Animation<double> _animation;
|
||||||
|
final List<_TextDiff> _diffs = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller = AnimationController(
|
||||||
|
duration: widget.duration,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
_animation = CurvedAnimation(parent: _controller, curve: widget.curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_computeDiff(widget.text, widget.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant AnimatedDiffText oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
final oldText = oldWidget.text;
|
||||||
|
final newText = widget.text;
|
||||||
|
if (oldText != newText) {
|
||||||
|
_computeDiff(oldText, newText);
|
||||||
|
_controller.forward(from: 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AnimatedBuilder(
|
||||||
|
animation: _animation,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Text.rich(
|
||||||
|
TextSpan(
|
||||||
|
children: _diffs.map((diff) {
|
||||||
|
final oldText = diff.item1;
|
||||||
|
final newText = diff.item2;
|
||||||
|
final oldWidth = diff.item3;
|
||||||
|
final newWidth = diff.item4;
|
||||||
|
final text = (_animation.value == 0 ? oldText : newText) ?? '';
|
||||||
|
return WidgetSpan(
|
||||||
|
child: AnimatedSize(
|
||||||
|
key: ValueKey(diff),
|
||||||
|
curve: widget.curve,
|
||||||
|
duration: widget.duration,
|
||||||
|
child: AnimatedSwitcher(
|
||||||
|
duration: widget.duration,
|
||||||
|
switchInCurve: widget.curve,
|
||||||
|
switchOutCurve: widget.curve,
|
||||||
|
layoutBuilder: (currentChild, previousChildren) {
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
...previousChildren.map(
|
||||||
|
(child) => ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: min(oldWidth, newWidth),
|
||||||
|
),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (currentChild != null) currentChild,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
text,
|
||||||
|
key: Key(text),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
),
|
||||||
|
strutStyle: widget.strutStyle,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
double textWidth(String text) {
|
||||||
|
final para = RenderParagraph(
|
||||||
|
TextSpan(text: text, style: widget.textStyle),
|
||||||
|
textDirection: Directionality.of(context),
|
||||||
|
textScaleFactor: MediaQuery.textScaleFactorOf(context),
|
||||||
|
strutStyle: widget.strutStyle,
|
||||||
|
)..layout(const BoxConstraints(), parentUsesSize: true);
|
||||||
|
return para.getMaxIntrinsicWidth(double.infinity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// use an adaptation of Google's `Diff Match and Patch`
|
||||||
|
// as package `diffutil_dart` (as of v3.0.0) is unreliable
|
||||||
|
void _computeDiff(String oldText, String newText) {
|
||||||
|
final oldCharacters = oldText.characters.join();
|
||||||
|
final newCharacters = newText.characters.join();
|
||||||
|
|
||||||
|
final dmp = DiffMatchPatch();
|
||||||
|
final d = dmp.diff_main(oldCharacters, newCharacters);
|
||||||
|
dmp.diff_cleanupSemantic(d);
|
||||||
|
|
||||||
|
_diffs
|
||||||
|
..clear()
|
||||||
|
..addAll(d.map((diff) {
|
||||||
|
final text = diff.text;
|
||||||
|
switch (diff.operation) {
|
||||||
|
case Operation.delete:
|
||||||
|
return Tuple4(text, null, textWidth(text), .0);
|
||||||
|
case Operation.insert:
|
||||||
|
return Tuple4(null, text, .0, textWidth(text));
|
||||||
|
case Operation.equal:
|
||||||
|
default:
|
||||||
|
final width = textWidth(text);
|
||||||
|
return Tuple4(text, text, width, width);
|
||||||
|
}
|
||||||
|
}).fold<List<_TextDiff>>([], (prev, v) {
|
||||||
|
if (prev.isNotEmpty) {
|
||||||
|
final last = prev.last;
|
||||||
|
final prevNewText = last.item2;
|
||||||
|
if (prevNewText == null) {
|
||||||
|
// previous diff is a deletion
|
||||||
|
final thisOldText = v.item1;
|
||||||
|
if (thisOldText == null) {
|
||||||
|
// this diff is an insertion
|
||||||
|
// merge deletion and insertion as a change operation
|
||||||
|
final change = Tuple4(last.item1, v.item2, last.item3, v.item4);
|
||||||
|
return [...prev.take(prev.length - 1), change];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...prev, v];
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// void _computeDiff(String oldText, String newText) {
|
||||||
|
// final oldCharacters = oldText.characters.toList();
|
||||||
|
// final newCharacters = newText.characters.toList();
|
||||||
|
// final diffResult = calculateListDiff<String>(oldCharacters, newCharacters, detectMoves: false);
|
||||||
|
// final updates = diffResult.getUpdatesWithData().toList();
|
||||||
|
// List<TextDiff> diffs = [];
|
||||||
|
// DataDiffUpdate<String>? pendingUpdate;
|
||||||
|
// int lastPos = oldCharacters.length;
|
||||||
|
// void addKeep(int pos) {
|
||||||
|
// if (pos < lastPos) {
|
||||||
|
// final text = oldCharacters.sublist(pos, lastPos).join();
|
||||||
|
// final width = textWidth(text);
|
||||||
|
// diffs.insert(0, Tuple4(text, text, width, width));
|
||||||
|
// lastPos = pos;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// void commit(DataDiffUpdate<String>? update) {
|
||||||
|
// update?.when(
|
||||||
|
// insert: (pos, data) {
|
||||||
|
// addKeep(pos);
|
||||||
|
// diffs.insert(0, Tuple4(null, data, 0, textWidth(data)));
|
||||||
|
// lastPos = pos;
|
||||||
|
// },
|
||||||
|
// remove: (pos, data) {
|
||||||
|
// addKeep(pos + data.length);
|
||||||
|
// diffs.insert(0, Tuple4(data, null, textWidth(data), 0));
|
||||||
|
// lastPos = pos;
|
||||||
|
// },
|
||||||
|
// change: (pos, oldData, newData) {
|
||||||
|
// addKeep(pos + oldData.length);
|
||||||
|
// diffs.insert(0, Tuple4(oldData, newData, textWidth(oldData), textWidth(newData)));
|
||||||
|
// lastPos = pos;
|
||||||
|
// },
|
||||||
|
// move: (from, to, data) {
|
||||||
|
// assert(false, '`move` update: from=$from, to=$from, data=$data');
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// for (var update in updates) {
|
||||||
|
// update.when(
|
||||||
|
// insert: (pos, data) {
|
||||||
|
// if (pendingUpdate == null) {
|
||||||
|
// pendingUpdate = update;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if (pendingUpdate is DataInsert) {
|
||||||
|
// final pendingInsert = pendingUpdate as DataInsert;
|
||||||
|
// if (pendingInsert.position == pos) {
|
||||||
|
// // merge insertions
|
||||||
|
// pendingUpdate = DataInsert(position: pos, data: data + pendingInsert.data);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// } else if (pendingUpdate is DataRemove) {
|
||||||
|
// final pendingRemove = pendingUpdate as DataRemove;
|
||||||
|
// if (pendingRemove.position == pos) {
|
||||||
|
// // convert to change
|
||||||
|
// pendingUpdate = DataChange(position: pos, oldData: pendingRemove.data, newData: data);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// } else if (pendingUpdate is DataChange) {
|
||||||
|
// final pendingChange = pendingUpdate as DataChange;
|
||||||
|
// if (pendingChange.position == pos) {
|
||||||
|
// // merge changes
|
||||||
|
// pendingUpdate = DataChange(position: pos, oldData: pendingChange.oldData, newData: data + pendingChange.newData);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// commit(pendingUpdate);
|
||||||
|
// pendingUpdate = update;
|
||||||
|
// },
|
||||||
|
// remove: (pos, data) {
|
||||||
|
// if (pendingUpdate == null) {
|
||||||
|
// pendingUpdate = update;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// if (pendingUpdate is DataRemove) {
|
||||||
|
// final pendingRemove = pendingUpdate as DataRemove;
|
||||||
|
// if (pendingRemove.position == pos + data.length) {
|
||||||
|
// // merge removals
|
||||||
|
// pendingUpdate = DataRemove(position: pos, data: data + pendingRemove.data);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// commit(pendingUpdate);
|
||||||
|
// pendingUpdate = update;
|
||||||
|
// },
|
||||||
|
// change: (pos, oldData, newData) {
|
||||||
|
// assert(false, '`change` update: from=$pos, oldData=$oldData, newData=$newData');
|
||||||
|
// },
|
||||||
|
// move: (from, to, data) {
|
||||||
|
// assert(false, '`move` update: from=$from, to=$from, data=$data');
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// commit(pendingUpdate);
|
||||||
|
// addKeep(0);
|
||||||
|
// _diffs
|
||||||
|
// ..clear()
|
||||||
|
// ..addAll(diffs);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef _TextDiff = Tuple4<String?, String?, double, double>;
|
|
@ -1,13 +1,13 @@
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class AnimatedText extends StatefulWidget {
|
class ChangeHighlightText extends StatefulWidget {
|
||||||
final String data;
|
final String data;
|
||||||
final TextStyle style, changedStyle;
|
final TextStyle style, changedStyle;
|
||||||
final Curve curve;
|
final Curve curve;
|
||||||
final Duration duration;
|
final Duration duration;
|
||||||
|
|
||||||
const AnimatedText(
|
const ChangeHighlightText(
|
||||||
this.data, {
|
this.data, {
|
||||||
super.key,
|
super.key,
|
||||||
required this.style,
|
required this.style,
|
||||||
|
@ -17,10 +17,10 @@ class AnimatedText extends StatefulWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<AnimatedText> createState() => _AnimatedTextState();
|
State<ChangeHighlightText> createState() => _ChangeHighlightTextState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AnimatedTextState extends State<AnimatedText> with SingleTickerProviderStateMixin {
|
class _ChangeHighlightTextState extends State<ChangeHighlightText> with SingleTickerProviderStateMixin {
|
||||||
late final AnimationController _controller;
|
late final AnimationController _controller;
|
||||||
late final Animation<TextStyle> _style;
|
late final Animation<TextStyle> _style;
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class _AnimatedTextState extends State<AnimatedText> with SingleTickerProviderSt
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(AnimatedText oldWidget) {
|
void didUpdateWidget(ChangeHighlightText oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (oldWidget.data != widget.data) {
|
if (oldWidget.data != widget.data) {
|
||||||
_controller
|
_controller
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/common/basic/animated_text.dart';
|
import 'package:aves/widgets/common/basic/text/change_highlight.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ class AvesCaption extends StatelessWidget {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final subtitleStyle = theme.textTheme.bodySmall!;
|
final subtitleStyle = theme.textTheme.bodySmall!;
|
||||||
final subtitleChangeShadowColor = theme.colorScheme.onPrimary;
|
final subtitleChangeShadowColor = theme.colorScheme.onPrimary;
|
||||||
return AnimatedText(
|
return ChangeHighlightText(
|
||||||
// provide key to refresh on theme brightness change
|
// provide key to refresh on theme brightness change
|
||||||
key: ValueKey(subtitleChangeShadowColor),
|
key: ValueKey(subtitleChangeShadowColor),
|
||||||
data,
|
data,
|
||||||
|
|
|
@ -3,7 +3,7 @@ import 'dart:ui';
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/widgets/common/basic/outlined_text.dart';
|
import 'package:aves/widgets/common/basic/text/outlined.dart';
|
||||||
import 'package:aves/widgets/common/fx/highlight_decoration.dart';
|
import 'package:aves/widgets/common/fx/highlight_decoration.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/widgets/common/basic/outlined_text.dart';
|
import 'package:aves/widgets/common/basic/text/outlined.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/plugin_api.dart';
|
import 'package:flutter_map/plugin_api.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/ref/brand_colors.dart';
|
import 'package:aves/ref/brand_colors.dart';
|
||||||
import 'package:aves/theme/colors.dart';
|
import 'package:aves/theme/colors.dart';
|
||||||
import 'package:aves/theme/durations.dart';
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/widgets/common/basic/outlined_text.dart';
|
import 'package:aves/widgets/common/basic/text/outlined.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/fx/highlight_decoration.dart';
|
import 'package:aves/widgets/common/fx/highlight_decoration.dart';
|
||||||
import 'package:aves/widgets/common/identity/highlight_title.dart';
|
import 'package:aves/widgets/common/identity/highlight_title.dart';
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:aves/model/settings/enums/enums.dart';
|
import 'package:aves/model/settings/enums/enums.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
import 'package:aves/widgets/common/basic/outlined_text.dart';
|
import 'package:aves/widgets/common/basic/text/background_painter.dart';
|
||||||
import 'package:aves/widgets/common/basic/text_background_painter.dart';
|
import 'package:aves/widgets/common/basic/text/outlined.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/common/fx/borders.dart';
|
import 'package:aves/widgets/common/fx/borders.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/subtitle.dart';
|
import 'package:aves/widgets/viewer/visual/subtitle/subtitle.dart';
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import 'package:aves/model/entry.dart';
|
import 'package:aves/model/entry.dart';
|
||||||
|
import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/utils/constants.dart';
|
import 'package:aves/utils/constants.dart';
|
||||||
|
import 'package:aves/widgets/common/basic/text/animated_diff.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||||
import 'package:aves/widgets/viewer/overlay/details/details.dart';
|
import 'package:aves/widgets/viewer/overlay/details/details.dart';
|
||||||
import 'package:decorated_icon/decorated_icon.dart';
|
import 'package:decorated_icon/decorated_icon.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class OverlayRatingTagsRow extends AnimatedWidget {
|
class OverlayRatingTagsRow extends AnimatedWidget {
|
||||||
final AvesEntry entry;
|
final AvesEntry entry;
|
||||||
|
@ -33,16 +36,25 @@ class OverlayRatingTagsRow extends AnimatedWidget {
|
||||||
final tags = entry.tags.join(Constants.separator);
|
final tags = entry.tags.join(Constants.separator);
|
||||||
final hasTags = tags.isNotEmpty;
|
final hasTags = tags.isNotEmpty;
|
||||||
|
|
||||||
|
final animationDuration = context.select<DurationsData, Duration>((v) => v.textDiffAnimation);
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
if (ratingString.isNotEmpty) ...[
|
AnimatedDiffText(
|
||||||
Text(ratingString, strutStyle: Constants.overflowStrutStyle),
|
ratingString,
|
||||||
if (hasTags) const Text(Constants.separator),
|
strutStyle: Constants.overflowStrutStyle,
|
||||||
],
|
duration: animationDuration,
|
||||||
|
),
|
||||||
if (hasTags) ...[
|
if (hasTags) ...[
|
||||||
|
if (ratingString.isNotEmpty) const Text(Constants.separator),
|
||||||
DecoratedIcon(AIcons.tag, size: ViewerDetailOverlayContent.iconSize, shadows: ViewerDetailOverlayContent.shadows(context)),
|
DecoratedIcon(AIcons.tag, size: ViewerDetailOverlayContent.iconSize, shadows: ViewerDetailOverlayContent.shadows(context)),
|
||||||
const SizedBox(width: ViewerDetailOverlayContent.iconPadding),
|
const SizedBox(width: ViewerDetailOverlayContent.iconPadding),
|
||||||
Expanded(child: Text(tags, strutStyle: Constants.overflowStrutStyle)),
|
Expanded(
|
||||||
|
child: AnimatedDiffText(
|
||||||
|
tags,
|
||||||
|
strutStyle: Constants.overflowStrutStyle,
|
||||||
|
duration: animationDuration,
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:aves/model/settings/enums/subtitle_position.dart';
|
import 'package:aves/model/settings/enums/subtitle_position.dart';
|
||||||
import 'package:aves/model/settings/settings.dart';
|
import 'package:aves/model/settings/settings.dart';
|
||||||
import 'package:aves/widgets/common/basic/outlined_text.dart';
|
import 'package:aves/widgets/common/basic/text/background_painter.dart';
|
||||||
import 'package:aves/widgets/common/basic/text_background_painter.dart';
|
import 'package:aves/widgets/common/basic/text/outlined.dart';
|
||||||
import 'package:aves/widgets/viewer/video/controller.dart';
|
import 'package:aves/widgets/viewer/video/controller.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/state.dart';
|
import 'package:aves/widgets/viewer/visual/state.dart';
|
||||||
import 'package:aves/widgets/viewer/visual/subtitle/ass_parser.dart';
|
import 'package:aves/widgets/viewer/visual/subtitle/ass_parser.dart';
|
||||||
|
|
Loading…
Reference in a new issue