import 'dart:async'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.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(); unawaited( SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky), ); _itemsFuture = VideoDao.list(); } @override void dispose() { unawaited( SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky), ); super.dispose(); } void _reload() { setState(() { _itemsFuture = VideoDao.list(); }); } @override Widget build(BuildContext context) { return MediaQuery.removePadding( context: context, removeTop: true, child: 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: LayoutBuilder( builder: (context, constraints) { final horizontalPadding = 30.w; final crossAxisSpacing = 12.w; final cardWidth = (constraints.maxWidth - horizontalPadding * 2 - crossAxisSpacing * 2) / 3; final cardHeight = cardWidth * 9 / 16 + 5.h + 24.h; return GridView.builder( physics: const AlwaysScrollableScrollPhysics(), padding: EdgeInsets.fromLTRB( horizontalPadding, 10.h, horizontalPadding, 16.h, ), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3, mainAxisSpacing: 14.h, crossAxisSpacing: crossAxisSpacing, mainAxisExtent: cardHeight, ), 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((_) { if (!mounted) return; unawaited( SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky), ); _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: [ AspectRatio( aspectRatio: 16 / 9, child: ClipRRect( borderRadius: BorderRadius.circular(8.r), child: 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)), ), ), ), ), SizedBox(height: 5.h), SizedBox( height: 24.h, child: Align( alignment: Alignment.centerLeft, child: Text( item.title, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( color: const Color(0xFF222222), fontFamily: null, fontSize: 17.sp, fontWeight: FontWeight.w500, ), ), ), ), ], ), ); } }