import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../settings/settings_store.dart'; import '../theme/tokens.dart'; import '../widgets/hairline_panel.dart'; /// Audio-quality settings: independent streaming and download knobs, both /// driven by Subsonic's `stream` transcode params (see `settings/settings_store.dart`). class SettingsScreen extends ConsumerWidget { const SettingsScreen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final settings = ref.watch(settingsProvider); final controller = ref.read(settingsProvider.notifier); return Scaffold( appBar: AppBar( title: const Text('Settings', style: TextStyle(fontWeight: FontWeight.w700)), ), body: SafeArea( child: ListView( padding: const EdgeInsets.all(TimbreSpacing.lg), children: [ HairlinePanel( title: 'Streaming', active: true, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const _Caption( 'Quality used when playing tracks that are not downloaded.'), const SizedBox(height: TimbreSpacing.md), _ChoiceChips( label: 'Max bitrate', values: AppSettings.bitrateChoices, selected: settings.streamMaxBitRate, labelFor: AppSettings.bitrateLabel, onSelect: controller.setStreamMaxBitRate, ), ], ), ), const SizedBox(height: TimbreSpacing.xl), HairlinePanel( title: 'Downloads', active: true, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const _Caption( 'Quality used when saving tracks for offline playback. ' '"Original" keeps the source file (best quality, largest).'), const SizedBox(height: TimbreSpacing.md), _ChoiceChips( label: 'Max bitrate', values: AppSettings.bitrateChoices, selected: settings.downloadMaxBitRate, labelFor: AppSettings.bitrateLabel, onSelect: controller.setDownloadMaxBitRate, ), const SizedBox(height: TimbreSpacing.lg), _ChoiceChips( label: 'Format', values: AppSettings.formatChoices, selected: settings.downloadFormat, labelFor: AppSettings.formatLabel, onSelect: controller.setDownloadFormat, ), ], ), ), ], ), ), ); } } class _Caption extends StatelessWidget { const _Caption(this.text); final String text; @override Widget build(BuildContext context) => Text( text, style: const TextStyle(color: TimbreColors.dimmed, fontSize: 12), ); } /// A labelled row of selectable value chips — the app's underline-accent /// language applied to compact bordered chips. class _ChoiceChips extends StatelessWidget { const _ChoiceChips({ required this.label, required this.values, required this.selected, required this.labelFor, required this.onSelect, }); final String label; final List values; final T selected; final String Function(T) labelFor; final ValueChanged onSelect; @override Widget build(BuildContext context) { final accent = Theme.of(context).colorScheme.primary; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle(color: TimbreColors.foreground)), const SizedBox(height: TimbreSpacing.sm), Wrap( spacing: TimbreSpacing.sm, runSpacing: TimbreSpacing.sm, children: [ for (final v in values) _Chip( text: labelFor(v), active: v == selected, accent: accent, onTap: () => onSelect(v), ), ], ), ], ); } } class _Chip extends StatelessWidget { const _Chip({ required this.text, required this.active, required this.accent, required this.onTap, }); final String text; final bool active; final Color accent; final VoidCallback onTap; @override Widget build(BuildContext context) { return InkWell( onTap: onTap, child: Container( constraints: const BoxConstraints(minHeight: TimbreSpacing.minTouchTarget), padding: const EdgeInsets.symmetric( horizontal: TimbreSpacing.lg, vertical: TimbreSpacing.md, ), decoration: BoxDecoration( border: Border.all( color: active ? accent : TimbreColors.border, ), color: active ? accent.withValues(alpha: 0.12) : TimbreColors.surface, ), child: Text( text, style: TextStyle( color: active ? accent : TimbreColors.foreground, fontWeight: active ? FontWeight.w700 : FontWeight.w400, ), ), ), ); } }