import 'package:client_proxy_framework/client_proxy_framework.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import '../../core/app_env.dart'; import '../../core/auth/auth_service.dart'; import '../../core/user/user_state.dart'; import '../../design/pencil_theme.dart'; import '../../widgets/pencil_chrome.dart'; import '../generate/generate_result_screen.dart'; import 'credit_record_tab.dart'; import 'widgets/history_grid_card.dart'; /// `WBRp4` My History — 黄→白渐变、顶栏、双 Tab、24h 提示、双列卡片。 class HistoryScreen extends StatefulWidget { const HistoryScreen({ super.key, this.isRootTab = false, this.isTabSelected = true, }); /// When true (e.g. bottom tab), hide back button; when pushed, show back. final bool isRootTab; /// Bottom shell: only `true` when the History tab is selected — defers `my-tasks` load. /// Pushed routes should default `true` so load runs as before. final bool isTabSelected; @override State createState() => _HistoryScreenState(); } class _HistoryScreenState extends State { int _tab = 0; bool _loading = false; String? _error; List _items = []; Map _localCovers = {}; VoidCallback? _cancelLoginWait; @override void initState() { super.initState(); _cancelLoginWait = AuthService.whenLoginSucceeded( onReady: _tryLoadTasks, onFailed: () { if (!mounted || !widget.isTabSelected) return; setState(() { _loading = false; _error = 'Sign in failed'; }); }, ); } @override void didUpdateWidget(HistoryScreen oldWidget) { super.didUpdateWidget(oldWidget); if (!oldWidget.isTabSelected && widget.isTabSelected) { _tryLoadTasks(); } } @override void dispose() { _cancelLoginWait?.call(); super.dispose(); } /// `GET /v1/image/my-tasks` only when this tab is visible and login succeeded. void _tryLoadTasks() { if (!widget.isTabSelected) return; if (!AuthService.isLoginComplete.value) return; final uid = UserState.userId.value; if (uid == null || uid.isEmpty) return; _load(); } Future _load() async { setState(() { _loading = true; _error = null; }); final res = await ImageApi.getMyTasks( app: currentBackendAppType(), page: '1', pageSize: '30', ); if (!mounted) return; if (!res.isSuccess || res.data == null) { setState(() { _loading = false; _error = res.msg.isNotEmpty ? res.msg : 'Failed to load'; }); return; } final tasks = res.data!.tasks ?? []; final locals = await ImageTaskHistory.localCoverPathsForMyTaskItems(tasks); setState(() { _loading = false; _items = tasks; _localCovers = locals; }); } @override Widget build(BuildContext context) { return Container( decoration: const BoxDecoration( gradient: PencilTheme.yellowWhitePageGradient, ), child: Scaffold( backgroundColor: Colors.transparent, body: SafeArea( bottom: false, child: Column( children: [ Padding( padding: const EdgeInsets.fromLTRB(4, 0, 4, 8), child: SizedBox( height: 58, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12), child: Row( children: [ if (widget.isRootTab) const SizedBox(width: 44) else PencilRoundBackButton( onPressed: () => Navigator.of(context).pop(), ), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _headerTab('My History', 0), const SizedBox(width: 26), _headerTab('Credit Record', 1), ], ), ), const SizedBox(width: 44), ], ), ), ), ), Expanded( child: _tab == 0 ? _myHistoryBody() : const CreditRecordTab(), ), ], ), ), ), ); } Widget _headerTab(String label, int index) { final selected = _tab == index; return InkWell( onTap: () => setState(() => _tab = index), child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( label, style: GoogleFonts.inter( fontSize: 17, fontWeight: selected ? FontWeight.w600 : FontWeight.w500, fontStyle: FontStyle.italic, color: selected ? PencilTheme.ink : PencilTheme.inkSoft, ), ), const SizedBox(height: 8), if (selected) Container( width: 50, height: 4, decoration: BoxDecoration( color: PencilTheme.underlineGold, borderRadius: BorderRadius.circular(2), ), ) else const SizedBox(height: 4), ], ), ); } Widget _myHistoryBody() { if (!widget.isTabSelected) { return const SizedBox.shrink(); } if (!AuthService.isLoginComplete.value) { return const Center(child: CircularProgressIndicator()); } final uid = UserState.userId.value; if (uid == null || uid.isEmpty) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( _error ?? 'Sign in failed', textAlign: TextAlign.center, ), ], ), ); } if (_loading) { return const Center(child: CircularProgressIndicator()); } if (_error != null) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text(_error!), TextButton(onPressed: _load, child: const Text('Retry')), ], ), ); } return RefreshIndicator( onRefresh: _load, child: CustomScrollView( slivers: [ SliverPadding( padding: const EdgeInsets.fromLTRB(16, 4, 16, 12), sliver: SliverToBoxAdapter(child: _expiryNotice()), ), if (_items.isEmpty) SliverFillRemaining( child: Center( child: Text('No tasks yet.', style: GoogleFonts.inter(color: PencilTheme.inkSoft)), ), ) else SliverPadding( padding: const EdgeInsets.fromLTRB(16, 0, 16, 28), sliver: SliverGrid( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, mainAxisSpacing: 14, crossAxisSpacing: 14, childAspectRatio: 171 / 182, ), delegate: SliverChildBuilderDelegate( (context, i) { final t = _items[i]; final id = t.taskId ?? ''; return HistoryGridCard( item: t, localCoverPath: id.isEmpty ? null : _localCovers[id], onTap: () { Navigator.of(context).push( MaterialPageRoute( builder: (_) => GenerateResultScreen( taskId: id, resultUrl: t.resultUrl?.trim() ?? '', ), ), ); }, onDownload: () {}, ); }, childCount: _items.length, ), ), ), ], ), ); } Widget _expiryNotice() { return Container( padding: const EdgeInsets.fromLTRB(12, 10, 12, 10), decoration: BoxDecoration( color: PencilTheme.expiryBg, borderRadius: BorderRadius.circular(12), border: Border.all(color: PencilTheme.expiryBorder), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '24-hour expiry', style: GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w600, color: PencilTheme.expiryHead, ), ), const SizedBox(height: 4), Text( 'Each item is kept for 24 hours after creation. Download before it expires.', style: GoogleFonts.inter( fontSize: 11, height: 1.35, color: PencilTheme.expiryBody, ), ), ], ), ); } }