import 'dart:io'; 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/user/user_state.dart'; import '../../design/pencil_theme.dart'; import '../../widgets/pencil_chrome.dart'; import 'generate_result_screen.dart'; /// `YoZaK` 生成中 — 与 EYsUi 同款顶栏结构,标题「生成中」。 class GenerateProgressScreen extends StatefulWidget { const GenerateProgressScreen({ super.key, required this.taskId, this.localPreviewPath, }); final String taskId; final String? localPreviewPath; @override State createState() => _GenerateProgressScreenState(); } class _GenerateProgressScreenState extends State { ImageProgressPollHandle? _pollHandle; String _status = ''; int _progress = 0; String? _resultUrl; String? _error; bool _navigated = false; @override void initState() { super.initState(); _pollHandle = ImageProgressPoll.start( app: currentBackendAppType(), taskId: widget.taskId, userId: UserState.userId.value, interval: const Duration(seconds: 5), onTick: _onProgressTick, onTransientNetworkFailure: (n, max) { if (!mounted || _navigated) return; setState(() { _status = 'Reconnecting… ($n/$max)'; }); }, onFatalError: (msg) { if (!mounted || _navigated) return; setState(() => _error = msg); }, ); } void _onProgressTick(ProgressPollTick tick) { if (!mounted || _navigated) return; final res = tick.response; if (!res.isSuccess || res.data == null) { setState(() => _error = res.msg.isNotEmpty ? res.msg : 'Progress error'); return; } final p = res.data!; setState(() { _status = p.status ?? ''; _progress = p.progress ?? 0; _resultUrl = p.resultUrl; }); final doneSuccess = ProgressPollSemantics.isSuccessTerminal(p.status) || ProgressPollSemantics.hasUsableResultUrl(p.resultUrl); if (doneSuccess) { _navigated = true; _pollHandle?.cancel(); Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => GenerateResultScreen( taskId: widget.taskId, resultUrl: _resultUrl ?? '', ), ), ); return; } if (ProgressPollSemantics.isTerminalStatus(p.status) && ProgressPollSemantics.isFailureTerminal(p.status)) { setState(() => _error ??= 'Task failed (${p.status})'); } } @override void dispose() { _pollHandle?.cancel(); super.dispose(); } @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(2, 0, 14, 10), child: SizedBox( height: 56, child: Row( children: [ PencilRoundBackButton( onPressed: () => Navigator.of(context).pop(), ), Expanded( child: Center( child: Text( 'Generating', style: GoogleFonts.inter( fontSize: 19, fontWeight: FontWeight.w700, fontStyle: FontStyle.italic, color: PencilTheme.stone900, ), ), ), ), const SizedBox(width: 44), ], ), ), ), Expanded( child: Padding( padding: const EdgeInsets.all(24), child: Column( children: [ if (widget.localPreviewPath != null) ClipRRect( borderRadius: BorderRadius.circular(12), child: AspectRatio( aspectRatio: 1, child: Image.file( File(widget.localPreviewPath!), fit: BoxFit.cover, ), ), ) else const SizedBox(height: 48), const SizedBox(height: 24), if (_error != null) Text( _error!, style: GoogleFonts.inter(color: Colors.red), textAlign: TextAlign.center, ) else ...[ LinearProgressIndicator( value: _progress > 0 ? _progress / 100 : null, color: PencilTheme.underlineGold, ), const SizedBox(height: 16), Text( _status.isEmpty ? 'Processing…' : _status, style: GoogleFonts.inter(color: PencilTheme.stone600), ), Text( '$_progress%', style: GoogleFonts.inter( fontSize: 28, fontWeight: FontWeight.w700, color: PencilTheme.stone900, ), ), ], ], ), ), ), ], ), ), ), ); } }