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