初始化

This commit is contained in:
谢宇宁
2026-09-15 15:44:13 +07:00
commit a23ba685e9
1065 changed files with 101122 additions and 0 deletions
+102
View File
@@ -0,0 +1,102 @@
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);
}
}