petsHero-AI/lib/features/home/home_screen.dart
2026-03-11 09:45:42 +08:00

169 lines
6.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import '../../core/api/services/image_api.dart';
import '../../core/user/user_state.dart';
import '../../core/auth/auth_service.dart';
import '../../core/theme/app_spacing.dart';
import '../../shared/widgets/top_nav_bar.dart';
import 'models/category_item.dart';
import 'models/task_item.dart';
import 'widgets/home_tab_row.dart';
import 'widgets/video_card.dart';
/// AI Video App home screen - tab 来自分类接口Grid 来自任务列表接口
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
List<CategoryItem> _categories = [];
CategoryItem? _selectedCategory;
List<TaskItem> _tasks = [];
bool _categoriesLoading = true;
bool _tasksLoading = false;
int? _activeCardIndex;
@override
void initState() {
super.initState();
_loadCategories();
}
Future<void> _loadCategories() async {
setState(() => _categoriesLoading = true);
await AuthService.loginComplete;
if (!mounted) return;
final res = await ImageApi.getCategoryList();
if (mounted) {
if (res.isSuccess && res.data is List) {
final list = (res.data as List)
.map((e) => CategoryItem.fromJson(e as Map<String, dynamic>))
.toList();
setState(() {
_categories = list;
_selectedCategory = list.isNotEmpty ? list.first : null;
if (_selectedCategory != null) _loadTasks(_selectedCategory!.id);
});
} else {
setState(() => _categories = []);
}
setState(() => _categoriesLoading = false);
}
}
Future<void> _loadTasks(int categoryId) async {
setState(() => _tasksLoading = true);
final res = await ImageApi.getImg2VideoTasks(insignia: categoryId);
if (mounted) {
if (res.isSuccess && res.data is List) {
final list = (res.data as List)
.map((e) => TaskItem.fromJson(e as Map<String, dynamic>))
.toList();
setState(() {
_tasks = list;
_activeCardIndex = null;
});
} else {
setState(() => _tasks = []);
}
setState(() => _tasksLoading = false);
}
}
void _onTabChanged(CategoryItem c) {
setState(() => _selectedCategory = c);
_loadTasks(c.id);
}
static const _placeholderImage =
'https://images.unsplash.com/photo-1763929272543-0df093e4f659?w=400';
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFFAFAFA),
appBar: PreferredSize(
preferredSize: const Size.fromHeight(56),
child: TopNavBar(
title: 'PetsHero AI',
credits: UserCreditsData.of(context)?.creditsDisplay ?? '--',
onCreditsTap: () => Navigator.of(context).pushNamed('/recharge'),
),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: AppSpacing.screenPadding,
vertical: AppSpacing.xs,
),
child: _categoriesLoading
? const SizedBox(
height: 40,
child: Center(child: CircularProgressIndicator()))
: HomeTabRow(
categories: _categories,
selectedId: _selectedCategory?.id ?? -1,
onTabChanged: _onTabChanged,
),
),
Expanded(
child: _tasksLoading
? const Center(child: CircularProgressIndicator())
: LayoutBuilder(
builder: (context, constraints) {
return Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 390),
child: GridView.builder(
padding: const EdgeInsets.fromLTRB(
AppSpacing.screenPadding,
AppSpacing.xl,
AppSpacing.screenPadding,
AppSpacing.screenPaddingLarge,
),
gridDelegate:
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 165 / 248,
mainAxisSpacing: AppSpacing.xl,
crossAxisSpacing: AppSpacing.xl,
),
itemCount: _tasks.length,
itemBuilder: (context, index) {
final task = _tasks[index];
final credits = task.credits480p != null
? task.credits480p.toString()
: '50';
return VideoCard(
imageUrl:
task.previewImageUrl ?? _placeholderImage,
videoUrl: task.previewVideoUrl,
credits: credits,
isActive: _activeCardIndex == index,
onPlayRequested: () =>
setState(() => _activeCardIndex = index),
onStopRequested: () =>
setState(() => _activeCardIndex = null),
onGenerateSimilar: () =>
Navigator.of(context).pushNamed(
'/generate',
arguments: task,
),
);
},
),
),
);
},
),
),
],
),
);
}
}