import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import '../core/ambience.dart'; import '../data/app_preferences_store.dart'; import '../data/focus_seed_store.dart'; import '../features/onboarding/welcome_screen.dart'; import '../features/shell/focus_seed_home.dart'; import '../l10n/generated/app_localizations.dart'; import '../models/appearance_preference.dart'; import '../models/language_preference.dart'; import '../widgets/loading_view.dart'; import 'app_theme.dart'; @visibleForTesting Locale focusSeedDefaultLocale({bool isRelease = kReleaseMode}) { return isRelease ? const Locale('en') : const Locale('zh'); } class FocusSeedApp extends StatefulWidget { const FocusSeedApp({ this.locale, this.focusSeedStore, this.showWelcomeOnFirstRun = true, super.key, }); final Locale? locale; final FocusSeedStore? focusSeedStore; final bool showWelcomeOnFirstRun; @override State createState() => _FocusSeedAppState(); } class _FocusSeedAppState extends State with WidgetsBindingObserver { final _preferencesStore = AppPreferencesStore(); AppearancePreference _appearance = AppearancePreference.system; LanguagePreference _language = LanguagePreference.defaultLocale; bool _onboardingComplete = true; bool _landingSetupComplete = true; bool _showLandingSetup = false; bool _preferencesLoaded = false; DayPhase _dayPhase = dayPhaseFor(DateTime.now()); Timer? _phaseTimer; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); _loadPreferences(); _schedulePhaseRefresh(); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { _refreshDayPhase(); } } Future _loadPreferences() async { final (appearance, language, firstRunProgress) = await ( _preferencesStore.loadAppearance(), _preferencesStore.loadLanguage(), _preferencesStore.loadFirstRunProgress(), ).wait; if (!mounted) { return; } setState(() { _appearance = appearance; _language = language; _onboardingComplete = firstRunProgress.onboardingComplete; _landingSetupComplete = firstRunProgress.landingSetupComplete; _showLandingSetup = widget.showWelcomeOnFirstRun && firstRunProgress.onboardingComplete && !firstRunProgress.landingSetupComplete; _preferencesLoaded = true; }); } Future _setAppearance(AppearancePreference appearance) async { setState(() => _appearance = appearance); await _preferencesStore.saveAppearance(appearance); } Future _setLanguage(LanguagePreference language) async { setState(() => _language = language); await _preferencesStore.saveLanguage(language); } void _refreshDayPhase() { final phase = dayPhaseFor(DateTime.now()); if (phase != _dayPhase && mounted) { setState(() => _dayPhase = phase); } _schedulePhaseRefresh(); } void _schedulePhaseRefresh() { _phaseTimer?.cancel(); final now = DateTime.now(); final delay = nextDayPhaseBoundary(now).difference(now); _phaseTimer = Timer(delay, _refreshDayPhase); } ThemeMode get _themeMode => switch (_appearance) { AppearancePreference.system => ThemeMode.system, AppearancePreference.light => ThemeMode.light, AppearancePreference.dark => ThemeMode.dark, }; Locale get _resolvedLocale { return widget.locale ?? _language.locale ?? focusSeedDefaultLocale(); } Future _completeOnboarding() async { setState(() { _onboardingComplete = true; _showLandingSetup = true; }); await _preferencesStore.saveOnboardingComplete(); } Future _completeLandingSetup() async { setState(() { _landingSetupComplete = true; _showLandingSetup = false; }); await _preferencesStore.saveLandingSetupComplete(); } @override Widget build(BuildContext context) { return MaterialApp( onGenerateTitle: (context) => AppLocalizations.of(context).appTitle, debugShowCheckedModeBanner: false, theme: AppTheme.light(_dayPhase), darkTheme: AppTheme.dark(_dayPhase), themeMode: _themeMode, themeAnimationDuration: const Duration(milliseconds: 240), locale: _resolvedLocale, localizationsDelegates: const [ AppLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, ], supportedLocales: AppLocalizations.supportedLocales, home: !_preferencesLoaded ? const Scaffold(body: SafeArea(child: LoadingView())) : widget.showWelcomeOnFirstRun && !_onboardingComplete ? WelcomeScreen( onStart: _completeOnboarding, onSkip: _completeOnboarding, ) : FocusSeedHome( focusSeedStore: widget.focusSeedStore, appearance: _appearance, onAppearanceChanged: _setAppearance, language: _language, showLanguageTools: !kReleaseMode && widget.locale == null, onLanguageChanged: _setLanguage, showLandingSetupInitially: widget.showWelcomeOnFirstRun && (!_landingSetupComplete || _showLandingSetup), onLandingSetupComplete: () { unawaited(_completeLandingSetup()); }, ), ); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); _phaseTimer?.cancel(); super.dispose(); } }