From 41ddeab41aeecca0b1bb966c0e734110db487566 Mon Sep 17 00:00:00 2001 From: orz12 Date: Sat, 20 Jan 2024 15:14:52 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E6=A8=A1=E6=8B=9F?= =?UTF-8?q?=E6=9C=AA=E7=99=BB=E5=BD=95=E6=8E=A8=E8=8D=90=EF=BC=8C=E7=8B=AC?= =?UTF-8?q?=E7=AB=8B=E6=8E=A8=E8=8D=90=E8=AE=BE=E7=BD=AE=EF=BC=8C=E6=96=B0?= =?UTF-8?q?=E5=A2=9Eaccesskey=E9=A3=8E=E6=8E=A7=E8=AD=A6=E5=91=8A=EF=BC=8C?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E6=8E=A8=E8=8D=90=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/video.dart | 18 ++- lib/models/common/rcmd_type.dart | 6 +- lib/pages/rcmd/controller.dart | 91 ++++++--------- lib/pages/rcmd/view.dart | 60 ++-------- lib/pages/setting/extra_setting.dart | 62 ---------- lib/pages/setting/recommend_setting.dart | 137 +++++++++++++++++++++++ lib/pages/setting/view.dart | 5 + lib/router/app_pages.dart | 4 +- lib/utils/storage.dart | 8 +- 9 files changed, 210 insertions(+), 181 deletions(-) create mode 100644 lib/pages/setting/recommend_setting.dart diff --git a/lib/http/video.dart b/lib/http/video.dart index 923e93a2..39c961cd 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -46,7 +46,9 @@ class VideoHttp { setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]); for (var i in res.data['data']['item']) { //过滤掉live与ad,以及拉黑用户 - if (i['goto'] == 'av' && !blackMidsList.contains(i['owner']['mid'])) { + if (i['goto'] == 'av' && + (i['owner'] != null && + !blackMidsList.contains(i['owner']['mid']))) { list.add(RecVideoItemModel.fromJson(i)); } } @@ -59,7 +61,9 @@ class VideoHttp { } } - static Future rcmdVideoListApp({int? ps, required int freshIdx}) async { + // 添加额外的loginState变量模拟未登录状态 + static Future rcmdVideoListApp( + {bool loginStatus = true, required int freshIdx}) async { try { var res = await Request().get( Api.recommendListApp, @@ -72,9 +76,11 @@ class VideoHttp { 'device_name': 'vivo', 'pull': freshIdx == 0 ? 'true' : 'false', 'appkey': Constants.appKey, - 'access_key': localCache - .get(LocalCacheKey.accessKey, defaultValue: {})['value'] ?? - '' + 'access_key': loginStatus + ? (localCache.get(LocalCacheKey.accessKey, + defaultValue: {})['value'] ?? + '') + : '' }, ); if (res.data['code'] == 0) { @@ -92,7 +98,7 @@ class VideoHttp { } return {'status': true, 'data': list}; } else { - return {'status': false, 'data': [], 'msg': ''}; + return {'status': false, 'data': [], 'msg': res.data['message']}; } } catch (err) { return {'status': false, 'data': [], 'msg': err.toString()}; diff --git a/lib/models/common/rcmd_type.dart b/lib/models/common/rcmd_type.dart index dbb64b15..5af18d77 100644 --- a/lib/models/common/rcmd_type.dart +++ b/lib/models/common/rcmd_type.dart @@ -1,7 +1,7 @@ // 首页推荐类型 -enum RcmdType { web, app } +enum RcmdType { web, app, notLogin } extension RcmdTypeExtension on RcmdType { - String get values => ['web', 'app'][index]; - String get labels => ['web端', 'app端'][index]; + String get values => ['web', 'app', 'notLogin'][index]; + String get labels => ['web端', 'app端', '模拟未登录'][index]; } diff --git a/lib/pages/rcmd/controller.dart b/lib/pages/rcmd/controller.dart index 4ed1ade1..ed98606a 100644 --- a/lib/pages/rcmd/controller.dart +++ b/lib/pages/rcmd/controller.dart @@ -9,8 +9,8 @@ import 'package:pilipala/utils/storage.dart'; class RcmdController extends GetxController { final ScrollController scrollController = ScrollController(); int _currentPage = 0; - RxList appVideoList = [].obs; - RxList webVideoList = [].obs; + // RxList appVideoList = [].obs; + // RxList webVideoList = [].obs; bool isLoadingMore = true; OverlayEntry? popupDialog; Box recVideo = GStrorage.recVideo; @@ -18,6 +18,7 @@ class RcmdController extends GetxController { RxInt crossAxisCount = 2.obs; late bool enableSaveLastData; late String defaultRcmdType = 'web'; + late RxList videoList; @override void onInit() { @@ -37,85 +38,59 @@ class RcmdController extends GetxController { setting.get(SettingBoxKey.enableSaveLastData, defaultValue: false); defaultRcmdType = setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web'); + if (defaultRcmdType == 'web'){ + videoList = [].obs; + } else { + videoList = [].obs; + } } // 获取推荐 Future queryRcmdFeed(type) async { - print(defaultRcmdType); - if (defaultRcmdType == 'app') { - return await queryRcmdFeedApp(type); - } - if (defaultRcmdType == 'web') { - return await queryRcmdFeedWeb(type); - } - } - - // 获取app端推荐 - Future queryRcmdFeedApp(type) async { if (isLoadingMore == false) { return; } if (type == 'onRefresh') { _currentPage = 0; } - var res = await VideoHttp.rcmdVideoListApp( - freshIdx: _currentPage, - ); + late final Map res; + switch (defaultRcmdType) { + case 'app': case 'notLogin': + res = await VideoHttp.rcmdVideoListApp( + loginStatus: defaultRcmdType != 'notLogin', + freshIdx: _currentPage, + ); + break; + default: //'web' + res = await VideoHttp.rcmdVideoList( + freshIdx: _currentPage, + ps: 20, + ); + } if (res['status']) { if (type == 'init') { - if (appVideoList.isNotEmpty) { - appVideoList.addAll(res['data']); + if (videoList.isNotEmpty) { + videoList.addAll(res['data']); } else { - appVideoList.value = res['data']; + videoList.value = res['data']; } } else if (type == 'onRefresh') { if (enableSaveLastData) { - appVideoList.insertAll(0, res['data']); + videoList.insertAll(0, res['data']); } else { - appVideoList.value = res['data']; + videoList.value = res['data']; } } else if (type == 'onLoad') { - appVideoList.addAll(res['data']); + videoList.addAll(res['data']); } - recVideo.put('cacheList', res['data']); - _currentPage += 1; - } - isLoadingMore = false; - return res; - } - - // 获取web端推荐 - Future queryRcmdFeedWeb(type) async { - if (isLoadingMore == false) { - return; - } - if (type == 'onRefresh') { - _currentPage = 0; - } - var res = await VideoHttp.rcmdVideoList( - ps: 20, - freshIdx: _currentPage, - ); - if (res['status']) { - if (type == 'init') { - if (webVideoList.isNotEmpty) { - webVideoList.addAll(res['data']); - } else { - webVideoList.value = res['data']; - } - } else if (type == 'onRefresh') { - if (enableSaveLastData) { - webVideoList.insertAll(0, res['data']); - } else { - webVideoList.value = res['data']; - } - } else if (type == 'onLoad') { - webVideoList.addAll(res['data']); + if (defaultRcmdType != 'web') { + recVideo.put('cacheList', res['data']); } _currentPage += 1; + } else { + Get.snackbar('提示', res['msg']); } isLoadingMore = false; - return res; } // 下拉刷新 @@ -129,7 +104,7 @@ class RcmdController extends GetxController { queryRcmdFeed('onLoad'); } - // 返回顶部并刷新 + // 返回顶部 void animateToTop() async { if (scrollController.offset >= MediaQuery.of(Get.context!).size.height * 5) { diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index bc3c4a00..0a345511 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -91,54 +91,18 @@ class _RcmdPageState extends State SliverPadding( padding: const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0), - sliver: FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { - return Platform.isAndroid || Platform.isIOS - ? Obx( - () => contentGrid( - _rcmdController, - _rcmdController.defaultRcmdType == 'web' - ? _rcmdController.webVideoList - : _rcmdController.appVideoList), - ) - : SliverLayoutBuilder( - builder: (context, boxConstraints) { - return Obx( - () => contentGrid( - _rcmdController, - _rcmdController.defaultRcmdType == 'web' - ? _rcmdController.webVideoList - : _rcmdController.appVideoList), - ); - }); - } else { - return HttpError( - errMsg: data['msg'], - fn: () { - setState(() { - _futureBuilderFuture = - _rcmdController.queryRcmdFeed('init'); - }); - }, - ); - } - } else { - // 缓存数据 - // if (_rcmdController.videoList.isNotEmpty) { - // return contentGrid( - // _rcmdController, _rcmdController.videoList); - // } - // // 骨架屏 - // else { - return contentGrid(_rcmdController, []); - // } - } - }, - ), + sliver: Obx(() { + // 使用Obx来监听数据的变化 + if (_rcmdController.isLoadingMore) { + // 如果正在加载,则显示骨架屏 + return contentGrid(_rcmdController, []); + } else { + // 显示视频列表 + return contentGrid( + _rcmdController, + _rcmdController.videoList); + } + }), ), LoadingMore(ctr: _rcmdController) ], diff --git a/lib/pages/setting/extra_setting.dart b/lib/pages/setting/extra_setting.dart index b4275815..b32a06f5 100644 --- a/lib/pages/setting/extra_setting.dart +++ b/lib/pages/setting/extra_setting.dart @@ -1,9 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:hive/hive.dart'; -import 'package:pilipala/http/member.dart'; import 'package:pilipala/models/common/dynamics_type.dart'; -import 'package:pilipala/models/common/rcmd_type.dart'; import 'package:pilipala/models/common/reply_sort_type.dart'; import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; import 'package:pilipala/utils/storage.dart'; @@ -20,23 +18,16 @@ class ExtraSetting extends StatefulWidget { class _ExtraSettingState extends State { Box setting = GStrorage.setting; static Box localCache = GStrorage.localCache; - late dynamic defaultRcmdType; late dynamic defaultReplySort; late dynamic defaultDynamicType; late dynamic enableSystemProxy; late String defaultSystemProxyHost; late String defaultSystemProxyPort; - Box userInfoCache = GStrorage.userInfo; - var userInfo; bool userLogin = false; - var accessKeyInfo; @override void initState() { super.initState(); - // 首页默认推荐类型 - defaultRcmdType = - setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web'); // 默认优先显示最新评论 defaultReplySort = setting.get(SettingBoxKey.replySortType, defaultValue: 0); @@ -49,9 +40,6 @@ class _ExtraSettingState extends State { localCache.get(LocalCacheKey.systemProxyHost, defaultValue: ''); defaultSystemProxyPort = localCache.get(LocalCacheKey.systemProxyPort, defaultValue: ''); - userInfo = userInfoCache.get('userInfoCache'); - userLogin = userInfo != null; - accessKeyInfo = localCache.get(LocalCacheKey.accessKey, defaultValue: null); } // 设置代理 @@ -159,12 +147,6 @@ class _ExtraSettingState extends State { setKey: SettingBoxKey.enableSearchWord, defaultVal: true, ), - const SetSwitchItem( - title: '推荐动态', - subTitle: '是否在推荐内容中展示动态', - setKey: SettingBoxKey.enableRcmdDynamic, - defaultVal: true, - ), const SetSwitchItem( title: '快速收藏', subTitle: '点按收藏至默认,长按选择文件夹', @@ -177,50 +159,6 @@ class _ExtraSettingState extends State { setKey: SettingBoxKey.enableWordRe, defaultVal: false, ), - const SetSwitchItem( - title: '首页推荐刷新', - subTitle: '下拉刷新时保留上次内容', - setKey: SettingBoxKey.enableSaveLastData, - defaultVal: false, - ), - ListTile( - dense: false, - title: Text('首页推荐类型', style: titleStyle), - subtitle: Text( - '当前使用「$defaultRcmdType端」推荐', - style: subTitleStyle, - ), - onTap: () async { - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: '推荐类型', - value: defaultRcmdType, - values: RcmdType.values.map((e) { - return {'title': e.labels, 'value': e.values}; - }).toList(), - ); - }, - ); - if (result != null) { - if (result == 'app') { - // app端推荐需要access_key - if (accessKeyInfo == null) { - if (!userLogin) { - SmartDialog.showToast('请先登录'); - return; - } - await MemberHttp.cookieToKey(); - } - } - defaultRcmdType = result; - setting.put(SettingBoxKey.defaultRcmdType, result); - SmartDialog.showToast('下次启动时生效'); - setState(() {}); - } - }, - ), const SetSwitchItem( title: '启用ai总结', subTitle: '视频详情页开启ai总结', diff --git a/lib/pages/setting/recommend_setting.dart b/lib/pages/setting/recommend_setting.dart new file mode 100644 index 00000000..8e049bc4 --- /dev/null +++ b/lib/pages/setting/recommend_setting.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/http/member.dart'; +import 'package:pilipala/models/common/rcmd_type.dart'; +import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; +import 'package:pilipala/utils/storage.dart'; + +import 'widgets/switch_item.dart'; + +class RecommendSetting extends StatefulWidget { + const RecommendSetting({super.key}); + + @override + State createState() => _RecommendSettingState(); +} + +class _RecommendSettingState extends State { + Box setting = GStrorage.setting; + static Box localCache = GStrorage.localCache; + late dynamic defaultRcmdType; + Box userInfoCache = GStrorage.userInfo; + late dynamic userInfo; + bool userLogin = false; + late dynamic accessKeyInfo; + + @override + void initState() { + super.initState(); + // 首页默认推荐类型 + defaultRcmdType = + setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web'); + userInfo = userInfoCache.get('userInfoCache'); + userLogin = userInfo != null; + accessKeyInfo = localCache.get(LocalCacheKey.accessKey, defaultValue: null); + } + + @override + Widget build(BuildContext context) { + TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!; + TextStyle subTitleStyle = Theme.of(context) + .textTheme + .labelMedium! + .copyWith(color: Theme.of(context).colorScheme.outline); + return Scaffold( + appBar: AppBar( + centerTitle: false, + titleSpacing: 0, + title: Text( + '推荐设置', + style: Theme.of(context).textTheme.titleMedium, + ), + ), + body: ListView( + children: [ + const SetSwitchItem( + title: '推荐动态', + subTitle: '是否在推荐内容中展示动态', + setKey: SettingBoxKey.enableRcmdDynamic, + defaultVal: true, + ), + const SetSwitchItem( + title: '首页推荐刷新', + subTitle: '下拉刷新时保留上次内容', + setKey: SettingBoxKey.enableSaveLastData, + defaultVal: false, + ), + ListTile( + dense: false, + title: Text('首页推荐类型', style: titleStyle), + subtitle: Text( + '当前使用「$defaultRcmdType端」推荐', + style: subTitleStyle, + ), + onTap: () async { + String? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '推荐类型', + value: defaultRcmdType, + values: RcmdType.values.map((e) { + return {'title': e.labels, 'value': e.values}; + }).toList(), + ); + }, + ); + if (result != null) { + if (result == 'app') { + // app端推荐需要access_key + if (accessKeyInfo == null) { + if (!userLogin) { + SmartDialog.showToast('请先登录'); + return; + } + // 显示一个确认框,告知用户可能会导致账号被风控 + SmartDialog.show( + animationType: SmartAnimationType.centerFade_otherSlide, + builder: (context) { + return AlertDialog( + title: const Text('提示'), + content: const Text( + '使用app端推荐需获取access_key,有小概率触发风控导致账号退出(在官方app重新登录即可解除),是否继续?'), + actions: [ + TextButton( + onPressed: () { + result = null; + SmartDialog.dismiss(); + }, + child: const Text('取消'), + ), + TextButton( + onPressed: () async { + SmartDialog.dismiss(); + await MemberHttp.cookieToKey(); + }, + child: const Text('确定'), + ), + ], + ); + }); + } + } + if (result != null) { + defaultRcmdType = result; + setting.put(SettingBoxKey.defaultRcmdType, result); + SmartDialog.showToast('下次启动时生效'); + setState(() {}); + } + } + }, + ), + ], + ), + ); + } +} diff --git a/lib/pages/setting/view.dart b/lib/pages/setting/view.dart index 677a4546..19cdedaf 100644 --- a/lib/pages/setting/view.dart +++ b/lib/pages/setting/view.dart @@ -24,6 +24,11 @@ class SettingPage extends StatelessWidget { dense: false, title: const Text('隐私设置'), ), + ListTile( + onTap: () => Get.toNamed('/recommendSetting'), + dense: false, + title: const Text('推荐设置'), + ), ListTile( onTap: () => Get.toNamed('/playSetting'), dense: false, diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 996869be..d6b2f9a9 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -37,6 +37,7 @@ import '../pages/setting/pages/color_select.dart'; import '../pages/setting/pages/display_mode.dart'; import '../pages/setting/pages/font_size_select.dart'; import '../pages/setting/pages/play_speed_set.dart'; +import '../pages/setting/recommend_setting.dart'; import '../pages/setting/play_setting.dart'; import '../pages/setting/privacy_setting.dart'; import '../pages/setting/style_setting.dart'; @@ -100,7 +101,8 @@ class Routes { // 二级回复 CustomGetPage( name: '/replyReply', page: () => const VideoReplyReplyPanel()), - + // 推荐设置 + CustomGetPage(name: '/recommendSetting', page: () => const RecommendSetting()), // 播放设置 CustomGetPage(name: '/playSetting', page: () => const PlaySetting()), // 外观设置 diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index f59a7bc1..c412606f 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -120,17 +120,19 @@ class SettingBoxKey { /// 隐私 blackMidsList = 'blackMidsList', + /// 推荐 + enableRcmdDynamic = 'enableRcmdDynamic', + defaultRcmdType = 'defaultRcmdType', + enableSaveLastData = 'enableSaveLastData', + /// 其他 autoUpdate = 'autoUpdate', - defaultRcmdType = 'defaultRcmdType', replySortType = 'replySortType', defaultDynamicType = 'defaultDynamicType', enableHotKey = 'enableHotKey', enableQuickFav = 'enableQuickFav', enableWordRe = 'enableWordRe', enableSearchWord = 'enableSearchWord', - enableRcmdDynamic = 'enableRcmdDynamic', - enableSaveLastData = 'enableSaveLastData', enableSystemProxy = 'enableSystemProxy', enableAi = 'enableAi'; From 9122dd7f3a16c3259b262ee52558fb9b0ca426df Mon Sep 17 00:00:00 2001 From: orz12 Date: Sat, 20 Jan 2024 17:07:10 +0800 Subject: [PATCH 2/4] =?UTF-8?q?mod:=20=E6=96=B0=E5=A2=9E=E6=8E=A8=E8=8D=90?= =?UTF-8?q?=E8=BF=87=E6=BB=A4=E5=99=A8=EF=BC=8C=E5=9B=9E=E9=80=80model?= =?UTF-8?q?=E8=BD=AC=E6=8D=A2=E4=BF=AE=E6=94=B9=EF=BC=8C=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E4=B8=8D=E5=BF=85=E8=A6=81=E7=9A=84futureBuilder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/video_card_h.dart | 5 +- lib/common/widgets/video_card_v.dart | 12 +- lib/http/video.dart | 16 ++- lib/main.dart | 2 + lib/models/home/rcmd/result.dart | 20 ++- lib/models/home/rcmd/result.g.dart | 2 +- lib/models/model_rec_video_item.dart | 11 +- lib/models/model_rec_video_item.g.dart | 4 +- lib/pages/rcmd/controller.dart | 19 ++- lib/pages/rcmd/view.dart | 24 ++-- lib/pages/setting/recommend_setting.dart | 151 ++++++++++++++++++++--- lib/utils/recommend_filter.dart | 52 ++++++++ lib/utils/storage.dart | 5 + lib/utils/utils.dart | 11 +- 14 files changed, 274 insertions(+), 60 deletions(-) create mode 100644 lib/utils/recommend_filter.dart diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index b00f1759..5d39bc65 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -324,8 +324,9 @@ class VideoContent extends StatelessWidget { reSrc: 11, ); SmartDialog.dismiss(); - SmartDialog.showToast( - res['msg'] ?? '成功'); + SmartDialog.showToast(res['code'] == 0 + ? '成功' + : res['msg']); }, child: const Text('确认'), ) diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index c5577af3..dfc7eb94 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -158,12 +158,12 @@ class VideoCardV extends StatelessWidget { height: maxHeight, ), ), - if (videoItem.duration != null) + if (videoItem.duration > 0) if (crossAxisCount == 1) ...[ PBadge( bottom: 10, right: 10, - text: videoItem.duration, + text: Utils.timeFormat(videoItem.duration), ) ] else ...[ PBadge( @@ -171,7 +171,7 @@ class VideoCardV extends StatelessWidget { right: 7, size: 'small', type: 'gray', - text: videoItem.duration, + text: Utils.timeFormat(videoItem.duration), ) ], ], @@ -330,10 +330,8 @@ class VideoStat extends StatelessWidget { color: Theme.of(context).colorScheme.outline, ), children: [ - if (videoItem.stat.view != '-') - TextSpan(text: '${videoItem.stat.view}观看'), - if (videoItem.stat.danmu != '-') - TextSpan(text: ' • ${videoItem.stat.danmu}弹幕'), + TextSpan(text: '${Utils.numFormat(videoItem.stat.view)}观看'), + TextSpan(text: ' • ${Utils.numFormat(videoItem.stat.danmu)}弹幕'), ], ), ); diff --git a/lib/http/video.dart b/lib/http/video.dart index 39c961cd..a48dd11b 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -9,6 +9,7 @@ import '../models/user/fav_folder.dart'; import '../models/video/ai.dart'; import '../models/video/play/url.dart'; import '../models/video_detail_res.dart'; +import '../utils/recommend_filter.dart'; import '../utils/storage.dart'; import '../utils/wbi_sign.dart'; import 'api.dart'; @@ -49,7 +50,10 @@ class VideoHttp { if (i['goto'] == 'av' && (i['owner'] != null && !blackMidsList.contains(i['owner']['mid']))) { - list.add(RecVideoItemModel.fromJson(i)); + RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i); + if (!RecommendFilter.filter(videoItem)){ + list.add(videoItem); + } } } return {'status': true, 'data': list}; @@ -93,7 +97,10 @@ class VideoHttp { (!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) && (i['args'] != null && !blackMidsList.contains(i['args']['up_mid']))) { - list.add(RecVideoItemAppModel.fromJson(i)); + RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i); + if (!RecommendFilter.filter(videoItem)){ + list.add(videoItem); + } } } return {'status': true, 'data': list}; @@ -209,7 +216,10 @@ class VideoHttp { if (res.data['code'] == 0) { List list = []; for (var i in res.data['data']) { - list.add(HotVideoItemModel.fromJson(i)); + HotVideoItemModel videoItem = HotVideoItemModel.fromJson(i); + if (!RecommendFilter.filter(videoItem, relatedVideos: true)){ + list.add(videoItem); + } } return {'status': true, 'data': list}; } else { diff --git a/lib/main.dart b/lib/main.dart index 4e1482d7..29b8d118 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,6 +21,7 @@ import 'package:pilipala/utils/app_scheme.dart'; import 'package:pilipala/utils/data.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc. +import 'package:pilipala/utils/recommend_filter.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -32,6 +33,7 @@ void main() async { await setupServiceLocator(); Request(); await Request.setCookie(); + RecommendFilter(); runApp(const MyApp()); // 小白条、导航栏沉浸 SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); diff --git a/lib/models/home/rcmd/result.dart b/lib/models/home/rcmd/result.dart index a2a8006d..4e810491 100644 --- a/lib/models/home/rcmd/result.dart +++ b/lib/models/home/rcmd/result.dart @@ -40,7 +40,7 @@ class RecVideoItemAppModel { @HiveField(5) RcmdStat? stat; @HiveField(6) - String? duration; + int? duration; @HiveField(7) String? title; @HiveField(8) @@ -79,13 +79,27 @@ class RecVideoItemAppModel { cid = json['player_args'] != null ? json['player_args']['cid'] : -1; pic = json['cover']; stat = RcmdStat.fromJson(json); - duration = json['cover_right_text']; + // 改用player_args中的duration作为原始数据(秒数) + duration = json['player_args'] != null + ? json['player_args']['duration'] + : -1; + //duration = json['cover_right_text']; title = json['title']; - isFollowed = 0; owner = RcmdOwner.fromJson(json); rcmdReason = json['rcmd_reason_style'] != null ? RcmdReason.fromJson(json['rcmd_reason_style']) : null; + // 由于app端api并不会直接返回与owner的关注状态 + // 所以借用推荐原因是否为“已关注”、“新关注”等判别关注状态,从而与web端接口等效 + isFollowed = rcmdReason != null && + rcmdReason!.content != null && + rcmdReason!.content!.contains('关注') + ? 1 + : 0; + // 如果是,就无需再显示推荐原因,交由view统一处理即可 + if (isFollowed == 1) { + rcmdReason = null; + } goto = json['goto']; param = int.parse(json['param']); uri = json['uri']; diff --git a/lib/models/home/rcmd/result.g.dart b/lib/models/home/rcmd/result.g.dart index 43bf4bcf..f46886e8 100644 --- a/lib/models/home/rcmd/result.g.dart +++ b/lib/models/home/rcmd/result.g.dart @@ -23,7 +23,7 @@ class RecVideoItemAppModelAdapter extends TypeAdapter { cid: fields[3] as int?, pic: fields[4] as String?, stat: fields[5] as RcmdStat?, - duration: fields[6] as String?, + duration: fields[6] as int?, title: fields[7] as String?, isFollowed: fields[8] as int?, owner: fields[9] as RcmdOwner?, diff --git a/lib/models/model_rec_video_item.dart b/lib/models/model_rec_video_item.dart index bd42fd82..1503f192 100644 --- a/lib/models/model_rec_video_item.dart +++ b/lib/models/model_rec_video_item.dart @@ -1,5 +1,3 @@ -import 'package:pilipala/utils/utils.dart'; - import './model_owner.dart'; import 'package:hive/hive.dart'; @@ -38,7 +36,7 @@ class RecVideoItemModel { @HiveField(6) String? title = ''; @HiveField(7) - String? duration = ''; + int? duration = -1; @HiveField(8) int? pubdate = -1; @HiveField(9) @@ -58,7 +56,7 @@ class RecVideoItemModel { uri = json["uri"]; pic = json["pic"]; title = json["title"]; - duration = Utils.tampToSeektime(json["duration"]); + duration = json["duration"]; pubdate = json["pubdate"]; owner = Owner.fromJson(json["owner"]); stat = Stat.fromJson(json["stat"]); @@ -77,14 +75,15 @@ class Stat { this.danmu, }); @HiveField(0) - String? view; + int? view; @HiveField(1) int? like; @HiveField(2) int? danmu; Stat.fromJson(Map json) { - view = Utils.numFormat(json["view"]); + // 无需在model中转换以保留原始数据,在view层处理即可 + view = json["view"]; like = json["like"]; danmu = json['danmaku']; } diff --git a/lib/models/model_rec_video_item.g.dart b/lib/models/model_rec_video_item.g.dart index 1de6ab03..dc614354 100644 --- a/lib/models/model_rec_video_item.g.dart +++ b/lib/models/model_rec_video_item.g.dart @@ -24,7 +24,7 @@ class RecVideoItemModelAdapter extends TypeAdapter { uri: fields[4] as String?, pic: fields[5] as String?, title: fields[6] as String?, - duration: fields[7] as String?, + duration: fields[7] as int?, pubdate: fields[8] as int?, owner: fields[9] as Owner?, stat: fields[10] as Stat?, @@ -87,7 +87,7 @@ class StatAdapter extends TypeAdapter { for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; return Stat( - view: fields[0] as String?, + view: fields[0] as int?, like: fields[1] as int?, danmu: fields[2] as int?, ); diff --git a/lib/pages/rcmd/controller.dart b/lib/pages/rcmd/controller.dart index ed98606a..54cb8c14 100644 --- a/lib/pages/rcmd/controller.dart +++ b/lib/pages/rcmd/controller.dart @@ -55,12 +55,13 @@ class RcmdController extends GetxController { } late final Map res; switch (defaultRcmdType) { - case 'app': case 'notLogin': - res = await VideoHttp.rcmdVideoListApp( - loginStatus: defaultRcmdType != 'notLogin', - freshIdx: _currentPage, - ); - break; + case 'app': + case 'notLogin': + res = await VideoHttp.rcmdVideoListApp( + loginStatus: defaultRcmdType != 'notLogin', + freshIdx: _currentPage, + ); + break; default: //'web' res = await VideoHttp.rcmdVideoList( freshIdx: _currentPage, @@ -83,10 +84,16 @@ class RcmdController extends GetxController { } else if (type == 'onLoad') { videoList.addAll(res['data']); } + // 目前仅支持app端系列保存缓存 if (defaultRcmdType != 'web') { recVideo.put('cacheList', res['data']); } _currentPage += 1; + // 若videoList数量太小,可能会影响翻页,此时再次请求 + // 为避免请求到的数据太少时还在反复请求,要求本次返回数据大于1条才触发 + if (res['data'].length > 1 && videoList.length < 10){ + queryRcmdFeed('onLoad'); + } } else { Get.snackbar('提示', res['msg']); } diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 0a345511..687eacd1 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; @@ -8,7 +7,7 @@ import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/skeleton/video_card_v.dart'; import 'package:pilipala/common/widgets/animated_dialog.dart'; -import 'package:pilipala/common/widgets/http_error.dart'; +// import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/overlay_pop.dart'; import 'package:pilipala/common/widgets/video_card_v.dart'; import 'package:pilipala/pages/home/index.dart'; @@ -26,7 +25,6 @@ class RcmdPage extends StatefulWidget { class _RcmdPageState extends State with AutomaticKeepAliveClientMixin { final RcmdController _rcmdController = Get.put(RcmdController()); - late Future _futureBuilderFuture; @override bool get wantKeepAlive => true; @@ -34,7 +32,7 @@ class _RcmdPageState extends State @override void initState() { super.initState(); - _futureBuilderFuture = _rcmdController.queryRcmdFeed('init'); + _rcmdController.queryRcmdFeed('init'); ScrollController scrollController = _rcmdController.scrollController; StreamController mainStream = Get.find().bottomBarStream; @@ -90,21 +88,21 @@ class _RcmdPageState extends State slivers: [ SliverPadding( padding: - const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0), - sliver: Obx(() { - // 使用Obx来监听数据的变化 - if (_rcmdController.isLoadingMore) { - // 如果正在加载,则显示骨架屏 + const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0), + sliver: Obx(() { // 使用Obx来监听数据的变化 + if (_rcmdController.isLoadingMore && _rcmdController.videoList.isEmpty) { return contentGrid(_rcmdController, []); + // 如果正在加载并且列表为空,则显示加载指示器 + // return const SliverToBoxAdapter( + // child: Center(child: CircularProgressIndicator()), + // ); } else { // 显示视频列表 - return contentGrid( - _rcmdController, - _rcmdController.videoList); + return contentGrid(_rcmdController, _rcmdController.videoList); } }), ), - LoadingMore(ctr: _rcmdController) + LoadingMore(ctr: _rcmdController), ], ), ), diff --git a/lib/pages/setting/recommend_setting.dart b/lib/pages/setting/recommend_setting.dart index 8e049bc4..ab8ec063 100644 --- a/lib/pages/setting/recommend_setting.dart +++ b/lib/pages/setting/recommend_setting.dart @@ -4,6 +4,7 @@ import 'package:hive/hive.dart'; import 'package:pilipala/http/member.dart'; import 'package:pilipala/models/common/rcmd_type.dart'; import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; +import 'package:pilipala/utils/recommend_filter.dart'; import 'package:pilipala/utils/storage.dart'; import 'widgets/switch_item.dart'; @@ -23,6 +24,9 @@ class _RecommendSettingState extends State { late dynamic userInfo; bool userLogin = false; late dynamic accessKeyInfo; + // late int filterUnfollowedRatio; + late int minDurationForRcmd; + late int minLikeRatioForRecommend; @override void initState() { @@ -33,6 +37,12 @@ class _RecommendSettingState extends State { userInfo = userInfoCache.get('userInfoCache'); userLogin = userInfo != null; accessKeyInfo = localCache.get(LocalCacheKey.accessKey, defaultValue: null); + // filterUnfollowedRatio = setting + // .get(SettingBoxKey.filterUnfollowedRatio, defaultValue: 0); + minDurationForRcmd = + setting.get(SettingBoxKey.minDurationForRcmd, defaultValue: 0); + minLikeRatioForRecommend = + setting.get(SettingBoxKey.minLikeRatioForRecommend, defaultValue: 0); } @override @@ -53,23 +63,11 @@ class _RecommendSettingState extends State { ), body: ListView( children: [ - const SetSwitchItem( - title: '推荐动态', - subTitle: '是否在推荐内容中展示动态', - setKey: SettingBoxKey.enableRcmdDynamic, - defaultVal: true, - ), - const SetSwitchItem( - title: '首页推荐刷新', - subTitle: '下拉刷新时保留上次内容', - setKey: SettingBoxKey.enableSaveLastData, - defaultVal: false, - ), ListTile( dense: false, title: Text('首页推荐类型', style: titleStyle), subtitle: Text( - '当前使用「$defaultRcmdType端」推荐', + '当前使用「$defaultRcmdType端」推荐¹', style: subTitleStyle, ), onTap: () async { @@ -100,7 +98,7 @@ class _RecommendSettingState extends State { return AlertDialog( title: const Text('提示'), content: const Text( - '使用app端推荐需获取access_key,有小概率触发风控导致账号退出(在官方app重新登录即可解除),是否继续?'), + '使用app端推荐需获取access_key,有小概率触发风控导致账号退出(在官方版本app重新登录即可解除),是否继续?'), actions: [ TextButton( onPressed: () { @@ -130,6 +128,131 @@ class _RecommendSettingState extends State { } }, ), + const SetSwitchItem( + title: '推荐动态', + subTitle: '是否在推荐内容中展示动态(仅app端)', + setKey: SettingBoxKey.enableRcmdDynamic, + defaultVal: true, + ), + const SetSwitchItem( + title: '首页推荐刷新', + subTitle: '下拉刷新时保留上次内容', + setKey: SettingBoxKey.enableSaveLastData, + defaultVal: false, + ), + // 分割线 + const Divider(height: 1), + ListTile( + dense: false, + title: Text('点赞率过滤', style: titleStyle), + subtitle: Text( + '过滤掉点赞数/播放量「小于$minLikeRatioForRecommend%」的推荐视频(仅web端)', + style: subTitleStyle, + ), + onTap: () async { + int? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '选择点赞率(0即不过滤)', + value: minLikeRatioForRecommend, + values: [0, 1, 2, 3, 4].map((e) { + return {'title': '$e %', 'value': e}; + }).toList()); + }, + ); + if (result != null) { + minLikeRatioForRecommend = result; + setting.put(SettingBoxKey.minLikeRatioForRecommend, result); + RecommendFilter.update(); + setState(() {}); + } + }, + ), + ListTile( + dense: false, + title: Text('视频时长过滤', style: titleStyle), + subtitle: Text( + '过滤掉时长「小于$minDurationForRcmd秒」的推荐视频', + style: subTitleStyle, + ), + onTap: () async { + int? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: '选择时长(0即不过滤)', + value: minDurationForRcmd, + values: [0, 30, 60, 90, 120].map((e) { + return {'title': '$e 秒', 'value': e}; + }).toList()); + }, + ); + if (result != null) { + minDurationForRcmd = result; + setting.put(SettingBoxKey.minDurationForRcmd, result); + RecommendFilter.update(); + setState(() {}); + } + }, + ), + SetSwitchItem( + title: '已关注Up豁免推荐过滤', + subTitle: '推荐中已关注用户发布的内容不会被过滤', + setKey: SettingBoxKey.exemptFilterForFollowed, + defaultVal: true, + callFn: (_) => {RecommendFilter.update}, + ), + // ListTile( + // dense: false, + // title: Text('按比例过滤未关注Up', style: titleStyle), + // subtitle: Text( + // '滤除推荐中占比「$filterUnfollowedRatio%」的未关注用户发布的内容', + // style: subTitleStyle, + // ), + // onTap: () async { + // int? result = await showDialog( + // context: context, + // builder: (context) { + // return SelectDialog( + // title: '选择滤除比例(0即不过滤)', + // value: filterUnfollowedRatio, + // values: [0, 16, 32, 48, 64].map((e) { + // return {'title': '$e %', 'value': e}; + // }).toList()); + // }, + // ); + // if (result != null) { + // filterUnfollowedRatio = result; + // setting.put( + // SettingBoxKey.filterUnfollowedRatio, result); + // RecommendFilter.update(); + // setState(() {}); + // } + // }, + // ), + SetSwitchItem( + title: '过滤器也应用于相关视频', + subTitle: '视频详情页的相关视频也进行过滤²', + setKey: SettingBoxKey.applyFilterToRelatedVideos, + defaultVal: true, + callFn: (_) => {RecommendFilter.update}, + ), + ListTile( + dense: true, + subtitle: Text( + '¹ 若默认web端推荐不太符合预期,可尝试切换至app端。\n' + '¹ 选择“模拟未登录(notLogin)”,将以空的key请求推荐接口,但播放页仍会携带用户信息,保证账号能正常记录进度、点赞投币等。\n\n' + '² 由于接口未提供关注信息,无法豁免相关视频中的已关注Up。\n\n' + '* 其它(如热门视频、手动搜索、链接跳转等)均不受过滤器影响。\n' + '* 设定较严苛的条件可导致推荐项数锐减或多次请求,请酌情选择。\n' + '* 后续可能会增加更多过滤条件,敬请期待。', + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith(color: Theme.of(context).colorScheme.outline.withOpacity(0.7)), + ), + ) ], ), ); diff --git a/lib/utils/recommend_filter.dart b/lib/utils/recommend_filter.dart new file mode 100644 index 00000000..113e2261 --- /dev/null +++ b/lib/utils/recommend_filter.dart @@ -0,0 +1,52 @@ +import 'dart:math'; + +import 'storage.dart'; + +class RecommendFilter { + // static late int filterUnfollowedRatio; + static late int minDurationForRcmd; + static late int minLikeRatioForRecommend; + static late bool exemptFilterForFollowed; + static late bool applyFilterToRelatedVideos; + RecommendFilter() { + update(); + } + + static void update() { + var setting = GStrorage.setting; + // filterUnfollowedRatio = + // setting.get(SettingBoxKey.filterUnfollowedRatio, defaultValue: 0); + minDurationForRcmd = + setting.get(SettingBoxKey.minDurationForRcmd, defaultValue: 0); + minLikeRatioForRecommend = + setting.get(SettingBoxKey.minLikeRatioForRecommend, defaultValue: 0); + exemptFilterForFollowed = + setting.get(SettingBoxKey.exemptFilterForFollowed, defaultValue: true); + applyFilterToRelatedVideos = setting + .get(SettingBoxKey.applyFilterToRelatedVideos, defaultValue: true); + } + + static bool filter(dynamic videoItem, {bool relatedVideos = false}) { + if (relatedVideos && !applyFilterToRelatedVideos) { + return false; + } + //由于相关视频中没有已关注标签,只能视为非关注视频 + if (!relatedVideos && + videoItem.isFollowed == 1 && + exemptFilterForFollowed) { + return false; + } + if (videoItem.duration > 0 && videoItem.duration < minDurationForRcmd) { + return true; + } + if (videoItem.stat.view is int && + videoItem.stat.view > -1 && + videoItem.stat.like is int && + videoItem.stat.like > -1 && + videoItem.stat.like * 100 < + minLikeRatioForRecommend * videoItem.stat.view) { + return true; + } + return false; + } +} diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index c412606f..fdb9ee02 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -124,6 +124,11 @@ class SettingBoxKey { enableRcmdDynamic = 'enableRcmdDynamic', defaultRcmdType = 'defaultRcmdType', enableSaveLastData = 'enableSaveLastData', + minDurationForRcmd = 'minDurationForRcmd', + minLikeRatioForRecommend = 'minLikeRatioForRecommend', + exemptFilterForFollowed = 'exemptFilterForFollowed', + //filterUnfollowedRatio = 'filterUnfollowedRatio', + applyFilterToRelatedVideos = 'applyFilterToRelatedVideos', /// 其他 autoUpdate = 'autoUpdate', diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 08693d24..f0b56fc4 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -9,7 +9,6 @@ import 'package:crypto/crypto.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:get/get_utils/get_utils.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -28,10 +27,16 @@ class Utils { return tempPath; } - static String numFormat(int number) { + static String numFormat(dynamic number) { + if (number == null){ + return '0'; + } + if (number is String) { + return number; + } final String res = (number / 10000).toString(); if (int.parse(res.split('.')[0]) >= 1) { - return '${(number / 10000).toPrecision(1)}万'; + return '${(number / 10000).toStringAsFixed(1)}万'; } else { return number.toString(); } From a560d66567891afc376a03acf95013fcc8fa8d40 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 4 Feb 2024 23:03:24 +0800 Subject: [PATCH 3/4] mod: rcmd FutureBuilder --- lib/models/common/rcmd_type.dart | 2 +- lib/pages/rcmd/controller.dart | 7 +++-- lib/pages/rcmd/view.dart | 53 +++++++++++++++++++++++--------- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/lib/models/common/rcmd_type.dart b/lib/models/common/rcmd_type.dart index 5af18d77..2dfdad1c 100644 --- a/lib/models/common/rcmd_type.dart +++ b/lib/models/common/rcmd_type.dart @@ -3,5 +3,5 @@ enum RcmdType { web, app, notLogin } extension RcmdTypeExtension on RcmdType { String get values => ['web', 'app', 'notLogin'][index]; - String get labels => ['web端', 'app端', '模拟未登录'][index]; + String get labels => ['web端', 'app端', '游客模式'][index]; } diff --git a/lib/pages/rcmd/controller.dart b/lib/pages/rcmd/controller.dart index 6ecc9d63..b7e58f44 100644 --- a/lib/pages/rcmd/controller.dart +++ b/lib/pages/rcmd/controller.dart @@ -28,7 +28,7 @@ class RcmdController extends GetxController { setting.get(SettingBoxKey.enableSaveLastData, defaultValue: false); defaultRcmdType = setting.get(SettingBoxKey.defaultRcmdType, defaultValue: 'web'); - if (defaultRcmdType == 'web'){ + if (defaultRcmdType == 'web') { videoList = [].obs; } else { videoList = [].obs; @@ -43,7 +43,7 @@ class RcmdController extends GetxController { if (type == 'onRefresh') { _currentPage = 0; } - late final Map res; + late final Map res; switch (defaultRcmdType) { case 'app': case 'notLogin': @@ -77,13 +77,14 @@ class RcmdController extends GetxController { _currentPage += 1; // 若videoList数量太小,可能会影响翻页,此时再次请求 // 为避免请求到的数据太少时还在反复请求,要求本次返回数据大于1条才触发 - if (res['data'].length > 1 && videoList.length < 10){ + if (res['data'].length > 1 && videoList.length < 10) { queryRcmdFeed('onLoad'); } } else { Get.snackbar('提示', res['msg']); } isLoadingMore = false; + return res; } // 下拉刷新 diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 687eacd1..42b66364 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -7,7 +7,7 @@ import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/skeleton/video_card_v.dart'; import 'package:pilipala/common/widgets/animated_dialog.dart'; -// import 'package:pilipala/common/widgets/http_error.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/overlay_pop.dart'; import 'package:pilipala/common/widgets/video_card_v.dart'; import 'package:pilipala/pages/home/index.dart'; @@ -25,6 +25,7 @@ class RcmdPage extends StatefulWidget { class _RcmdPageState extends State with AutomaticKeepAliveClientMixin { final RcmdController _rcmdController = Get.put(RcmdController()); + late Future _futureBuilderFuture; @override bool get wantKeepAlive => true; @@ -32,7 +33,7 @@ class _RcmdPageState extends State @override void initState() { super.initState(); - _rcmdController.queryRcmdFeed('init'); + _futureBuilderFuture = _rcmdController.queryRcmdFeed('init'); ScrollController scrollController = _rcmdController.scrollController; StreamController mainStream = Get.find().bottomBarStream; @@ -88,19 +89,41 @@ class _RcmdPageState extends State slivers: [ SliverPadding( padding: - const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0), - sliver: Obx(() { // 使用Obx来监听数据的变化 - if (_rcmdController.isLoadingMore && _rcmdController.videoList.isEmpty) { - return contentGrid(_rcmdController, []); - // 如果正在加载并且列表为空,则显示加载指示器 - // return const SliverToBoxAdapter( - // child: Center(child: CircularProgressIndicator()), - // ); - } else { - // 显示视频列表 - return contentGrid(_rcmdController, _rcmdController.videoList); - } - }), + const EdgeInsets.fromLTRB(0, StyleString.safeSpace, 0, 0), + sliver: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + return Obx( + () { + if (_rcmdController.isLoadingMore && + _rcmdController.videoList.isEmpty) { + return contentGrid(_rcmdController, []); + } else { + // 显示视频列表 + return contentGrid( + _rcmdController, _rcmdController.videoList); + } + }, + ); + } else { + return HttpError( + errMsg: data['msg'], + fn: () { + setState(() { + _futureBuilderFuture = + _rcmdController.queryRcmdFeed('init'); + }); + }, + ); + } + } else { + return contentGrid(_rcmdController, []); + } + }, + ), ), LoadingMore(ctr: _rcmdController), ], From 9e471b83d992755d23764c7e24228949c9b75cd4 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 5 Feb 2024 00:19:03 +0800 Subject: [PATCH 4/4] mod: cancel Get.snackbar --- lib/pages/rcmd/controller.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pages/rcmd/controller.dart b/lib/pages/rcmd/controller.dart index b7e58f44..28ff055b 100644 --- a/lib/pages/rcmd/controller.dart +++ b/lib/pages/rcmd/controller.dart @@ -80,8 +80,6 @@ class RcmdController extends GetxController { if (res['data'].length > 1 && videoList.length < 10) { queryRcmdFeed('onLoad'); } - } else { - Get.snackbar('提示', res['msg']); } isLoadingMore = false; return res;