8947221b27
MusicService (looping audioplayers player, independent of SFX) driven by the active season theme's new 'bgm' key; switches track on season change, pauses on app background, all failures swallowed. Separate Music on/off toggle in Settings (persisted, independent of SFX). Season packs carry bgm keys (menu/season_001/ season_002), manifest regenerated. Assets slot assets/audio/bgm/ ready — drop in menu.mp3/season_001.mp3/season_002.mp3 (CC0) and it plays; silent until then. 180 tests green, analyze clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 lines
2.5 KiB
Dart
75 lines
2.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import 'l10n/gen/app_localizations.dart';
|
|
import 'state/providers.dart';
|
|
import 'ui/screens/splash_screen.dart';
|
|
|
|
class BlockSeasonsApp extends ConsumerStatefulWidget {
|
|
const BlockSeasonsApp({super.key});
|
|
|
|
@override
|
|
ConsumerState<BlockSeasonsApp> createState() => _BlockSeasonsAppState();
|
|
}
|
|
|
|
class _BlockSeasonsAppState extends ConsumerState<BlockSeasonsApp>
|
|
with WidgetsBindingObserver {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addObserver(this);
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
ref.read(consentServiceProvider).ensureConsentAndInitialize();
|
|
// Eagerly start the IAP service so its purchase stream is live for the
|
|
// whole session — restores and interrupted/deferred transactions are
|
|
// delivered (and completed) even if the player never opens Settings.
|
|
ref.read(iapServiceProvider);
|
|
// Start background music for the current context (menu by default).
|
|
ref.read(musicServiceProvider).playKey(ref.read(activeThemeProvider).bgm);
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
WidgetsBinding.instance.removeObserver(this);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
// Pause music while the app is not in the foreground.
|
|
ref
|
|
.read(musicServiceProvider)
|
|
.setBackgrounded(state != AppLifecycleState.resumed);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Switch the looping track whenever the active season theme changes
|
|
// (home/menu -> a season -> back). playKey is a no-op if unchanged.
|
|
ref.listen(activeThemeProvider, (_, next) {
|
|
ref.read(musicServiceProvider).playKey(next.bgm);
|
|
});
|
|
return MaterialApp(
|
|
onGenerateTitle: (context) => AppLocalizations.of(context)!.appTitle,
|
|
debugShowCheckedModeBanner: false,
|
|
localizationsDelegates: const [
|
|
AppLocalizations.delegate,
|
|
GlobalMaterialLocalizations.delegate,
|
|
GlobalWidgetsLocalizations.delegate,
|
|
GlobalCupertinoLocalizations.delegate,
|
|
],
|
|
supportedLocales: const [Locale('en'), Locale('ko')],
|
|
theme: ThemeData(
|
|
colorScheme: ColorScheme.fromSeed(
|
|
seedColor: const Color(0xFF5B7FFF),
|
|
brightness: Brightness.dark,
|
|
),
|
|
useMaterial3: true,
|
|
),
|
|
home: const SplashScreen(),
|
|
);
|
|
}
|
|
}
|