Files
SpotiFLAC-Mobile/lib/screens/settings/download_fallback_extensions_page.dart
T
zarzet 4b9853eef7 refactor(ui): consolidate duplicated sheets, rows, and cover widgets
- single-track convert sheet now reuses BatchConvertSheet
  (confirmLabelBuilder + sourceIsLossless params) instead of a
  full inline copy
- provider priority page migrated to PrioritySettingsScaffold;
  shared showDiscardChangesDialog and ReorderablePriorityItem
  replace per-page copies
- home tab search rows unified into one _SearchResultRowItem;
  _parseTrack copies rebuilt on Track.fromBackendMap with only
  the load-bearing local overrides kept; loading/error scaffold
  extracted
- PlayerArtwork widget shared by now-playing and mini player
- LocalOrNetworkCoverImage dispatches file vs network cover in
  one place (playlist picker, queue nav, library folder)
2026-07-12 18:58:16 +07:00

199 lines
7.0 KiB
Dart

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/providers/settings_provider.dart';
import 'package:spotiflac_android/widgets/discard_changes_dialog.dart';
import 'package:spotiflac_android/widgets/settings_group.dart';
import 'package:spotiflac_android/widgets/settings_sliver_app_bar.dart';
class DownloadFallbackExtensionsPage extends ConsumerStatefulWidget {
const DownloadFallbackExtensionsPage({super.key});
@override
ConsumerState<DownloadFallbackExtensionsPage> createState() =>
_DownloadFallbackExtensionsPageState();
}
class _DownloadFallbackExtensionsPageState
extends ConsumerState<DownloadFallbackExtensionsPage> {
late List<Extension> _extensions;
late Set<String> _selectedExtensionIds;
bool _hasChanges = false;
@override
void initState() {
super.initState();
_loadExtensions();
}
void _loadExtensions() {
final extState = ref.read(extensionProvider);
final settings = ref.read(settingsProvider);
_extensions = extState.extensions
.where(
(extension) => extension.enabled && extension.hasDownloadProvider,
)
.toList();
final savedIds = settings.downloadFallbackExtensionIds;
if (savedIds == null) {
_selectedExtensionIds = _extensions
.map((extension) => extension.id)
.toSet();
} else {
final allowedIds = _extensions.map((extension) => extension.id).toSet();
_selectedExtensionIds = savedIds
.where((extensionId) => allowedIds.contains(extensionId))
.toSet();
}
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return PopScope(
canPop: !_hasChanges,
onPopInvokedWithResult: (didPop, result) async {
if (didPop) return;
final shouldPop = await showDiscardChangesDialog(context);
if (shouldPop && context.mounted) {
Navigator.pop(context);
}
},
child: Scaffold(
body: CustomScrollView(
slivers: [
SettingsSliverAppBar(
title: context.l10n.extensionsFallbackTitle,
leading: IconButton(
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
icon: const Icon(Icons.arrow_back),
onPressed: () async {
if (_hasChanges) {
final shouldPop = await showDiscardChangesDialog(context);
if (shouldPop && context.mounted) {
Navigator.pop(context);
}
} else {
Navigator.pop(context);
}
},
),
actions: [
if (_hasChanges)
TextButton(
onPressed: _saveChanges,
child: Text(context.l10n.dialogSave),
),
],
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(16),
child: Text(
context.l10n.providerPriorityFallbackExtensionsDescription,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
),
if (_extensions.isEmpty)
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: colorScheme.surfaceContainerHighest.withValues(
alpha: 0.4,
),
borderRadius: BorderRadius.circular(16),
),
child: Text(
context.l10n.extensionsNoDownloadProvider,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
),
),
if (_extensions.isNotEmpty)
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
sliver: SliverToBoxAdapter(
child: SettingsGroup(
margin: EdgeInsets.zero,
children: List.generate(_extensions.length, (index) {
final extension = _extensions[index];
final isSelected = _selectedExtensionIds.contains(
extension.id,
);
return SettingsSwitchItem(
icon: Icons.extension_rounded,
title: extension.displayName,
subtitle: extension.id,
value: isSelected,
showDivider: index != _extensions.length - 1,
onChanged: (value) {
setState(() {
if (value) {
_selectedExtensionIds.add(extension.id);
} else {
_selectedExtensionIds.remove(extension.id);
}
_hasChanges = true;
});
},
);
}),
),
),
),
if (_extensions.isNotEmpty)
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 0),
child: Text(
context.l10n.providerPriorityFallbackExtensionsHint,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
),
const SliverToBoxAdapter(child: SizedBox(height: 32)),
],
),
),
);
}
void _saveChanges() {
final allExtensionIds = _extensions
.map((extension) => extension.id)
.toList();
final selectedExtensionIds = allExtensionIds
.where(_selectedExtensionIds.contains)
.toList();
final fallbackExtensionIds =
selectedExtensionIds.length == allExtensionIds.length
? null
: selectedExtensionIds;
ref
.read(settingsProvider.notifier)
.setDownloadFallbackExtensionIds(fallbackExtensionIds);
setState(() {
_hasChanges = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.l10n.snackbarProviderPrioritySaved)),
);
}
}