194 lines
6.0 KiB
Dart
194 lines
6.0 KiB
Dart
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<GenerateProgressScreen> createState() => _GenerateProgressScreenState();
|
|
}
|
|
|
|
class _GenerateProgressScreenState extends State<GenerateProgressScreen> {
|
|
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<void>(
|
|
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,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|