// Author: fruit.com.cn import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../core/data/repositories/watch_history_repository.dart'; import '../../core/data/services/auth_controller.dart'; import '../../core/data/services/locale_controller.dart'; import '../../core/data/services/wallet_controller.dart'; import '../../core/theme/app_colors.dart'; import '../../core/theme/app_dimens.dart'; import '../../core/theme/app_theme.dart'; import '../../core/widgets/app_bottom_nav.dart'; import '../../core/widgets/auth_gate.dart'; import '../../core/widgets/plain_page_scaffold.dart'; import '../../l10n/app_localizations.dart'; import '../downloads/downloads_page.dart'; import '../favorites/favorites_page.dart'; import '../notices/notices_page.dart'; import '../settings/settings_page.dart'; import '../store/recharge_page.dart'; import '../store/vip_page.dart'; import '../wallet/wallet_page.dart'; import '../watch_records/watch_records_page.dart'; import 'mine_binding.dart'; import 'mine_logic.dart'; import 'mine_types.dart'; import 'widgets/mine_header.dart'; import 'widgets/mine_menu.dart'; /// K28-B ๆˆ‘็š„้กตใ€‚ class MyPage extends StatefulWidget { const MyPage({ super.key, this.active = true, this.callbacks = const MyPageCallbacks(), }); final bool active; final MyPageCallbacks callbacks; @override State createState() => _MyPageState(); } class _MyPageState extends State { late final MineLogic logic; bool get embedded => widget.callbacks.embedded; @override void initState() { super.initState(); logic = MineBinding().create( history: context.read(), ); } @override void didUpdateWidget(covariant MyPage oldWidget) { super.didUpdateWidget(oldWidget); logic.syncPage(active: widget.active, wasActive: oldWidget.active); } @override void dispose() { logic.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return ListenableBuilder( listenable: logic, builder: (context, _) => _page(context), ); } Widget _page(BuildContext context) { final l10n = AppLocalizations.of(context)!; final content = SafeArea( bottom: !embedded, child: Column( children: [ _topBar(l10n), Expanded(child: _body(context, l10n)), if (!embedded) _bottomNav(l10n), ], ), ); return Theme( data: AppTheme.light(), child: embedded ? content : Scaffold(backgroundColor: AppColors.ink, body: content), ); } Widget _topBar(AppLocalizations l10n) { return SizedBox( height: AppDimens.contentHeaderHeight, child: Padding( padding: const EdgeInsets.symmetric( horizontal: AppDimens.pageHorizontal, ), child: Align( alignment: Alignment.centerLeft, child: Text( l10n.myTab, style: const TextStyle( fontFamily: 'Outfit', color: AppColors.onSurface, fontSize: AppDimens.pageTitle, fontWeight: FontWeight.w800, ), ), ), ), ); } Widget _body(BuildContext context, AppLocalizations l10n) { final auth = context.watch(); final wallet = context.watch(); final locale = context.watch(); return ListView( key: const Key('mine-page-scroll'), padding: const EdgeInsets.fromLTRB( AppDimens.pageHorizontal, 0, AppDimens.pageHorizontal, AppDimens.spaceXl, ), children: [ MineHeader(onTap: () => _openDestination(MineDestination.settings)), const SizedBox(height: AppDimens.spaceMd), MineShortcutGrid(onOpen: _openDestination), const SizedBox(height: AppDimens.spaceMd), MineWalletCard( balance: _walletValue(l10n, auth, wallet), onOpen: () => _openDestination(MineDestination.wallet), onRecharge: _openRecharge, ), const SizedBox(height: AppDimens.spaceMd), MineVipCard(onOpen: () => _openDestination(MineDestination.vip)), const SizedBox(height: AppDimens.spaceMd), MineMenuList( onOpen: _openDestination, onLanguage: _openLanguage, languageLabel: _languageLabel(l10n, locale.locale), ), ], ); } String _walletValue( AppLocalizations l10n, AuthController auth, WalletController wallet, ) { if (!auth.isSignedIn) return l10n.logInHint; if (wallet.snapshot == null) return l10n.coins; return l10n.walletBalanceShort(wallet.balance); } void _openDestination(MineDestination destination) { switch (destination) { case MineDestination.watchlist: _push(const FavoritesPage()); case MineDestination.history: _push(const WatchRecordsPage()); case MineDestination.downloads: _push(const DownloadsPage()); case MineDestination.wallet: _openProtected(const WalletPage()); case MineDestination.vip: _openVip(); case MineDestination.works: _pushPlaceholder(MinePlaceholderKind.works); case MineDestination.creatorCenter: _pushPlaceholder(MinePlaceholderKind.creator); case MineDestination.notices: _push(const NoticesPage()); case MineDestination.settings: _push(const SettingsPage()); case MineDestination.help: _pushPlaceholder(MinePlaceholderKind.help); } } void _push(Widget page) { Navigator.of(context).push(MaterialPageRoute(builder: (_) => page)); } void _pushPlaceholder(MinePlaceholderKind kind) { _push(_MinePlaceholderPage(kind: kind)); } Future _openProtected(Widget page) async { if (!context.read().isSignedIn) { await ensureSignedIn(context); return; } if (mounted) _push(page); } void _openVip() { final wallet = context.read(); _openProtected(VipPage(onPurchased: () => unawaited(wallet.load()))); } String _languageLabel(AppLocalizations l10n, Locale? locale) { return switch (locale?.languageCode) { 'zh' => l10n.simplifiedChinese, 'en' => l10n.english, 'id' => l10n.bahasaIndonesia, _ => l10n.language, }; } Future _openLanguage() async { final selected = await showModalBottomSheet( context: context, backgroundColor: Colors.transparent, showDragHandle: false, builder: (_) => _LanguageSheet(selected: context.read().locale), ); if (selected == null || !mounted) return; await context.read().select(selected); } void _openRecharge() { final wallet = context.read(); _openProtected(RechargePage(onPurchased: () => unawaited(wallet.load()))); } Widget _bottomNav(AppLocalizations l10n) { return AppBottomNav( items: [ AppBottomNavItem( tabKey: const Key('mine-nav-home'), icon: Icons.home_outlined, selectedIcon: Icons.home_rounded, label: l10n.homeTab, onTap: () => _act(MyPageAction.home), ), AppBottomNavItem( tabKey: const Key('mine-nav-discover'), icon: Icons.explore_outlined, selectedIcon: Icons.explore_rounded, label: l10n.theaterTab, onTap: () => _act(MyPageAction.discover), ), AppBottomNavItem( tabKey: const Key('mine-nav-create'), icon: Icons.add_box_outlined, selectedIcon: Icons.add_box_rounded, label: l10n.createTab, onTap: () => _act(MyPageAction.create), ), AppBottomNavItem( tabKey: const Key('mine-nav-current'), icon: Icons.person_outline_rounded, selectedIcon: Icons.person_rounded, label: l10n.myTab, selected: true, onTap: () {}, ), ], ); } void _act(MyPageAction action) { logic.act( context, action, embedded: embedded, onAction: widget.callbacks.onAction, ); } } class _LanguageSheet extends StatelessWidget { const _LanguageSheet({required this.selected}); final Locale? selected; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final options = [ ( const Locale('zh'), const Key('mine-language-zh'), l10n.simplifiedChinese, ), (const Locale('en'), const Key('mine-language-en'), l10n.english), (const Locale('id'), const Key('mine-language-id'), l10n.bahasaIndonesia), ]; return Material( key: const Key('mine-language-sheet'), color: AppColors.card, borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), clipBehavior: Clip.antiAlias, child: SafeArea( top: false, child: Padding( padding: const EdgeInsets.fromLTRB(20, 14, 20, 20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( l10n.language, style: const TextStyle( color: AppColors.onSurface, fontSize: 20, fontWeight: FontWeight.w800, ), ), ), IconButton( tooltip: l10n.close, onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close_rounded), ), ], ), for (final option in options) ListTile( key: option.$2, contentPadding: const EdgeInsets.symmetric(horizontal: 4), title: Text(option.$3), trailing: selected?.languageCode == option.$1.languageCode ? const Icon(Icons.check_rounded, color: AppColors.blue) : null, onTap: () => Navigator.pop(context, option.$1), ), ], ), ), ), ); } } enum MinePlaceholderKind { works, creator, help } class _MinePlaceholderPage extends StatelessWidget { const _MinePlaceholderPage({required this.kind}); final MinePlaceholderKind kind; @override Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final title = switch (kind) { MinePlaceholderKind.works => l10n.myWorks, MinePlaceholderKind.creator => l10n.creatorCenter, MinePlaceholderKind.help => l10n.helpFeedback, }; return PlainPageScaffold( pageKey: const Key('mine-placeholder-page'), backKey: const Key('mine-placeholder-back'), title: title, child: Center( child: Text( l10n.comingSoon, style: const TextStyle( color: AppColors.muted, fontSize: AppDimens.body, ), ), ), ); } }