petsHero-AI/lib/features/generate_video/generate_progress_screen.dart
2026-03-09 20:55:41 +08:00

207 lines
5.6 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_lucide/flutter_lucide.dart';
import '../../core/api/api_config.dart';
import '../../core/auth/auth_service.dart';
import '../../core/theme/app_colors.dart';
import '../../core/theme/app_spacing.dart';
import '../../core/theme/app_typography.dart';
import '../../core/user/user_state.dart';
import '../../shared/widgets/top_nav_bar.dart';
import '../../core/api/services/image_api.dart';
/// Generate Video Progress screen - matches Pencil qGs6n
class GenerateProgressScreen extends StatefulWidget {
const GenerateProgressScreen({super.key, this.taskId});
final dynamic taskId;
@override
State<GenerateProgressScreen> createState() => _GenerateProgressScreenState();
}
class _GenerateProgressScreenState extends State<GenerateProgressScreen> {
double _progress = 0;
Timer? _pollTimer;
@override
void initState() {
super.initState();
if (widget.taskId != null) {
_startPolling();
} else {
_progress = 0.45;
}
}
@override
void dispose() {
_pollTimer?.cancel();
super.dispose();
}
Future<void> _startPolling() async {
await AuthService.loginComplete;
_pollTimer = Timer.periodic(const Duration(seconds: 2), (_) => _fetchProgress());
_fetchProgress();
}
Future<void> _fetchProgress() async {
if (widget.taskId == null) return;
try {
final res = await ImageApi.getProgress(
sentinel: ApiConfig.appId,
tree: widget.taskId.toString(),
asset: UserState.userId.value,
);
if (!res.isSuccess || res.data == null) return;
final data = res.data as Map<String, dynamic>;
final progress = (data['dice'] as num?)?.toInt() ?? 0;
final state = (data['listing'] as num?)?.toInt();
if (!mounted) return;
setState(() {
_progress = progress / 100.0;
});
if (progress >= 100 || state == 2) {
_pollTimer?.cancel();
Navigator.of(context).pushReplacementNamed('/result', arguments: widget.taskId);
} else if (state == 3) {
_pollTimer?.cancel();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Generation failed'),
behavior: SnackBarBehavior.floating,
),
);
}
} catch (_) {}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.background,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: TopNavBar(
title: 'Generating',
showBackButton: true,
onBack: () => Navigator.of(context).pop(),
),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(AppSpacing.screenPaddingLarge),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_VideoPreview(),
const SizedBox(height: AppSpacing.xxl),
Text(
'Video generation may take some time. Please wait patiently.',
textAlign: TextAlign.center,
style: AppTypography.bodyRegular.copyWith(
color: AppColors.textSecondary,
),
),
const SizedBox(height: AppSpacing.xxl),
_ProgressSection(progress: _progress),
],
),
),
);
}
}
class _VideoPreview extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
height: 360,
decoration: BoxDecoration(
color: AppColors.textPrimary,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: AppColors.border, width: 1),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
LucideIcons.film,
size: 64,
color: AppColors.textSecondary,
),
const SizedBox(height: AppSpacing.lg),
Text(
'Video Preview',
style: AppTypography.bodyRegular.copyWith(
color: AppColors.textSecondary,
),
),
],
),
);
}
}
class _ProgressSection extends StatelessWidget {
const _ProgressSection({required this.progress});
final double progress;
@override
Widget build(BuildContext context) {
final percentage = (progress * 100).round();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Generating... $percentage%',
style: AppTypography.bodyMedium.copyWith(
color: AppColors.textPrimary,
),
),
const SizedBox(height: AppSpacing.lg),
LayoutBuilder(
builder: (context, constraints) {
final fillWidth =
constraints.maxWidth * progress.clamp(0.0, 1.0);
return Stack(
children: [
Container(
height: 8,
decoration: BoxDecoration(
color: AppColors.border,
borderRadius: BorderRadius.circular(4),
),
),
Positioned(
left: 0,
top: 0,
bottom: 0,
child: Container(
width: fillWidth,
decoration: BoxDecoration(
color: AppColors.primary,
borderRadius: BorderRadius.circular(4),
),
),
),
],
);
},
),
],
);
}
}