feat(settings): soundEnabled provider gates SFX and haptics

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 17:59:08 +09:00
parent 93397988a2
commit 3ca038ec65
4 changed files with 48 additions and 4 deletions
+6 -1
View File
@@ -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;
});
+17
View File
@@ -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;
}
}
+4 -3
View File
@@ -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) {
+21
View File
@@ -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);
});
}