diff --git a/lib/app.dart b/lib/app.dart index 34c070f..eed6a28 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -90,7 +90,7 @@ class _MainScaffold extends StatelessWidget { body: IndexedStack( index: currentTab.index, children: [ - const HomeScreen(), + HomeScreen(isActive: currentTab == NavTab.home), GalleryScreen(isActive: currentTab == NavTab.gallery), ProfileScreen(isActive: currentTab == NavTab.profile), ], diff --git a/lib/core/user/account_refresh.dart b/lib/core/user/account_refresh.dart new file mode 100644 index 0000000..cfd3af0 --- /dev/null +++ b/lib/core/user/account_refresh.dart @@ -0,0 +1,30 @@ +import '../api/api_config.dart'; +import '../api/services/user_api.dart'; +import '../auth/auth_service.dart'; +import 'user_state.dart'; + +/// 刷新用户账户信息并更新 UserState +/// +/// [updateProfile] 为 true 时,同时更新 avatar 和 userName(用于 Profile 页) +Future refreshAccount({bool updateProfile = false}) async { + final uid = UserState.userId.value; + if (uid == null || uid.isEmpty) return; + try { + await AuthService.loginComplete; + final res = await UserApi.getAccount( + sentinel: ApiConfig.appId, + asset: uid, + ); + if (!res.isSuccess || res.data == null) return; + final data = res.data as Map?; + final credits = data?['reveal'] as int?; + if (credits != null) UserState.setCredits(credits); + if (updateProfile) { + final avatarUrl = data?['realm'] as String?; + UserState.setAvatar( + avatarUrl != null && avatarUrl.isNotEmpty ? avatarUrl : null); + final name = data?['terminal'] as String?; + UserState.setUserName(name != null && name.isNotEmpty ? name : null); + } + } catch (_) {} +} diff --git a/lib/features/gallery/gallery_screen.dart b/lib/features/gallery/gallery_screen.dart index 1a3f152..75afa13 100644 --- a/lib/features/gallery/gallery_screen.dart +++ b/lib/features/gallery/gallery_screen.dart @@ -8,7 +8,6 @@ import '../../core/api/services/image_api.dart'; import '../../core/auth/auth_service.dart'; import '../../core/theme/app_colors.dart'; import '../../core/theme/app_spacing.dart'; -import '../../core/user/user_state.dart'; import '../../shared/widgets/top_nav_bar.dart'; import 'models/gallery_task_item.dart'; @@ -142,9 +141,7 @@ class _GalleryScreenState extends State { appBar: PreferredSize( preferredSize: const Size.fromHeight(56), child: TopNavBar( - title: 'Gallery', - credits: UserCreditsData.of(context)?.creditsDisplay ?? '--', - onCreditsTap: () => Navigator.of(context).pushNamed('/recharge'), + title: 'My Gallery', ), ), body: _loading @@ -176,7 +173,8 @@ class _GalleryScreenState extends State { onRefresh: () => _loadTasks(refresh: true), child: _gridItems.isEmpty && !_loading ? SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), + physics: + const AlwaysScrollableScrollPhysics(), child: SizedBox( height: constraints.maxHeight - 100, child: Center( @@ -190,7 +188,8 @@ class _GalleryScreenState extends State { ), ) : GridView.builder( - physics: const AlwaysScrollableScrollPhysics(), + physics: + const AlwaysScrollableScrollPhysics(), controller: _scrollController, padding: EdgeInsets.fromLTRB( AppSpacing.screenPadding, @@ -206,8 +205,8 @@ class _GalleryScreenState extends State { mainAxisSpacing: AppSpacing.xl, crossAxisSpacing: AppSpacing.xl, ), - itemCount: - _gridItems.length + (_loadingMore ? 1 : 0), + itemCount: _gridItems.length + + (_loadingMore ? 1 : 0), itemBuilder: (context, index) { if (index >= _gridItems.length) { return const Center( @@ -276,17 +275,17 @@ class _GalleryCard extends StatelessWidget { child: ClipRRect( borderRadius: BorderRadius.circular(24), child: mediaItem.imageUrl != null - ? CachedNetworkImage( - imageUrl: mediaItem.imageUrl!, - fit: BoxFit.cover, - placeholder: (_, __) => Container( - color: AppColors.surfaceAlt, - ), - errorWidget: (_, __, ___) => Container( - color: AppColors.surfaceAlt, - ), - ) - : _VideoThumbnailCover(videoUrl: mediaItem.videoUrl!), + ? CachedNetworkImage( + imageUrl: mediaItem.imageUrl!, + fit: BoxFit.cover, + placeholder: (_, __) => Container( + color: AppColors.surfaceAlt, + ), + errorWidget: (_, __, ___) => Container( + color: AppColors.surfaceAlt, + ), + ) + : _VideoThumbnailCover(videoUrl: mediaItem.videoUrl!), ), ); }, diff --git a/lib/features/generate_video/generate_video_screen.dart b/lib/features/generate_video/generate_video_screen.dart index 190b0f9..6d2178b 100644 --- a/lib/features/generate_video/generate_video_screen.dart +++ b/lib/features/generate_video/generate_video_screen.dart @@ -13,13 +13,12 @@ import '../../core/log/app_logger.dart'; import '../../core/theme/app_colors.dart'; import '../../core/theme/app_spacing.dart'; import '../../core/theme/app_typography.dart'; +import '../../core/user/account_refresh.dart'; import '../../core/user/user_state.dart'; import '../../features/home/models/task_item.dart'; import '../../shared/widgets/top_nav_bar.dart'; -import '../../core/api/api_config.dart'; import '../../core/api/services/image_api.dart'; -import '../../core/api/services/user_api.dart'; /// Generate Video screen - matches Pencil mmLB5 class GenerateVideoScreen extends StatefulWidget { @@ -56,6 +55,7 @@ class _GenerateVideoScreenState extends State { @override void initState() { super.initState(); + refreshAccount(); WidgetsBinding.instance.addPostFrameCallback((_) { GenerateVideoScreen._log.d('opened with task: ${widget.task}'); }); @@ -192,17 +192,7 @@ class _GenerateVideoScreenState extends State { final taskId = taskData?['tree']; // 创建任务成功后刷新用户账户信息(积分等) - final accountRes = await UserApi.getAccount( - sentinel: ApiConfig.appId, - asset: userId, - ); - if (accountRes.isSuccess && accountRes.data != null) { - final accountData = accountRes.data as Map?; - final credits = accountData?['reveal'] as int?; - if (credits != null) { - UserState.setCredits(credits); - } - } + await refreshAccount(); if (!mounted) return; Navigator.of(context).pushReplacementNamed( @@ -235,10 +225,8 @@ class _GenerateVideoScreenState extends State { preferredSize: const Size.fromHeight(56), child: TopNavBar( title: 'Generate Video', - credits: UserCreditsData.of(context)?.creditsDisplay ?? '--', showBackButton: true, onBack: () => Navigator.of(context).pop(), - onCreditsTap: () => Navigator.of(context).pushNamed('/recharge'), ), ), body: Column( diff --git a/lib/features/home/home_screen.dart b/lib/features/home/home_screen.dart index 5e4a131..0630eb5 100644 --- a/lib/features/home/home_screen.dart +++ b/lib/features/home/home_screen.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import '../../core/api/services/image_api.dart'; -import '../../core/user/user_state.dart'; import '../../core/auth/auth_service.dart'; +import '../../core/user/account_refresh.dart'; +import '../../core/user/user_state.dart'; import '../../core/theme/app_spacing.dart'; import '../../shared/widgets/top_nav_bar.dart'; import 'models/category_item.dart'; @@ -12,7 +13,9 @@ import 'widgets/video_card.dart'; /// AI Video App home screen - tab 来自分类接口,Grid 来自任务列表接口 class HomeScreen extends StatefulWidget { - const HomeScreen({super.key}); + const HomeScreen({super.key, this.isActive = true}); + + final bool isActive; @override State createState() => _HomeScreenState(); @@ -30,6 +33,15 @@ class _HomeScreenState extends State { void initState() { super.initState(); _loadCategories(); + if (widget.isActive) refreshAccount(); + } + + @override + void didUpdateWidget(covariant HomeScreen oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.isActive && !oldWidget.isActive) { + refreshAccount(); + } } Future _loadCategories() async { diff --git a/lib/features/profile/profile_screen.dart b/lib/features/profile/profile_screen.dart index 1b56bf2..ac073c9 100644 --- a/lib/features/profile/profile_screen.dart +++ b/lib/features/profile/profile_screen.dart @@ -2,9 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_lucide/flutter_lucide.dart'; import 'package:cached_network_image/cached_network_image.dart'; -import '../../core/api/api_config.dart'; -import '../../core/api/services/user_api.dart'; -import '../../core/auth/auth_service.dart'; +import '../../core/user/account_refresh.dart'; import '../recharge/payment_webview_screen.dart'; import '../../core/theme/app_colors.dart'; import '../../core/user/user_state.dart'; @@ -25,41 +23,17 @@ class _ProfileScreenState extends State { @override void initState() { super.initState(); - if (widget.isActive) _fetchAccount(); + if (widget.isActive) refreshAccount(updateProfile: true); } @override void didUpdateWidget(covariant ProfileScreen oldWidget) { super.didUpdateWidget(oldWidget); if (widget.isActive && !oldWidget.isActive) { - _fetchAccount(); + refreshAccount(updateProfile: true); } } - Future _fetchAccount() async { - final uid = UserState.userId.value; - if (uid == null || uid.isEmpty) return; - try { - await AuthService.loginComplete; - final res = await UserApi.getAccount( - sentinel: ApiConfig.appId, - asset: uid, - ); - if (!mounted) return; - if (res.isSuccess && res.data != null) { - final data = res.data as Map?; - final credits = data?['reveal'] as int?; - if (credits != null) UserState.setCredits(credits); - final avatarUrl = data?['realm'] as String?; - UserState.setAvatar( - avatarUrl != null && avatarUrl.isNotEmpty ? avatarUrl : null); - final name = data?['terminal'] as String?; - UserState.setUserName( - name != null && name.isNotEmpty ? name : null); - } - } catch (_) {} - } - @override Widget build(BuildContext context) { return Scaffold( diff --git a/lib/features/recharge/recharge_screen.dart b/lib/features/recharge/recharge_screen.dart index 81be29e..f47d76f 100644 --- a/lib/features/recharge/recharge_screen.dart +++ b/lib/features/recharge/recharge_screen.dart @@ -6,6 +6,7 @@ import '../../core/adjust/adjust_events.dart'; import '../../core/api/api_config.dart'; import '../../core/api/services/payment_api.dart'; import '../../core/auth/auth_service.dart'; +import '../../core/user/account_refresh.dart'; import '../../core/log/app_logger.dart'; import '../../core/theme/app_colors.dart'; import '../../core/user/user_state.dart'; @@ -39,6 +40,7 @@ class _RechargeScreenState extends State with WidgetsBindingObse void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); + refreshAccount(); _fetchActivities(); } @@ -368,10 +370,8 @@ class _RechargeScreenState extends State with WidgetsBindingObse preferredSize: const Size.fromHeight(56), child: TopNavBar( title: 'Recharge', - credits: UserCreditsData.of(context)?.creditsDisplay ?? '--', showBackButton: true, onBack: () => Navigator.of(context).pop(), - onCreditsTap: null, ), ), body: SingleChildScrollView(