video_zone_page.dart 5.58 KB
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<VideoZonePage> createState() => _VideoZonePageState();
}

class _VideoZonePageState extends State<VideoZonePage> {
  late Future<List<VideoZoneItem>> _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<List<VideoZoneItem>>(
        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 <VideoZoneItem>[];
          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<VideoZoneItem> 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,
            ),
          ),
        ],
      ),
    );
  }
}