feat(autoplay): enable personalization by default and confirm learning resets

This commit is contained in:
zarzet committed 2026-10-17 16:18:26 +07:00
1 parent befcd2adec
commit 21fd51d62c
5 files changed
+54 -23

No files matched your search

+1 -1
View File
@@ -198,7 +198,7 @@ class AppSettings {
this.usbAllowFixedVolume = false,
this.dapExclusive = false,
this.autoplay = false,
this.autoplayPersonalization = false,
this.autoplayPersonalization = true,
this.isFirstLaunch = true,
this.checkForUpdates = true,
this.updateChannel = 'stable',
+1 -1
View File
@@ -59,7 +59,7 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
usbAllowFixedVolume: json['usbAllowFixedVolume'] as bool? ?? false,
dapExclusive: json['dapExclusive'] as bool? ?? false,
autoplay: json['autoplay'] as bool? ?? false,
autoplayPersonalization: json['autoplayPersonalization'] as bool? ?? false,
autoplayPersonalization: json['autoplayPersonalization'] as bool? ?? true,
isFirstLaunch: json['isFirstLaunch'] as bool? ?? true,
checkForUpdates: json['checkForUpdates'] as bool? ?? true,
updateChannel: json['updateChannel'] as String? ?? 'stable',
@@ -47,6 +47,31 @@ class _PlaybackSettingsPageState extends ConsumerState<PlaybackSettingsPage> {
false;
}
/// Learning cannot be restored once cleared, so a stray tap must not wipe it.
Future<void> _confirmResetLearning() async {
final confirmed =
await showAppDialog<bool>(
context: context,
builder: (context) => AppAlertDialog(
title: Text(context.l10n.autoplayResetLearningConfirmTitle),
content: Text(context.l10n.autoplayResetLearningConfirmMessage),
actions: [
AppDialogAction(
onPressed: () => Navigator.pop(context, false),
child: Text(context.l10n.dialogCancel),
),
AppDialogAction(
isDestructive: true,
onPressed: () => Navigator.pop(context, true),
child: Text(context.l10n.autoplayResetLearningConfirm),
),
],
),
) ??
false;
if (confirmed) await resetAutoplayLearning();
}
@override
Widget build(BuildContext context) {
final settings = ref.watch(settingsProvider);
@@ -160,7 +185,7 @@ class _PlaybackSettingsPageState extends ConsumerState<PlaybackSettingsPage> {
SettingsItem(
icon: Icons.restart_alt,
title: context.l10n.autoplayResetLearning,
onTap: resetAutoplayLearning,
onTap: _confirmResetLearning,
showDivider: false,
),
],
+12 -15
View File
@@ -112,21 +112,18 @@ void main() {
'${durations[(durations.length * .95).floor()]} us; model parameters 65536 bytes.',
);
});
test(
'cold start is neutral and personalization is an independent opt-in',
() {
final model = AutoplayPreferenceModel();
expect(model.preference(_rock, at: _at), 0.5);
expect(model.rankAdjustment(_rock, previous: _jazz, at: _at), 0);
expect(AppSettings.fromJson({}).autoplayPersonalization, isFalse);
final settings = const AppSettings().copyWith(
autoplayPersonalization: true,
);
final restored = AppSettings.fromJson(settings.toJson());
expect(restored.autoplayPersonalization, isTrue);
expect(restored.autoplay, isFalse);
},
);
test('cold start is neutral and personalization is on independently', () {
final model = AutoplayPreferenceModel();
expect(model.preference(_rock, at: _at), 0.5);
expect(model.rankAdjustment(_rock, previous: _jazz, at: _at), 0);
expect(AppSettings.fromJson({}).autoplayPersonalization, isTrue);
final settings = const AppSettings().copyWith(
autoplayPersonalization: false,
);
final restored = AppSettings.fromJson(settings.toJson());
expect(restored.autoplayPersonalization, isFalse);
expect(restored.autoplay, isFalse);
});
test('learning favors engagement, transfers genre and handles context', () {
final model = AutoplayPreferenceModel();
+14 -5
View File
@@ -50,24 +50,33 @@ void main() {
scrollable: find.byType(Scrollable).first,
);
await tester.pumpAndSettle();
expect(find.text('Reset recommendation learning'), findsNothing);
// On by default; the reset action belongs to the enabled state.
expect(container.read(settingsProvider).autoplayPersonalization, isTrue);
expect(find.text('Reset recommendation learning'), findsOneWidget);
// A stray tap only asks; cancelling keeps what was learned.
await tester.tap(find.text('Reset recommendation learning'));
await tester.pumpAndSettle();
expect(find.text('Reset recommendation learning?'), findsOneWidget);
await tester.tap(find.text('Cancel'));
await tester.pumpAndSettle();
expect(find.text('Reset recommendation learning?'), findsNothing);
await tester.tap(label);
await tester.pumpAndSettle();
expect(container.read(settingsProvider).autoplayPersonalization, isTrue);
expect(container.read(settingsProvider).autoplayPersonalization, isFalse);
expect(container.read(settingsProvider).autoplay, isFalse);
final prefs = await SharedPreferences.getInstance();
expect(
AppSettings.fromJson(
jsonDecode(prefs.getString('app_settings')!) as Map<String, dynamic>,
).autoplayPersonalization,
isTrue,
isFalse,
);
expect(find.text('Reset recommendation learning'), findsOneWidget);
expect(find.text('Reset recommendation learning'), findsNothing);
await tester.ensureVisible(label);
await tester.pumpAndSettle();
await tester.tap(label);
await tester.pumpAndSettle();
expect(container.read(settingsProvider).autoplayPersonalization, isFalse);
expect(container.read(settingsProvider).autoplayPersonalization, isTrue);
final l10n = AppLocalizations.of(tester.element(label));
expect(
SettingsSearchCatalog(l10n).playback.any(