103 lines
3.9 KiB
Dart
103 lines
3.9 KiB
Dart
import 'package:flutter/material.dart';
|
||
import 'package:get/get.dart';
|
||
import 'package:hgdj/hj_model/drama/drama_models.dart';
|
||
import 'package:hgdj/hj_model/video_model.dart';
|
||
import 'package:hgdj/tools_base/cache/history/drama_resume_store.dart';
|
||
import 'package:hgdj/hj_utils/api_service/drama_service.dart';
|
||
import 'package:hgdj/tools_base/debug_log.dart';
|
||
import 'package:uuid/uuid.dart';
|
||
|
||
/// AI短剧信息流:一条 = 一部剧的第 1 集,竖划换下一部剧。
|
||
/// 排序、去重、已看沉底全在服务端,**没有页码**——每次请求就是下一批,客户端不得二次排序,
|
||
/// 也不做二次去重(服务端目前会重发同一部剧,去重就意味着列表不增长、用户直接划不动)
|
||
class DramaFeedLogic extends GetxController {
|
||
// GetBuilder 局部刷新 id:当前页变化时只刷信息流
|
||
static const idFeed = 'feed';
|
||
|
||
// ===== 外部传入 =====
|
||
final TabController? superTabCtr; // 外层频道 tab,用于切到本 tab 时补加载
|
||
final int? tabIndex;
|
||
|
||
DramaFeedLogic({this.superTabCtr, this.tabIndex});
|
||
|
||
// ===== 状态 =====
|
||
bool isFirstLoaded = false; // 首屏是否加载完成(false 时显示 loading)
|
||
bool isLoading = false; // 防并发翻页
|
||
bool hasNext = true;
|
||
|
||
/// 本批的幂等键:网络重试沿用,拿到结果才作废
|
||
String? _requestId;
|
||
|
||
// ===== 数据 =====
|
||
List<VideoModel>? list;
|
||
|
||
// ===== 交互态 =====
|
||
int pageIndex = 0; // 当前显示第几条
|
||
|
||
// ===== Controller =====
|
||
late PageController pageCtr;
|
||
final ValueNotifier<bool> isScrolling = ValueNotifier(false); // 上下滑动切剧时隐藏进度条
|
||
|
||
@override
|
||
void onInit() {
|
||
super.onInit();
|
||
pageCtr = PageController(initialPage: pageIndex);
|
||
superTabCtr?.addListener(_onTabChanged);
|
||
//续播位置是本地记的,进来就把表读上来。不用 await:读的是本地 sqflite,比首批网络请求快得多,
|
||
//播放器起播时(要等 /media_content/info 回来)必然已就绪
|
||
dramaResume.load();
|
||
loadData();
|
||
}
|
||
|
||
@override
|
||
void onClose() {
|
||
superTabCtr?.removeListener(_onTabChanged);
|
||
pageCtr.dispose();
|
||
isScrolling.dispose();
|
||
super.onClose();
|
||
}
|
||
|
||
// 切到本 tab 且还没数据就补一次
|
||
void _onTabChanged() {
|
||
if (superTabCtr?.index == tabIndex && list?.isEmpty == true) loadData();
|
||
}
|
||
|
||
/// [isRefresh] 下拉刷新时清空重来,否则往后追加
|
||
Future<void> loadData({bool isRefresh = true}) async {
|
||
if (isLoading) return;
|
||
if (!isRefresh && !hasNext) return;
|
||
isLoading = true;
|
||
try {
|
||
final resp = await DramaService.fetchFeed(
|
||
size: 20, requestId: _requestId ??= const Uuid().v4());
|
||
if (resp != null) _requestId = null; // 成功才作废;失败重试沿用同一个 id,避免服务端当成两批
|
||
//拿到什么收什么,不按剧 id 去重:服务端反复下发同一部剧时,去重会让列表不增长,
|
||
//用户停在最后一条划不动、onPageChanged 也不再触发,就彻底卡死了。
|
||
//宁可刷到重复的剧,也不能卡住——重复的 key 由页面用「剧 id + 下标」避开
|
||
final more = <VideoModel>[];
|
||
for (final item in resp?.list ?? const <DramaFeedItem>[]) {
|
||
final media = item.media;
|
||
if (media == null) continue; // 缺 media 的脏数据,别在信息流里留个空位
|
||
more.add(media.toVideoModel(item.content));
|
||
}
|
||
hasNext = resp?.hasNext ?? false;
|
||
list = isRefresh ? more : [...?list, ...more];
|
||
} catch (e) {
|
||
list ??= [];
|
||
debugLog('短剧信息流获取失败:$e');
|
||
}
|
||
isLoading = false;
|
||
isFirstLoaded = true;
|
||
update();
|
||
}
|
||
|
||
void onPageChanged(int index) {
|
||
if (pageIndex != index) {
|
||
pageIndex = index;
|
||
update([idFeed]); // 只刷信息流,各 item 重算 isCurrentPage
|
||
}
|
||
// 快到底了先把下一批拉上,别等滑到最后一条才请求
|
||
if (hasNext && (list?.length ?? 0) - index <= 6) loadData(isRefresh: false);
|
||
}
|
||
}
|