FunyMeeAI/lib/features/history/history_task_progress_screen.dart

184 lines
5.8 KiB
Dart

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 '../../widgets/pencil_yellow_white_background.dart';
import '../generate/generate_result_screen.dart';
/// 从 My History 点「生成中」任务进入:轮询 [ImageApi.getProgress],完成后进入 [GenerateResultScreen]。
///
/// 与 app_client [GenerateProgressScreen] 行为对齐(权威数据源为 `/v1/image/progress`)。
class HistoryTaskProgressScreen extends StatefulWidget {
const HistoryTaskProgressScreen({super.key, required this.taskId});
final String taskId;
@override
State<HistoryTaskProgressScreen> createState() =>
_HistoryTaskProgressScreenState();
}
class _HistoryTaskProgressScreenState extends State<HistoryTaskProgressScreen> {
ImageProgressPollHandle? _pollHandle;
String _genStatus = '';
int _genProgress = 0;
String? _genResultUrl;
String? _genError;
bool _navigated = false;
@override
void initState() {
super.initState();
_startPoll();
}
void _startPoll() {
_pollHandle?.cancel();
_pollHandle = ImageProgressPoll.start(
app: currentBackendAppType(),
taskId: widget.taskId,
userId: UserState.userId.value,
interval: const Duration(seconds: 5),
onTick: _onTick,
onFatalError: (msg) {
if (!mounted || _navigated) return;
setState(() => _genError = msg);
},
);
}
void _onTick(ProgressPollTick tick) {
if (!mounted || _navigated) return;
final res = tick.response;
if (!res.isSuccess || res.data == null) {
setState(() => _genError = res.msg.isNotEmpty ? res.msg : 'Progress error');
return;
}
final p = res.data!;
setState(() {
_genError = null;
_genStatus = p.status ?? '';
_genProgress = p.progress ?? 0;
_genResultUrl = p.resultUrl;
});
if (ProgressPollSemantics.isProgressSuccess(p)) {
_navigated = true;
_pollHandle?.cancel();
_pollHandle = null;
final url = _genResultUrl ?? '';
Navigator.of(context).pushReplacement(
MaterialPageRoute<void>(
builder: (_) => GenerateResultScreen(
taskId: widget.taskId,
resultUrl: url,
),
),
);
return;
}
if (ProgressPollSemantics.isProgressFailure(p)) {
_pollHandle?.cancel();
_pollHandle = null;
setState(() {
_genError ??= 'Task failed (${p.status})';
});
}
}
@override
void dispose() {
_pollHandle?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return PencilYellowWhitePageBackground(
child: Scaffold(
backgroundColor: Colors.transparent,
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(2, 0, 14, 8),
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.symmetric(horizontal: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
if (_genError != null) ...[
Text(
_genError!,
style: GoogleFonts.inter(color: Colors.red),
textAlign: TextAlign.center,
),
] else ...[
LinearProgressIndicator(
value: _genProgress > 0 ? _genProgress / 100 : null,
color: PencilTheme.underlineGold,
),
const SizedBox(height: 16),
Text(
_genStatus.isEmpty ? 'Processing…' : _genStatus,
textAlign: TextAlign.center,
style: GoogleFonts.inter(
fontSize: 14,
color: PencilTheme.stone600,
),
),
Text(
'$_genProgress%',
textAlign: TextAlign.center,
style: GoogleFonts.inter(
fontSize: 28,
fontWeight: FontWeight.w700,
color: PencilTheme.stone900,
),
),
],
],
),
),
),
],
),
),
),
);
}
}