feat(settings): soundEnabled provider gates SFX and haptics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@ import '../services/consent_service.dart';
|
||||
import '../services/iap_service.dart';
|
||||
import 'ads_notifier.dart';
|
||||
import 'endless_best_notifier.dart';
|
||||
import 'sound_notifier.dart';
|
||||
import 'game_session_notifier.dart';
|
||||
import 'progress_notifier.dart';
|
||||
import 'season_flow_notifier.dart';
|
||||
@@ -22,8 +23,12 @@ final gameSessionProvider =
|
||||
GameSessionNotifier.new,
|
||||
);
|
||||
|
||||
final soundEnabledProvider =
|
||||
NotifierProvider<SoundEnabledNotifier, bool>(SoundEnabledNotifier.new);
|
||||
|
||||
final audioServiceProvider = Provider<AudioService>((ref) {
|
||||
final service = AudioService();
|
||||
final service = AudioService(enabled: ref.read(soundEnabledProvider));
|
||||
ref.listen<bool>(soundEnabledProvider, (_, next) => service.enabled = next);
|
||||
ref.onDispose(service.dispose);
|
||||
return service;
|
||||
});
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'providers.dart';
|
||||
|
||||
/// SFX + gameplay haptics on/off, seeded from the save repository.
|
||||
class SoundEnabledNotifier extends Notifier<bool> {
|
||||
@override
|
||||
bool build() => ref.read(saveRepositoryProvider).soundEnabled;
|
||||
|
||||
Future<void> toggle() => set(!state);
|
||||
|
||||
Future<void> set(bool value) async {
|
||||
if (state == value) return;
|
||||
await ref.read(saveRepositoryProvider).setSoundEnabled(value);
|
||||
state = value;
|
||||
}
|
||||
}
|
||||
@@ -120,16 +120,17 @@ class _GameScreenState extends ConsumerState<GameScreen>
|
||||
final audio = ref.read(audioServiceProvider);
|
||||
if (prev?.fxTick != next.fxTick && next.lastPlacement != null) {
|
||||
final placement = next.lastPlacement!;
|
||||
final hapticsOn = ref.read(soundEnabledProvider);
|
||||
if (placement.linesCleared > 0) {
|
||||
audio.play(placement.comboStreak >= 2 ? Sfx.combo : Sfx.clear);
|
||||
HapticFeedback.mediumImpact();
|
||||
if (hapticsOn) HapticFeedback.mediumImpact();
|
||||
if (placement.comboStreak >= 4) {
|
||||
HapticFeedback.heavyImpact();
|
||||
if (hapticsOn) HapticFeedback.heavyImpact();
|
||||
_shake.forward(from: 0);
|
||||
}
|
||||
} else {
|
||||
audio.play(Sfx.place);
|
||||
HapticFeedback.lightImpact();
|
||||
if (hapticsOn) HapticFeedback.lightImpact();
|
||||
}
|
||||
ref.read(tutorialProvider.notifier).onPlaced();
|
||||
if (placement.linesCleared > 0) {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:block_seasons/data/save_repository.dart';
|
||||
import 'package:block_seasons/state/providers.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
test('reads persisted sound flag and toggles + persists', () async {
|
||||
SharedPreferences.setMockInitialValues({});
|
||||
final repo = await SaveRepository.open();
|
||||
final c = ProviderContainer(
|
||||
overrides: [saveRepositoryProvider.overrideWithValue(repo)],
|
||||
);
|
||||
addTearDown(c.dispose);
|
||||
|
||||
expect(c.read(soundEnabledProvider), isTrue);
|
||||
await c.read(soundEnabledProvider.notifier).toggle();
|
||||
expect(c.read(soundEnabledProvider), isFalse);
|
||||
expect(repo.soundEnabled, isFalse);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user