import 'package:flutter/material.dart'; import '../../../../core/auth/session_store.dart'; import '../../../divination/presentation/screens/divination_screen.dart'; import '../../../settings/data/models/profile_settings.dart'; import '../../../settings/presentation/screens/settings_screen.dart'; import '../../../../l10n/app_localizations.dart'; import '../../../../shared/theme/app_color_palette.dart'; import '../../../../shared/theme/design_tokens.dart'; import '../../../../shared/widgets/bottom_nav_bar.dart'; import '../../../../shared/widgets/toast/toast.dart'; import '../../../../shared/widgets/toast/toast_type.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({ super.key, required this.account, required this.sessionStore, required this.currentLocale, required this.profileSettings, required this.coinBalance, required this.onLocaleChanged, required this.onLogout, }); final String account; final SessionStore sessionStore; final Locale currentLocale; final ProfileSettingsV1 profileSettings; final int coinBalance; final Future Function(String languageTag) onLocaleChanged; final Future Function() onLogout; @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { bool _showNotificationDot = true; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _tryShowWelcomeDialog(); }); } Future _tryShowWelcomeDialog() async { final hasRead = await widget.sessionStore.hasReadWelcome(); if (hasRead || !mounted) { return; } await showDialog( context: context, barrierDismissible: false, builder: (context) { return _WelcomeDialog( onDone: () async { await widget.sessionStore.setWelcomeRead(true); }, ); }, ); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final colors = Theme.of(context).colorScheme; final palette = Theme.of(context).extension()!; final historyItems = [ _HistoryItemData( question: l10n.historyQuestion1, category: _HistoryCategory.career, guaName: l10n.guaName1, sign: _HistorySign.good, ), _HistoryItemData( question: l10n.historyQuestion2, category: _HistoryCategory.love, guaName: l10n.guaName2, sign: _HistorySign.normal, ), _HistoryItemData( question: l10n.historyQuestion3, category: _HistoryCategory.money, guaName: l10n.guaName3, sign: _HistorySign.best, ), ]; return Scaffold( backgroundColor: colors.surfaceContainerLow, body: SafeArea( child: SingleChildScrollView( padding: const EdgeInsets.only( top: AppSpacing.lg, bottom: AppSpacing.lg, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( l10n.helloUser( widget.account.isEmpty ? l10n.defaultUserName : widget.account, ), style: Theme.of( context, ).textTheme.titleLarge?.copyWith(color: colors.primary), ), Stack( children: [ IconButton( onPressed: () { setState(() { _showNotificationDot = false; }); _showSnack(context, l10n.featurePending); }, icon: Icon( Icons.notifications, color: colors.primary, size: 28, ), tooltip: l10n.notify, ), if (_showNotificationDot) Positioned( right: AppSpacing.sm, top: AppSpacing.sm, child: Container( width: 10, height: 10, decoration: BoxDecoration( color: palette.notificationDot, shape: BoxShape.circle, ), ), ), ], ), ], ), ), const SizedBox(height: AppSpacing.xl), Padding( padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg), child: Container( width: double.infinity, padding: const EdgeInsets.all(AppSpacing.xl), decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppRadius.lg), gradient: LinearGradient( colors: [colors.primary, palette.accentPurple], ), ), child: Column( children: [ Icon( Icons.auto_awesome, color: colors.onPrimary, size: 48, ), const SizedBox(height: AppSpacing.lg), Text( l10n.startJourney, style: Theme.of(context).textTheme.titleMedium ?.copyWith( color: colors.onPrimary, fontWeight: FontWeight.w700, ), ), const SizedBox(height: AppSpacing.sm), Text( l10n.journeySubtitle, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colors.onPrimary, ), ), const SizedBox(height: AppSpacing.lg), FilledButton( style: FilledButton.styleFrom( backgroundColor: colors.surface, foregroundColor: colors.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.sm), ), ), onPressed: _onStartDivination, child: Text(l10n.startNow), ), ], ), ), ), const SizedBox(height: AppSpacing.xl), Padding( padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( l10n.historyTitle, style: Theme.of(context).textTheme.titleMedium, ), TextButton( onPressed: () => _showSnack(context, l10n.featurePending), child: Text(l10n.more), ), ], ), ), const SizedBox(height: AppSpacing.md), if (historyItems.isEmpty) SizedBox( width: double.infinity, height: 200, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( l10n.noRecords, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: AppSpacing.sm), Text(l10n.noRecordsSubtitle), ], ), ) else Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: historyItems.map((item) { return Padding( padding: const EdgeInsets.only( left: AppSpacing.md, right: AppSpacing.md, bottom: AppSpacing.md, ), child: _HistoryCard(item: item), ); }).toList(), ), ], ), ), ), bottomNavigationBar: BottomNavBar( currentTab: MainTab.home, onTabChange: _onTabChange, onLogoTap: _onStartDivination, ), ); } void _onTabChange(MainTab tab) { if (tab == MainTab.profile) { Navigator.of(context).push( MaterialPageRoute( builder: (_) => SettingsScreen( account: widget.account, settings: widget.profileSettings, coinBalance: widget.coinBalance, onInterfaceLanguageChanged: widget.onLocaleChanged, onLogout: widget.onLogout, ), ), ); } } void _onStartDivination() { Navigator.of( context, ).push(MaterialPageRoute(builder: (_) => const DivinationScreen())); } void _showSnack(BuildContext context, String message) { Toast.show(context, message, type: ToastType.info); } } class _HistoryCard extends StatelessWidget { const _HistoryCard({required this.item}); final _HistoryItemData item; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final colors = Theme.of(context).colorScheme; final palette = Theme.of(context).extension()!; final categoryLabel = switch (item.category) { _HistoryCategory.career => l10n.categoryCareer, _HistoryCategory.love => l10n.categoryLove, _HistoryCategory.money => l10n.categoryMoney, }; final categoryStyle = switch (item.category) { _HistoryCategory.career => ( palette.categoryCareerBg, palette.categoryCareerText, ), _HistoryCategory.love => ( palette.categoryLoveBg, palette.categoryLoveText, ), _HistoryCategory.money => ( palette.categoryMoneyBg, palette.categoryMoneyText, ), }; final signLabel = switch (item.sign) { _HistorySign.best => l10n.signBest, _HistorySign.good => l10n.signGood, _HistorySign.normal => l10n.signNormal, }; final signStyle = switch (item.sign) { _HistorySign.best => (palette.historyGoldBg, palette.historyGoldText), _HistorySign.good => (colors.surfaceContainerHighest, colors.primary), _HistorySign.normal => (palette.historyGrayBg, palette.historyGrayText), }; return Card( margin: EdgeInsets.zero, color: colors.surface, elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.md), ), child: Padding( padding: const EdgeInsets.all(AppSpacing.lg), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( item.question, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: AppSpacing.sm), Wrap( spacing: AppSpacing.sm, runSpacing: AppSpacing.sm, children: [ _Tag( label: categoryLabel, background: categoryStyle.$1, foreground: categoryStyle.$2, ), _Tag( label: item.guaName, background: palette.historyBlueBg, foreground: palette.historyBlueText, ), _Tag( label: signLabel, background: signStyle.$1, foreground: signStyle.$2, ), ], ), ], ), ), ); } } class _Tag extends StatelessWidget { const _Tag({ required this.label, required this.background, required this.foreground, }); final String label; final Color background; final Color foreground; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric( horizontal: AppSpacing.sm, vertical: AppSpacing.xs, ), decoration: BoxDecoration( color: background, borderRadius: BorderRadius.circular(AppRadius.sm), ), child: Text( label, style: Theme.of( context, ).textTheme.bodySmall?.copyWith(color: foreground), ), ); } } class _WelcomeDialog extends StatefulWidget { const _WelcomeDialog({required this.onDone}); final Future Function() onDone; @override State<_WelcomeDialog> createState() => _WelcomeDialogState(); } class _WelcomeDialogState extends State<_WelcomeDialog> { final ScrollController _scrollController = ScrollController(); bool _hasScrolledToBottom = false; @override void initState() { super.initState(); _scrollController.addListener(_handleScroll); WidgetsBinding.instance.addPostFrameCallback((_) { _syncScrollState(); }); } @override void dispose() { _scrollController.removeListener(_handleScroll); _scrollController.dispose(); super.dispose(); } void _handleScroll() { _syncScrollState(); } void _syncScrollState() { if (!_scrollController.hasClients) { return; } final max = _scrollController.position.maxScrollExtent; final current = _scrollController.offset; final canReadAll = max <= AppSpacing.xs || current >= max - AppSpacing.md; if (_hasScrolledToBottom == canReadAll) { return; } setState(() { _hasScrolledToBottom = canReadAll; }); } @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final colors = Theme.of(context).colorScheme; final palette = Theme.of(context).extension()!; return Dialog( insetPadding: const EdgeInsets.symmetric( horizontal: AppSpacing.lg, vertical: AppSpacing.xl, ), child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 620), child: Padding( padding: const EdgeInsets.all(AppSpacing.xl), child: Column( children: [ Text( l10n.welcomeDialogTitle, textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge?.copyWith( color: colors.primary, fontWeight: FontWeight.w700, ), ), const SizedBox(height: AppSpacing.lg), Expanded( child: SingleChildScrollView( controller: _scrollController, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( l10n.welcomeParagraph1, style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: AppSpacing.md), Text( l10n.welcomeParagraph2, style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: AppSpacing.md), Text( l10n.welcomeParagraph3, style: Theme.of(context).textTheme.bodyMedium, ), const SizedBox(height: AppSpacing.lg), Text( l10n.warningTitle, style: Theme.of(context).textTheme.titleMedium ?.copyWith(color: palette.warning), ), const SizedBox(height: AppSpacing.xs), Text( l10n.warningBody, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: palette.warning, ), ), ], ), ), ), const SizedBox(height: AppSpacing.md), if (!_hasScrolledToBottom) Padding( padding: const EdgeInsets.only(bottom: AppSpacing.sm), child: Text( l10n.scrollHint, style: Theme.of(context).textTheme.bodySmall, ), ), SizedBox( width: double.infinity, child: FilledButton( onPressed: _hasScrolledToBottom ? () async { await widget.onDone(); if (!context.mounted) { return; } Navigator.of(context).pop(); } : null, style: FilledButton.styleFrom( backgroundColor: _hasScrolledToBottom ? colors.primary : colors.outline, foregroundColor: colors.onPrimary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.sm), ), ), child: Padding( padding: const EdgeInsets.symmetric( vertical: AppSpacing.sm, ), child: Text( _hasScrolledToBottom ? l10n.understood : l10n.readAllFirst, ), ), ), ), ], ), ), ), ); } } enum _HistoryCategory { career, love, money } enum _HistorySign { best, good, normal } class _HistoryItemData { const _HistoryItemData({ required this.question, required this.category, required this.guaName, required this.sign, }); final String question; final _HistoryCategory category; final String guaName; final _HistorySign sign; }