import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:just_audio/just_audio.dart'; import '../../app/theme.dart'; import '../../audio/player_providers.dart'; import '../../audio/sound_settings.dart'; void showSoundSettings(BuildContext context) { showModalBottomSheet( context: context, isScrollControlled: true, showDragHandle: true, backgroundColor: NkColors.surface, builder: (_) => const _SoundSheet(), ); } class _SoundSheet extends ConsumerWidget { const _SoundSheet(); @override Widget build(BuildContext context, WidgetRef ref) { final s = ref.watch(soundSettingsProvider); final settings = ref.read(soundSettingsProvider.notifier); final text = Theme.of(context).textTheme; if (!soundEffectsSupported) { return const Padding( padding: EdgeInsets.fromLTRB(24, 0, 24, 40), child: Text( 'On iPhone, sound is adjusted in Settings > Apps > Music > EQ, ' 'which applies to all apps.', style: TextStyle(color: NkColors.muted), ), ); } return SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded(child: Text('Sound', style: text.titleLarge)), TextButton(onPressed: settings.reset, child: const Text('Reset')), ], ), const SizedBox(height: 8), SwitchListTile( contentPadding: EdgeInsets.zero, title: const Text('Equalizer', style: TextStyle(fontWeight: FontWeight.w700)), subtitle: const Text('Shape the sound to your taste', style: TextStyle(color: NkColors.muted)), value: s.eqEnabled, onChanged: settings.setEnabled, ), Wrap( spacing: 8, runSpacing: 8, children: [ for (final p in EqPreset.values.where((p) => p != EqPreset.custom || s.preset == EqPreset.custom)) ChoiceChip( label: Text(p.label), selected: s.eqEnabled && s.preset == p, onSelected: (_) => settings.setPreset(p), ), ], ), const SizedBox(height: 16), AnimatedOpacity( duration: const Duration(milliseconds: 200), opacity: s.eqEnabled ? 1 : 0.4, child: const _Bands(), ), const Divider(height: 32, color: NkColors.outline), Row( children: [ const Icon(Icons.volume_up_rounded, color: NkColors.sun), const SizedBox(width: 10), const Expanded(child: Text('Volume boost', style: TextStyle(fontWeight: FontWeight.w700))), Text(s.boostDb == 0 ? 'Off' : '+${s.boostDb.round()} dB', style: const TextStyle(color: NkColors.muted)), ], ), Slider( value: s.boostDb, max: 10, divisions: 10, onChanged: settings.setBoost, ), const Text( 'Makes quiet old recordings louder. Keep it low with headphones.', style: TextStyle(color: NkColors.muted, fontSize: 13), ), ], ), ), ); } } /// One vertical slider per equalizer band. The bands are only known once a song has loaded. class _Bands extends ConsumerWidget { const _Bands(); @override Widget build(BuildContext context, WidgetRef ref) { ref.watch(soundSettingsProvider); // rebuild when a preset changes the gains final eq = ref.read(audioHandlerProvider).equalizer; return FutureBuilder( future: eq.parameters, builder: (context, snap) { final p = snap.data; if (p == null) { return const SizedBox( height: 60, child: Center( child: Text('Play a song to adjust the bands', style: TextStyle(color: NkColors.muted)), ), ); } return SizedBox( height: 210, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ for (final band in p.bands) StreamBuilder( stream: band.gainStream, builder: (context, g) => Column( children: [ Text('${(g.data ?? 0) >= 0 ? '+' : ''}${(g.data ?? 0).toStringAsFixed(0)}', style: const TextStyle(color: NkColors.muted, fontSize: 12)), Expanded( child: RotatedBox( quarterTurns: 3, child: Slider( min: p.minDecibels, max: p.maxDecibels, value: (g.data ?? 0).clamp(p.minDecibels, p.maxDecibels), onChanged: (v) => ref.read(soundSettingsProvider.notifier).setBandGain(band.index, v), ), ), ), Text(_hz(band.centerFrequency), style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600)), ], ), ), ], ), ); }, ); } static String _hz(double hz) => hz >= 1000 ? '${(hz / 1000).toStringAsFixed(hz >= 10000 ? 0 : 1)}k' : '${hz.round()}'; }