mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-18 21:47:34 +02:00
feat(lyrics): customize text size and animate active lines
This commit is contained in:
1 parent
b30dd9fed0
commit
d273be026d
10 files changed
+302
-22
No files matched your search
@@ -7156,6 +7156,8 @@
|
||||
"appearanceCustomFontSubtitle": "Import a TTF or OTF file (up to 10 MB). Overrides the theme and system font.",
|
||||
"appearanceCustomFontInvalid": "Could not load this font. Choose a valid TTF or OTF file up to 10 MB.",
|
||||
"appearanceCustomFontReset": "Use theme font",
|
||||
"lyricsTextSize": "Lyrics text size",
|
||||
"lyricsTextPreview": "Follow every line of your favorite song",
|
||||
"optionsEmbeddedCoverSizeOriginal": "Original resolution",
|
||||
"@optionsEmbeddedCoverSizeOriginal": {
|
||||
"description": "Option that preserves the provider artwork at its original resolution"
|
||||
|
||||
@@ -7169,6 +7169,8 @@
|
||||
"appearanceCustomFontSubtitle": "Impor file TTF atau OTF (maksimal 10 MB). Menggantikan font tema dan sistem.",
|
||||
"appearanceCustomFontInvalid": "Font tidak dapat dimuat. Pilih file TTF atau OTF yang valid, maksimal 10 MB.",
|
||||
"appearanceCustomFontReset": "Gunakan font tema",
|
||||
"lyricsTextSize": "Ukuran teks lirik",
|
||||
"lyricsTextPreview": "Ikuti setiap baris lagu favoritmu",
|
||||
"optionsEmbeddedCoverSizeOriginal": "Resolusi asli",
|
||||
"@optionsEmbeddedCoverSizeOriginal": {
|
||||
"description": "Option that preserves the provider artwork at its original resolution"
|
||||
|
||||
@@ -9,6 +9,10 @@ const String kMornyeAccentKey = 'mornye_accent';
|
||||
const String kUseSystemFontKey = 'use_system_font';
|
||||
const String kCustomFontIdKey = 'custom_font_id';
|
||||
const String kCustomFontNameKey = 'custom_font_name';
|
||||
const String kLyricsTextScaleKey = 'lyrics_text_scale';
|
||||
|
||||
double normalizeLyricsTextScale(num? value) =>
|
||||
value != null && value.isFinite ? value.toDouble().clamp(0.8, 1.4) : 1.0;
|
||||
const String kMornyeGlassClarityKey = 'mornye_glass_clarity';
|
||||
const String kMornyeGlassOutlinesKey = 'mornye_glass_outlines';
|
||||
const double kDefaultMornyeGlassClarity = 0.75;
|
||||
@@ -47,6 +51,7 @@ class ThemeSettings {
|
||||
final bool useSystemFont;
|
||||
final String customFontId;
|
||||
final String customFontName;
|
||||
final double lyricsTextScale;
|
||||
final double mornyeGlassClarity;
|
||||
final bool mornyeGlassOutlines;
|
||||
|
||||
@@ -60,6 +65,7 @@ class ThemeSettings {
|
||||
this.useSystemFont = false,
|
||||
this.customFontId = '',
|
||||
this.customFontName = '',
|
||||
this.lyricsTextScale = 1,
|
||||
this.mornyeGlassClarity = kDefaultMornyeGlassClarity,
|
||||
this.mornyeGlassOutlines = true,
|
||||
});
|
||||
@@ -76,6 +82,7 @@ class ThemeSettings {
|
||||
bool? useSystemFont,
|
||||
String? customFontId,
|
||||
String? customFontName,
|
||||
double? lyricsTextScale,
|
||||
double? mornyeGlassClarity,
|
||||
bool? mornyeGlassOutlines,
|
||||
}) {
|
||||
@@ -89,6 +96,9 @@ class ThemeSettings {
|
||||
useSystemFont: useSystemFont ?? this.useSystemFont,
|
||||
customFontId: customFontId ?? this.customFontId,
|
||||
customFontName: customFontName ?? this.customFontName,
|
||||
lyricsTextScale: normalizeLyricsTextScale(
|
||||
lyricsTextScale ?? this.lyricsTextScale,
|
||||
),
|
||||
mornyeGlassClarity: normalizeMornyeGlassClarity(
|
||||
mornyeGlassClarity ?? this.mornyeGlassClarity,
|
||||
),
|
||||
@@ -106,6 +116,7 @@ class ThemeSettings {
|
||||
kUseSystemFontKey: useSystemFont,
|
||||
kCustomFontIdKey: customFontId,
|
||||
kCustomFontNameKey: customFontName,
|
||||
kLyricsTextScaleKey: lyricsTextScale,
|
||||
kMornyeGlassClarityKey: mornyeGlassClarity,
|
||||
kMornyeGlassOutlinesKey: mornyeGlassOutlines,
|
||||
};
|
||||
@@ -121,6 +132,9 @@ class ThemeSettings {
|
||||
useSystemFont: json[kUseSystemFontKey] as bool? ?? false,
|
||||
customFontId: json[kCustomFontIdKey] as String? ?? '',
|
||||
customFontName: json[kCustomFontNameKey] as String? ?? '',
|
||||
lyricsTextScale: normalizeLyricsTextScale(
|
||||
json[kLyricsTextScaleKey] as num?,
|
||||
),
|
||||
mornyeGlassClarity: normalizeMornyeGlassClarity(
|
||||
json[kMornyeGlassClarityKey] as num?,
|
||||
),
|
||||
@@ -141,6 +155,7 @@ class ThemeSettings {
|
||||
other.useSystemFont == useSystemFont &&
|
||||
other.customFontId == customFontId &&
|
||||
other.customFontName == customFontName &&
|
||||
other.lyricsTextScale == lyricsTextScale &&
|
||||
other.mornyeGlassClarity == mornyeGlassClarity &&
|
||||
other.mornyeGlassOutlines == mornyeGlassOutlines;
|
||||
}
|
||||
@@ -156,6 +171,7 @@ class ThemeSettings {
|
||||
useSystemFont.hashCode ^
|
||||
customFontId.hashCode ^
|
||||
customFontName.hashCode ^
|
||||
lyricsTextScale.hashCode ^
|
||||
mornyeGlassClarity.hashCode ^
|
||||
mornyeGlassOutlines.hashCode;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ ThemeSettings loadBootstrapThemeSettings(SharedPreferences prefs) {
|
||||
useSystemFont: prefs.getBool(kUseSystemFontKey) ?? false,
|
||||
customFontId: prefs.getString(kCustomFontIdKey) ?? '',
|
||||
customFontName: prefs.getString(kCustomFontNameKey) ?? '',
|
||||
lyricsTextScale: normalizeLyricsTextScale(
|
||||
prefs.getDouble(kLyricsTextScaleKey),
|
||||
),
|
||||
mornyeGlassClarity: normalizeMornyeGlassClarity(
|
||||
prefs.getDouble(kMornyeGlassClarityKey),
|
||||
),
|
||||
@@ -81,6 +84,7 @@ class ThemeNotifier extends Notifier<ThemeSettings> {
|
||||
await prefs.setBool(kUseSystemFontKey, snapshot.useSystemFont);
|
||||
await prefs.setString(kCustomFontIdKey, snapshot.customFontId);
|
||||
await prefs.setString(kCustomFontNameKey, snapshot.customFontName);
|
||||
await prefs.setDouble(kLyricsTextScaleKey, snapshot.lyricsTextScale);
|
||||
await prefs.setDouble(
|
||||
kMornyeGlassClarityKey,
|
||||
snapshot.mornyeGlassClarity,
|
||||
@@ -136,6 +140,11 @@ class ThemeNotifier extends Notifier<ThemeSettings> {
|
||||
await _saveToStorage();
|
||||
}
|
||||
|
||||
Future<void> setLyricsTextScale(double value, {bool persist = true}) async {
|
||||
_editState((current) => current.copyWith(lyricsTextScale: value));
|
||||
if (persist) await _saveToStorage();
|
||||
}
|
||||
|
||||
Future<void> setMornyeGlassClarity(
|
||||
double value, {
|
||||
bool persist = true,
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:spotiflac_android/widgets/app_sliver_header.dart';
|
||||
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/app_alert_dialog.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
import 'package:spotiflac_android/widgets/lyrics_size_setting.dart';
|
||||
|
||||
class LyricsSettingsPage extends ConsumerWidget {
|
||||
const LyricsSettingsPage({super.key});
|
||||
@@ -34,6 +35,7 @@ class LyricsSettingsPage extends ConsumerWidget {
|
||||
SliverToBoxAdapter(
|
||||
child: SettingsGroup(
|
||||
children: [
|
||||
const LyricsSizeSetting(),
|
||||
SettingsSwitchItem(
|
||||
icon: Icons.light_mode_outlined,
|
||||
title: context.l10n.lyricsKeepScreenOn,
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/providers/theme_provider.dart';
|
||||
import 'package:spotiflac_android/widgets/lyrics_text_scale.dart';
|
||||
|
||||
class LyricsSizeSetting extends ConsumerWidget {
|
||||
const LyricsSizeSetting({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final scale = ref.watch(
|
||||
themeProvider.select((theme) => theme.lyricsTextScale),
|
||||
);
|
||||
final notifier = ref.read(themeProvider.notifier);
|
||||
final percent = '${(scale * 100).round()}%';
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.l10n.lyricsTextSize,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
Text(percent),
|
||||
IconButton(
|
||||
tooltip: context.l10n.libraryFilterReset,
|
||||
onPressed: scale == 1
|
||||
? null
|
||||
: () => notifier.setLyricsTextScale(1),
|
||||
icon: const Icon(Icons.restart_alt),
|
||||
),
|
||||
],
|
||||
),
|
||||
LyricsTextScale(
|
||||
child: Text(
|
||||
context.l10n.lyricsTextPreview,
|
||||
style: Theme.of(context).textTheme.headlineSmall,
|
||||
),
|
||||
),
|
||||
Slider(
|
||||
value: scale,
|
||||
min: 0.8,
|
||||
max: 1.4,
|
||||
divisions: 12,
|
||||
label: percent,
|
||||
semanticFormatterCallback: (value) => '${(value * 100).round()}%',
|
||||
onChanged: (value) =>
|
||||
notifier.setLyricsTextScale(value, persist: false),
|
||||
onChangeEnd: notifier.setLyricsTextScale,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:spotiflac_android/providers/theme_provider.dart';
|
||||
|
||||
/// Applies the lyric preference on top of the system's accessibility scaling.
|
||||
class LyricsTextScale extends ConsumerWidget {
|
||||
const LyricsTextScale({super.key, required this.child});
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final factor = ref.watch(
|
||||
themeProvider.select((theme) => theme.lyricsTextScale),
|
||||
);
|
||||
final media = MediaQuery.of(context);
|
||||
return MediaQuery(
|
||||
data: media.copyWith(
|
||||
textScaler: LyricsTextScaler(media.textScaler, factor),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LyricsTextScaler extends TextScaler {
|
||||
const LyricsTextScaler(this.system, this.factor);
|
||||
final TextScaler system;
|
||||
final double factor;
|
||||
|
||||
@override
|
||||
double scale(double fontSize) => system.scale(fontSize) * factor;
|
||||
|
||||
@override
|
||||
double get textScaleFactor => scale(14) / 14;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
other is LyricsTextScaler &&
|
||||
other.system == system &&
|
||||
other.factor == factor;
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(system, factor);
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import 'package:spotiflac_android/widgets/lyric_supplement_transition.dart';
|
||||
import 'package:spotiflac_android/widgets/lyric_scroll_motion.dart';
|
||||
import 'package:spotiflac_android/widgets/lyric_gap_indicator.dart';
|
||||
import 'package:spotiflac_android/widgets/mornye_player_slider.dart';
|
||||
import 'package:spotiflac_android/widgets/lyrics_text_scale.dart';
|
||||
|
||||
const _mornyeLyricFontSize = 34.0;
|
||||
const mornyeLyricsContentInset = 28.0 + MornyePlayerSlider.horizontalInset;
|
||||
@@ -389,7 +390,7 @@ class _SyncedLyricsViewState extends ConsumerState<SyncedLyricsView>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _measureMornyeLines(double width) {
|
||||
void _measureMornyeLines(BuildContext context, double width) {
|
||||
final style = _mornyeLyricStyle(context);
|
||||
final scaler = MediaQuery.textScalerOf(context);
|
||||
final direction = Directionality.of(context);
|
||||
@@ -557,18 +558,20 @@ class _SyncedLyricsViewState extends ConsumerState<SyncedLyricsView>
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => TweenAnimationBuilder<Offset>(
|
||||
tween: Tween(
|
||||
end: Offset(
|
||||
widget.showPronunciation ? 1 : 0,
|
||||
widget.showTranslation ? 1 : 0,
|
||||
Widget build(BuildContext context) => LyricsTextScale(
|
||||
child: TweenAnimationBuilder<Offset>(
|
||||
tween: Tween(
|
||||
end: Offset(
|
||||
widget.showPronunciation ? 1 : 0,
|
||||
widget.showTranslation ? 1 : 0,
|
||||
),
|
||||
),
|
||||
duration: MediaQuery.disableAnimationsOf(context)
|
||||
? Duration.zero
|
||||
: const Duration(milliseconds: 420),
|
||||
curve: Curves.easeInOutCubic,
|
||||
builder: (context, visibility, _) => _buildLyrics(context, visibility),
|
||||
),
|
||||
duration: MediaQuery.disableAnimationsOf(context)
|
||||
? Duration.zero
|
||||
: const Duration(milliseconds: 420),
|
||||
curve: Curves.easeInOutCubic,
|
||||
builder: (context, visibility, _) => _buildLyrics(context, visibility),
|
||||
);
|
||||
|
||||
Widget _buildLyrics(BuildContext context, Offset visibility) {
|
||||
@@ -636,7 +639,7 @@ class _SyncedLyricsViewState extends ConsumerState<SyncedLyricsView>
|
||||
: 1.0;
|
||||
if (mornye) {
|
||||
final previousExtents = _lineExtents;
|
||||
_measureMornyeLines(layoutWidth);
|
||||
_measureMornyeLines(context, layoutWidth);
|
||||
// Reuse text measurements; only interpolate row heights as the
|
||||
// supplements fade. Scrolling follows the same animation clock.
|
||||
final fullExtents = List.generate(_lineMeasurements.length, (row) {
|
||||
@@ -897,15 +900,19 @@ class _SyncedLyricsViewState extends ConsumerState<SyncedLyricsView>
|
||||
onTap: () =>
|
||||
ref.read(musicPlayerControllerProvider).seek(line.time),
|
||||
child: AnimatedScale(
|
||||
scale: mornye || isActive ? 1.0 : 0.96,
|
||||
alignment: Alignment.center,
|
||||
duration: const Duration(milliseconds: 280),
|
||||
scale: isActive ? 1.0 : 0.96,
|
||||
alignment: !mornye
|
||||
? Alignment.center
|
||||
: textAlign == TextAlign.right
|
||||
? Alignment.centerRight
|
||||
: Alignment.centerLeft,
|
||||
duration: motion,
|
||||
curve: Curves.easeOutCubic,
|
||||
child: AnimatedOpacity(
|
||||
opacity: mornye
|
||||
? (isActive || highContrast ? 1 : 0.48)
|
||||
: (isActive ? 1.0 : (isPast ? 0.55 : 0.85)),
|
||||
duration: const Duration(milliseconds: 280),
|
||||
duration: motion,
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
import 'package:spotiflac_android/utils/playback_seek_preview.dart';
|
||||
import 'package:spotiflac_android/widgets/app_loading_indicator.dart';
|
||||
import 'package:spotiflac_android/widgets/player_lyrics.dart';
|
||||
import 'package:spotiflac_android/widgets/lyrics_text_scale.dart';
|
||||
|
||||
class PlayerLyricsSection extends ConsumerWidget {
|
||||
const PlayerLyricsSection({
|
||||
@@ -90,13 +91,15 @@ class PlayerLyricsSection extends ConsumerWidget {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
metadata.lyrics.plainText,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
height: 1.6,
|
||||
color: colorScheme.onSurface,
|
||||
LyricsTextScale(
|
||||
child: Text(
|
||||
metadata.lyrics.plainText,
|
||||
style: Theme.of(context).textTheme.titleMedium?.copyWith(
|
||||
height: 1.6,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
?_lyricsCredits(),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/models/theme_settings.dart';
|
||||
import 'package:spotiflac_android/providers/theme_provider.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
import 'package:spotiflac_android/utils/lyrics_parser.dart';
|
||||
import 'package:spotiflac_android/widgets/lyrics_size_setting.dart';
|
||||
import 'package:spotiflac_android/widgets/lyrics_text_scale.dart';
|
||||
import 'package:spotiflac_android/widgets/player_lyrics.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() => SharedPreferences.setMockInitialValues({}));
|
||||
|
||||
test('lyric scaling respects accessibility and validates stored bounds', () {
|
||||
const scale = LyricsTextScaler(TextScaler.linear(1.5), 1.2);
|
||||
expect(scale.scale(20), 36);
|
||||
expect(normalizeLyricsTextScale(double.nan), 1);
|
||||
expect(normalizeLyricsTextScale(20), 1.4);
|
||||
expect(normalizeLyricsTextScale(0), 0.8);
|
||||
final settings = const ThemeSettings().copyWith(lyricsTextScale: 1.3);
|
||||
expect(ThemeSettings.fromJson(settings.toJson()), settings);
|
||||
});
|
||||
|
||||
testWidgets('size slider previews, persists on release and resets', (
|
||||
tester,
|
||||
) async {
|
||||
final container = ProviderContainer();
|
||||
addTearDown(container.dispose);
|
||||
await tester.pumpWidget(
|
||||
UncontrolledProviderScope(
|
||||
container: container,
|
||||
child: MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: const Scaffold(body: LyricsSizeSetting()),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final slider = tester.widget<Slider>(find.byType(Slider));
|
||||
slider.onChanged!(1.3);
|
||||
await tester.pump();
|
||||
expect(find.text('130%'), findsOneWidget);
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
expect(prefs.getDouble(kLyricsTextScaleKey), isNull);
|
||||
slider.onChangeEnd!(1.3);
|
||||
await tester.pumpAndSettle();
|
||||
expect(loadBootstrapThemeSettings(prefs).lyricsTextScale, 1.3);
|
||||
await tester.tap(find.byTooltip('Reset'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(loadBootstrapThemeSettings(prefs).lyricsTextScale, 1);
|
||||
});
|
||||
|
||||
for (final reduced in [false, true]) {
|
||||
testWidgets(
|
||||
'Mornye subtly scales active lyrics without changing layout (reduced=$reduced)',
|
||||
(tester) async {
|
||||
final preview = ValueNotifier<Duration?>(const Duration(seconds: 1));
|
||||
addTearDown(preview.dispose);
|
||||
final theme = MornyeTheme.build(Brightness.dark);
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
child: MaterialApp(
|
||||
theme: theme,
|
||||
builder: (context, child) => MediaQuery(
|
||||
data: MediaQuery.of(
|
||||
context,
|
||||
).copyWith(disableAnimations: reduced),
|
||||
child: child!,
|
||||
),
|
||||
home: Scaffold(
|
||||
body: SyncedLyricsView(
|
||||
lyrics: const ParsedLyrics(
|
||||
synced: true,
|
||||
wordSynced: false,
|
||||
plainText: '',
|
||||
lines: [
|
||||
LyricLine(time: Duration.zero, text: 'First line'),
|
||||
LyricLine(
|
||||
time: Duration(seconds: 10),
|
||||
text: 'Second line',
|
||||
),
|
||||
],
|
||||
),
|
||||
seekPreview: preview,
|
||||
colorScheme: theme.colorScheme,
|
||||
isActive: true,
|
||||
showPronunciation: false,
|
||||
showTranslation: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final scales = tester
|
||||
.widgetList<AnimatedScale>(find.byType(AnimatedScale))
|
||||
.toList();
|
||||
expect(scales.map((widget) => widget.scale), containsAll([1.0, 0.96]));
|
||||
expect(
|
||||
scales.first.duration,
|
||||
reduced ? Duration.zero : const Duration(milliseconds: 280),
|
||||
);
|
||||
final sizes = find
|
||||
.byType(AnimatedScale)
|
||||
.evaluate()
|
||||
.map((e) => (e.renderObject! as RenderBox).size)
|
||||
.toList();
|
||||
preview.value = const Duration(seconds: 11);
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
tester
|
||||
.widgetList<AnimatedScale>(find.byType(AnimatedScale))
|
||||
.map((w) => w.scale)
|
||||
.toList(),
|
||||
scales.map((w) => w.scale).toList().reversed.toList(),
|
||||
);
|
||||
expect(
|
||||
find
|
||||
.byType(AnimatedScale)
|
||||
.evaluate()
|
||||
.map((e) => (e.renderObject! as RenderBox).size)
|
||||
.toList(),
|
||||
sizes,
|
||||
);
|
||||
expect(tester.takeException(), isNull);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user