feat(lyrics): customize text size and animate active lines

This commit is contained in:
zarzet committed 2026-10-10 17:04:09 +07:00
1 parent b30dd9fed0
commit d273be026d
10 files changed
+302 -22

No files matched your search

+134
View File
@@ -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());
},
);
}
}