From ee8719cc5d774f880af3f30584755f5954c2ca64 Mon Sep 17 00:00:00 2001 From: zarzet <42882290+zarzet@users.noreply.github.com> Date: Sun, 4 Oct 2026 04:37:36 +0700 Subject: [PATCH] fix(automix): use themed transition effect picker --- lib/widgets/automix_settings.dart | 70 +++++++++++++++++++++------ test/automix_settings_test.dart | 78 +++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 15 deletions(-) diff --git a/lib/widgets/automix_settings.dart b/lib/widgets/automix_settings.dart index 0d5ce15d..f11e1470 100644 --- a/lib/widgets/automix_settings.dart +++ b/lib/widgets/automix_settings.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/models/automix_options.dart'; import 'package:spotiflac_android/providers/settings_provider.dart'; +import 'package:spotiflac_android/widgets/app_bottom_sheet.dart'; import 'package:spotiflac_android/widgets/settings_group.dart'; class AutoMixSettings extends ConsumerStatefulWidget { @@ -17,6 +18,53 @@ class _AutoMixSettingsState extends ConsumerState { double? _pitch; double? _speed; + void _showEffectPicker( + BuildContext context, + AutoMixEffect current, + Map names, + ) { + final notifier = ref.read(settingsProvider.notifier); + showAppBottomSheet( + context: context, + useRootNavigator: true, + title: context.l10n.autoMixEffect, + maxHeightFactor: 0.85, + builder: (sheetContext) => SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (final effect in AutoMixEffect.values) + Semantics( + selected: effect == current, + child: AppSheetOption( + leading: Icon(switch (effect) { + AutoMixEffect.auto => Icons.auto_awesome, + AutoMixEffect.crossfade => Icons.compare_arrows, + AutoMixEffect.muffled => Icons.graphic_eq, + AutoMixEffect.echo => Icons.surround_sound, + AutoMixEffect.pitch => Icons.music_note, + AutoMixEffect.custom => Icons.tune, + }), + title: Text(names[effect]!), + trailing: effect == current + ? Icon( + Icons.check, + color: Theme.of(sheetContext).colorScheme.primary, + ) + : null, + onTap: () { + notifier.setAutoMixEffect(effect); + Navigator.pop(sheetContext); + }, + ), + ), + const SizedBox(height: 8), + ], + ), + ), + ); + } + @override Widget build(BuildContext context) { final settings = ref.watch(settingsProvider); @@ -83,21 +131,13 @@ class _AutoMixSettingsState extends ConsumerState { setState(() => _duration = null); }, ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: DropdownButtonFormField( - initialValue: settings.autoMixEffect, - decoration: InputDecoration(labelText: l10n.autoMixEffect), - items: [ - for (final effect in AutoMixEffect.values) - DropdownMenuItem(value: effect, child: Text(names[effect]!)), - ], - onChanged: enabled - ? (value) { - if (value != null) notifier.setAutoMixEffect(value); - } - : null, - ), + SettingsItem( + icon: Icons.tune, + title: l10n.autoMixEffect, + subtitle: names[settings.autoMixEffect], + onTap: enabled + ? () => _showEffectPicker(context, settings.autoMixEffect, names) + : null, ), if (settings.autoMixEffect == AutoMixEffect.pitch || settings.autoMixEffect == AutoMixEffect.custom) diff --git a/test/automix_settings_test.dart b/test/automix_settings_test.dart index 0cc79674..11bfab06 100644 --- a/test/automix_settings_test.dart +++ b/test/automix_settings_test.dart @@ -5,8 +5,12 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/models/automix_options.dart'; import 'package:spotiflac_android/models/settings.dart'; +import 'package:spotiflac_android/models/theme_settings.dart'; import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/widgets/automix_settings.dart'; +import 'package:spotiflac_android/theme/app_theme.dart'; +import 'package:spotiflac_android/theme/mornye_theme.dart'; +import 'package:spotiflac_android/widgets/app_bottom_sheet.dart'; void main() { testWidgets( @@ -50,7 +54,81 @@ void main() { for (final slider in tester.widgetList(find.byType(Slider))) { expect(slider.onChanged, isNull); } + await tester.tap(find.text('Transition effect')); + await tester.pumpAndSettle(); + expect(find.byType(BottomSheet), findsNothing); expect(tester.takeException(), isNull); }, ); + + for (final mornye in [false, true]) { + testWidgets( + 'effect picker follows app theme and saves selection ($mornye)', + (tester) async { + SharedPreferences.setMockInitialValues({}); + tester.view.physicalSize = const Size(350, 600); + tester.view.devicePixelRatio = 1; + addTearDown(tester.view.reset); + final container = ProviderContainer( + overrides: [ + initialSettingsProvider.overrideWithValue( + const AppSettings(autoMix: true), + ), + ], + ); + addTearDown(container.dispose); + await tester.pumpWidget( + UncontrolledProviderScope( + container: container, + child: MaterialApp( + theme: mornye + ? MornyeTheme.build( + Brightness.dark, + accent: MornyeAccent.blue, + ) + : AppTheme.light(seedColor: Colors.blue), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: const MornyeSettingsTheme( + child: Scaffold( + body: SingleChildScrollView(child: AutoMixSettings()), + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + await tester.tap(find.text('Transition effect')); + await tester.pumpAndSettle(); + expect(find.byType(AppBottomSheet), findsOneWidget); + expect(find.byType(AppSheetOption), findsNWidgets(6)); + final context = tester.element(find.byType(AppBottomSheet)); + expect(context.isMornye, mornye); + final selected = tester + .widgetList(find.byType(AppSheetOption)) + .singleWhere((option) => option.trailing != null); + expect((selected.title as Text).data, 'Auto'); + expect( + (selected.trailing as Icon).color, + Theme.of(context).colorScheme.primary, + ); + final custom = find.descendant( + of: find.byType(AppBottomSheet), + matching: find.text('Custom'), + ); + await tester.ensureVisible(custom); + await tester.pumpAndSettle(); + await tester.tap(custom); + await tester.pumpAndSettle(); + expect( + container.read(settingsProvider).autoMixEffect, + AutoMixEffect.custom, + ); + expect(find.byType(BottomSheet), findsNothing); + expect(find.text('Custom'), findsOneWidget); + expect(find.byType(Slider), findsNWidgets(2)); + expect(tester.takeException(), isNull); + }, + ); + } }