import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:wow_english/common/dialogs/show_dialog.dart'; import 'package:wow_english/common/request/dao/video_dao.dart'; import 'package:wow_english/common/widgets/we_app_bar.dart'; import 'package:wow_english/route/route.dart'; import 'video_zone_item.dart'; class VideoZonePage extends StatefulWidget { const VideoZonePage({super.key}); @override State createState() => _VideoZonePageState(); } class _VideoZonePageState extends State { late Future> _itemsFuture; @override void initState() { super.initState(); _itemsFuture = VideoDao.list(); } void _reload() { setState(() => _itemsFuture = VideoDao.list()); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: const WEAppBar( titleText: '视频专区', centerTitle: false, ), body: FutureBuilder>( future: _itemsFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return _VideoLoadError(onRetry: _reload); } final items = snapshot.data ?? const []; if (items.isEmpty) { return const Center( child: Text( '暂无视频', style: TextStyle(color: Color(0xFF999999), fontSize: 18), ), ); } return RefreshIndicator( onRefresh: () async { final future = VideoDao.list(); setState(() => _itemsFuture = future); await future; }, child: GridView.builder( physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.fromLTRB(14.w, 10.h, 14.w, 16.h), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, mainAxisSpacing: 14.h, crossAxisSpacing: 12.w, childAspectRatio: 1.55, ), itemCount: items.length, itemBuilder: (context, index) => _VideoCard( item: items[index], onTap: () => _openVideo(items, index), ), ), ); }, ), ); } void _openVideo(List items, int index) { final item = items[index]; if (!item.accessible || item.videoUrl.isEmpty) { showTwoActionDialog('提示', '取消', '去购买', '购买课程后即可观看视频。', leftTap: popPage, rightTap: () { popPage(); pushNamed(AppRouteName.shop); }); return; } Navigator.of(context).pushNamed( AppRouteName.videoZonePlayer, arguments: { 'items': items .where((video) => video.accessible && video.videoUrl.isNotEmpty) .toList(), 'initialVideoId': item.id, }, ).then((_) => _reload()); } } class _VideoLoadError extends StatelessWidget { const _VideoLoadError({required this.onRetry}); final VoidCallback onRetry; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text( '视频加载失败,请稍后重试', style: TextStyle(color: Color(0xFF999999), fontSize: 18), ), const SizedBox(height: 12), FilledButton(onPressed: onRetry, child: const Text('重新加载')), ], ), ); } } class _VideoCard extends StatelessWidget { const _VideoCard({required this.item, required this.onTap}); final VideoZoneItem item; final VoidCallback onTap; @override Widget build(BuildContext context) { return InkWell( borderRadius: BorderRadius.circular(8.r), onTap: onTap, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: ClipRRect( borderRadius: BorderRadius.circular(8.r), child: Stack( fit: StackFit.expand, children: [ CachedNetworkImage( imageUrl: item.coverUrl, fit: BoxFit.cover, placeholder: (_, __) => const ColoredBox( color: Color(0xFFF2F2F2), child: Center(child: CircularProgressIndicator()), ), errorWidget: (_, __, ___) => const ColoredBox( color: Color(0xFFF2F2F2), child: Icon(Icons.broken_image_outlined, color: Color(0xFFAAAAAA)), ), ), const Center( child: Icon(Icons.play_circle_fill, size: 42, color: Colors.white), ), ], ), ), ), SizedBox(height: 5.h), Text( item.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: const Color(0xFF222222), fontFamily: null, fontSize: 17.sp, fontWeight: FontWeight.w500, ), ), ], ), ); } }