refactor(settings): share the provider-priority reorder page

The download- and metadata-provider priority pages were 99% identical. Extract
ProviderPriorityListPage taking the provider-list load/save callbacks and
labels; both pages become thin wrappers. (The metadata page's explicit
saveLabel was the same dialogSave the scaffold already defaults to.)
This commit is contained in:
zarzet
2026-07-14 18:55:48 +07:00
parent cd6cf05227
commit ca1c95346f
3 changed files with 149 additions and 190 deletions
@@ -1,108 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/providers/extension_provider.dart';
import 'package:spotiflac_android/utils/adaptive_layout.dart';
import 'package:spotiflac_android/widgets/discard_changes_dialog.dart';
import 'package:spotiflac_android/widgets/priority_settings_scaffold.dart';
import 'package:spotiflac_android/widgets/reorderable_priority_item.dart';
import 'package:spotiflac_android/screens/settings/provider_priority_list_page.dart';
class MetadataProviderPriorityPage extends ConsumerStatefulWidget {
class MetadataProviderPriorityPage extends StatelessWidget {
const MetadataProviderPriorityPage({super.key});
@override
ConsumerState<MetadataProviderPriorityPage> createState() =>
_MetadataProviderPriorityPageState();
}
class _MetadataProviderPriorityPageState
extends ConsumerState<MetadataProviderPriorityPage> {
late List<String> _providers;
bool _hasChanges = false;
@override
void initState() {
super.initState();
_loadProviders();
}
void _loadProviders() {
final extState = ref.read(extensionProvider);
final allProviders = ref
.read(extensionProvider.notifier)
.getAllMetadataProviders();
if (extState.metadataProviderPriority.isNotEmpty) {
_providers = List.from(extState.metadataProviderPriority);
for (final provider in allProviders) {
if (!_providers.contains(provider)) {
_providers.add(provider);
}
}
_providers.removeWhere((p) => !allProviders.contains(p));
} else {
_providers = allProviders;
}
}
@override
Widget build(BuildContext context) {
return PrioritySettingsScaffold(
hasChanges: _hasChanges,
title: context.l10n.metadataProviderPriorityTitle,
description: context.l10n.metadataProviderPriorityDescription,
descriptionPadding: const EdgeInsets.all(16),
infoText: context.l10n.metadataProviderPriorityInfo,
saveLabel: context.l10n.dialogSave,
onSave: _saveChanges,
onConfirmDiscard: showDiscardChangesDialog,
slivers: [
SliverPadding(
padding: EdgeInsets.symmetric(
horizontal: 16 + wideListInset(context),
),
sliver: SliverReorderableList(
itemCount: _providers.length,
itemBuilder: (context, index) {
final provider = _providers[index];
final extension = ref
.read(extensionProvider)
.extensions
.where((ext) => ext.id == provider)
.firstOrNull;
return ReorderablePriorityItem(
key: ValueKey(provider),
index: index,
isFirst: index == 0,
icon: Icons.extension,
iconColor: Theme.of(context).colorScheme.secondary,
name: extension?.displayName ?? provider,
subtitle: context.l10n.providerExtension,
);
},
onReorderItem: (oldIndex, newIndex) {
setState(() {
final item = _providers.removeAt(oldIndex);
_providers.insert(newIndex, item);
_hasChanges = true;
});
},
),
),
],
);
}
Future<void> _saveChanges() async {
await ref
.read(extensionProvider.notifier)
.setMetadataProviderPriority(_providers);
if (!mounted) return;
setState(() {
_hasChanges = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.snackbarMetadataProviderSaved)),
return ProviderPriorityListPage(
loadAllProviders: (ref) =>
ref.read(extensionProvider.notifier).getAllMetadataProviders(),
currentPriority: (ref) =>
ref.read(extensionProvider).metadataProviderPriority,
save: (ref, priority) => ref
.read(extensionProvider.notifier)
.setMetadataProviderPriority(priority),
title: (c) => c.l10n.metadataProviderPriorityTitle,
description: (c) => c.l10n.metadataProviderPriorityDescription,
infoText: (c) => c.l10n.metadataProviderPriorityInfo,
savedSnackbar: (c) => c.l10n.snackbarMetadataProviderSaved,
);
}
}
@@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/providers/extension_provider.dart';
import 'package:spotiflac_android/utils/adaptive_layout.dart';
import 'package:spotiflac_android/widgets/discard_changes_dialog.dart';
import 'package:spotiflac_android/widgets/priority_settings_scaffold.dart';
import 'package:spotiflac_android/widgets/reorderable_priority_item.dart';
/// Reorderable priority list shared by the download- and metadata-provider
/// priority pages; the two differ only in which provider list they read/write
/// and their labels, passed in as callbacks.
class ProviderPriorityListPage extends ConsumerStatefulWidget {
final List<String> Function(WidgetRef ref) loadAllProviders;
final List<String> Function(WidgetRef ref) currentPriority;
final Future<void> Function(WidgetRef ref, List<String> priority) save;
final String Function(BuildContext context) title;
final String Function(BuildContext context) description;
final String Function(BuildContext context) infoText;
final String Function(BuildContext context) savedSnackbar;
const ProviderPriorityListPage({
super.key,
required this.loadAllProviders,
required this.currentPriority,
required this.save,
required this.title,
required this.description,
required this.infoText,
required this.savedSnackbar,
});
@override
ConsumerState<ProviderPriorityListPage> createState() =>
_ProviderPriorityListPageState();
}
class _ProviderPriorityListPageState
extends ConsumerState<ProviderPriorityListPage> {
late List<String> _providers;
bool _hasChanges = false;
@override
void initState() {
super.initState();
_loadProviders();
}
void _loadProviders() {
final allProviders = widget.loadAllProviders(ref);
final saved = widget.currentPriority(ref);
if (saved.isNotEmpty) {
_providers = List.from(saved);
for (final provider in allProviders) {
if (!_providers.contains(provider)) {
_providers.add(provider);
}
}
_providers.removeWhere((p) => !allProviders.contains(p));
} else {
_providers = allProviders;
}
}
@override
Widget build(BuildContext context) {
return PrioritySettingsScaffold(
hasChanges: _hasChanges,
title: widget.title(context),
description: widget.description(context),
descriptionPadding: const EdgeInsets.all(16),
infoText: widget.infoText(context),
onSave: _saveChanges,
onConfirmDiscard: showDiscardChangesDialog,
slivers: [
SliverPadding(
padding: EdgeInsets.symmetric(
horizontal: 16 + wideListInset(context),
),
sliver: SliverReorderableList(
itemCount: _providers.length,
itemBuilder: (context, index) {
final provider = _providers[index];
final extension = ref
.read(extensionProvider)
.extensions
.where((ext) => ext.id == provider)
.firstOrNull;
return ReorderablePriorityItem(
key: ValueKey(provider),
index: index,
isFirst: index == 0,
icon: Icons.extension,
iconColor: Theme.of(context).colorScheme.secondary,
name: extension?.displayName ?? provider,
subtitle: context.l10n.providerExtension,
);
},
onReorderItem: (oldIndex, newIndex) {
setState(() {
final item = _providers.removeAt(oldIndex);
_providers.insert(newIndex, item);
_hasChanges = true;
});
},
),
),
],
);
}
Future<void> _saveChanges() async {
await widget.save(ref, _providers);
if (!mounted) return;
setState(() {
_hasChanges = false;
});
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text(widget.savedSnackbar(context))));
}
}
@@ -1,104 +1,23 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/providers/extension_provider.dart';
import 'package:spotiflac_android/utils/adaptive_layout.dart';
import 'package:spotiflac_android/widgets/discard_changes_dialog.dart';
import 'package:spotiflac_android/widgets/priority_settings_scaffold.dart';
import 'package:spotiflac_android/widgets/reorderable_priority_item.dart';
import 'package:spotiflac_android/screens/settings/provider_priority_list_page.dart';
class ProviderPriorityPage extends ConsumerStatefulWidget {
class ProviderPriorityPage extends StatelessWidget {
const ProviderPriorityPage({super.key});
@override
ConsumerState<ProviderPriorityPage> createState() =>
_ProviderPriorityPageState();
}
class _ProviderPriorityPageState extends ConsumerState<ProviderPriorityPage> {
late List<String> _providers;
bool _hasChanges = false;
@override
void initState() {
super.initState();
_loadProviders();
}
void _loadProviders() {
final extState = ref.read(extensionProvider);
final allProviders = ref
.read(extensionProvider.notifier)
.getAllDownloadProviders();
if (extState.providerPriority.isNotEmpty) {
_providers = List.from(extState.providerPriority);
for (final provider in allProviders) {
if (!_providers.contains(provider)) {
_providers.add(provider);
}
}
_providers.removeWhere((p) => !allProviders.contains(p));
} else {
_providers = allProviders;
}
}
@override
Widget build(BuildContext context) {
return PrioritySettingsScaffold(
hasChanges: _hasChanges,
title: context.l10n.providerPriorityTitle,
description: context.l10n.providerPriorityDescription,
descriptionPadding: const EdgeInsets.all(16),
infoText: context.l10n.providerPriorityInfo,
onSave: _saveChanges,
onConfirmDiscard: showDiscardChangesDialog,
slivers: [
SliverPadding(
padding: EdgeInsets.symmetric(
horizontal: 16 + wideListInset(context),
),
sliver: SliverReorderableList(
itemCount: _providers.length,
itemBuilder: (context, index) {
final provider = _providers[index];
final extension = ref
.read(extensionProvider)
.extensions
.where((ext) => ext.id == provider)
.firstOrNull;
return ReorderablePriorityItem(
key: ValueKey(provider),
index: index,
isFirst: index == 0,
icon: Icons.extension,
iconColor: Theme.of(context).colorScheme.secondary,
name: extension?.displayName ?? provider,
subtitle: context.l10n.providerExtension,
);
},
onReorderItem: (oldIndex, newIndex) {
setState(() {
final item = _providers.removeAt(oldIndex);
_providers.insert(newIndex, item);
_hasChanges = true;
});
},
),
),
],
);
}
Future<void> _saveChanges() async {
await ref.read(extensionProvider.notifier).setProviderPriority(_providers);
if (!mounted) return;
setState(() {
_hasChanges = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.snackbarProviderPrioritySaved)),
return ProviderPriorityListPage(
loadAllProviders: (ref) =>
ref.read(extensionProvider.notifier).getAllDownloadProviders(),
currentPriority: (ref) => ref.read(extensionProvider).providerPriority,
save: (ref, priority) =>
ref.read(extensionProvider.notifier).setProviderPriority(priority),
title: (c) => c.l10n.providerPriorityTitle,
description: (c) => c.l10n.providerPriorityDescription,
infoText: (c) => c.l10n.providerPriorityInfo,
savedSnackbar: (c) => c.l10n.snackbarProviderPrioritySaved,
);
}
}