import 'dart:async'; 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 { Timer? _timer; String _status = ''; int _progress = 0; String? _resultUrl; String? _error; bool _finished = false; @override void initState() { super.initState(); _poll(); _timer = Timer.periodic(const Duration(seconds: 2), (_) => _poll()); } Future _poll() async { if (_error != null || _finished) return; final uid = UserState.userId.value; final res = await ImageApi.getProgress( app: currentBackendAppType(), taskId: widget.taskId, userId: uid, ); if (!mounted) return; 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; }); if (_isTerminal(_status) || _hasUsableResult(_resultUrl)) { _timer?.cancel(); if (_isSuccess(_status) || _hasUsableResult(_resultUrl)) { _finished = true; if (!mounted) return; Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (_) => GenerateResultScreen( taskId: widget.taskId, resultUrl: _resultUrl ?? '', ), ), ); } else if (_isFailure(_status)) { setState(() => _error ??= 'Task failed ($_status)'); } } } bool _hasUsableResult(String? url) { if (url == null || url.isEmpty) return false; return url.startsWith('http://') || url.startsWith('https://'); } bool _isTerminal(String s) { final t = s.toLowerCase(); return t == 'success' || t == 'completed' || t == 'complete' || t == 'failed' || t == 'failure' || t == 'error' || t == 'cancelled' || t == 'canceled'; } bool _isSuccess(String s) { final t = s.toLowerCase(); return t == 'success' || t == 'completed' || t == 'complete'; } bool _isFailure(String s) { final t = s.toLowerCase(); return t == 'failed' || t == 'failure' || t == 'error' || t == 'cancelled' || t == 'canceled'; } @override void dispose() { _timer?.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, ), ), ], ], ), ), ), ], ), ), ), ); } }