163 lines
5.7 KiB
Dart
163 lines
5.7 KiB
Dart
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: 'AI Video',
|
||
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];
|
||
return VideoCard(
|
||
imageUrl: task.previewImageUrl ?? _placeholderImage,
|
||
videoUrl: task.previewVideoUrl,
|
||
isActive: _activeCardIndex == index,
|
||
onPlayRequested: () =>
|
||
setState(() => _activeCardIndex = index),
|
||
onStopRequested: () =>
|
||
setState(() => _activeCardIndex = null),
|
||
onGenerateSimilar: () =>
|
||
Navigator.of(context).pushNamed(
|
||
'/generate',
|
||
arguments: task,
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
}
|