From bb6aaaa480f11cf810cb43b6c3148c69f658ca7e Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Sun, 8 Sep 2024 16:14:57 +0800 Subject: [PATCH] refactor: reply --- lib/http/reply.dart | 40 +-- .../bangumi/introduction/controller.dart | 2 +- lib/pages/bangumi/view.dart | 9 +- lib/pages/common/common_controller.dart | 33 +- lib/pages/common/reply_controller.dart | 108 +++++++ lib/pages/dynamics/detail/controller.dart | 106 +----- lib/pages/dynamics/detail/view.dart | 172 +++++----- lib/pages/hot/view.dart | 7 +- lib/pages/html/controller.dart | 111 ++----- lib/pages/html/view.dart | 203 ++++++------ lib/pages/rank/zone/view.dart | 7 +- lib/pages/rcmd/controller.dart | 2 +- lib/pages/rcmd/view.dart | 7 +- .../video/detail/introduction/controller.dart | 2 +- lib/pages/video/detail/related/view.dart | 7 +- lib/pages/video/detail/reply/controller.dart | 134 +------- lib/pages/video/detail/reply/view.dart | 302 ++++++++---------- .../video/detail/reply_reply/controller.dart | 88 +++-- lib/pages/video/detail/reply_reply/view.dart | 230 ++++++------- lib/pages/video/detail/view.dart | 5 +- 20 files changed, 640 insertions(+), 935 deletions(-) create mode 100644 lib/pages/common/reply_controller.dart diff --git a/lib/http/reply.dart b/lib/http/reply.dart index e9eaabfc..4aa34eab 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -1,3 +1,5 @@ +import 'package:PiliPalaX/http/loading_state.dart'; +import 'package:PiliPalaX/utils/extension.dart'; import 'package:dio/dio.dart'; import '../models/video/reply/data.dart'; @@ -8,7 +10,7 @@ import 'init.dart'; class ReplyHttp { static final _dio = Dio(); - static Future replyList({ + static Future replyList({ required int oid, required String nextOffset, required int type, @@ -23,27 +25,16 @@ class ReplyHttp { 'mode': sort + 2, //2:按时间排序;3:按热度排序 }); if (res.data['code'] == 0) { - return { - 'status': true, - 'data': ReplyData.fromJson(res.data['data']), - }; + return LoadingState.success(ReplyData.fromJson(res.data['data'])); } else { - return { - 'status': false, - 'date': [], - 'msg': res.data['message'], - }; + return LoadingState.error(res.data['message']); } } catch (e) { - return { - 'status': false, - 'date': [], - 'msg': e.toString(), - }; + return LoadingState.error(e.toString()); } } - static Future replyReplyList({ + static Future replyReplyList({ required int oid, required String root, required int pageNum, @@ -61,23 +52,12 @@ class ReplyHttp { 'csrf': await Request.getCsrf(), }); if (res.data['code'] == 0) { - return { - 'status': true, - 'data': ReplyReplyData.fromJson(res.data['data']), - }; + return LoadingState.success(ReplyReplyData.fromJson(res.data['data'])); } else { - return { - 'status': false, - 'date': [], - 'msg': res.data['message'], - }; + return LoadingState.error(res.data['message']); } } catch (e) { - return { - 'status': false, - 'date': [], - 'msg': e.toString(), - }; + return LoadingState.error(e.toString()); } } diff --git a/lib/pages/bangumi/introduction/controller.dart b/lib/pages/bangumi/introduction/controller.dart index a76de744..ec305214 100644 --- a/lib/pages/bangumi/introduction/controller.dart +++ b/lib/pages/bangumi/introduction/controller.dart @@ -294,7 +294,7 @@ class BangumiIntroController extends GetxController { VideoReplyController videoReplyCtr = Get.find(tag: Get.arguments['heroTag']); videoReplyCtr.aid = aid; - videoReplyCtr.queryReplyList(type: 'init'); + videoReplyCtr.queryData(); } catch (_) {} } diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart index 418c8619..e44a1ab1 100644 --- a/lib/pages/bangumi/view.dart +++ b/lib/pages/bangumi/view.dart @@ -70,7 +70,7 @@ class _BangumiPageState extends State super.build(context); return RefreshIndicator( onRefresh: () async { - await _bangumiController.queryData(); + await _bangumiController.onRefresh(); await _bangumiController.queryBangumiFollow(); }, child: CustomScrollView( @@ -154,11 +154,8 @@ class _BangumiPageState extends State ? (_bangumiController.loadingState.value as Error) .errMsg : '没有相关数据', - fn: () { - _bangumiController.loadingState.value = - LoadingState.loading(); - _bangumiController.onRefresh(); - }), + fn: _bangumiController.onReload, + ), ), ), ], diff --git a/lib/pages/common/common_controller.dart b/lib/pages/common/common_controller.dart index 5cc4a087..3ecd2a44 100644 --- a/lib/pages/common/common_controller.dart +++ b/lib/pages/common/common_controller.dart @@ -12,10 +12,14 @@ abstract class CommonController extends GetxController { Future customGetData(); - List? handleResponse(List currentList, List dataList) { + List? handleListResponse(List currentList, List dataList) { return null; } + bool customHandleResponse(Success response) { + return false; + } + void handleSuccess(List currentList, List dataList) {} Future queryData([bool isRefresh = true]) async { @@ -23,17 +27,19 @@ abstract class CommonController extends GetxController { isLoading = true; LoadingState response = await customGetData(); if (response is Success) { + if (!customHandleResponse(response)) { + List currentList = loadingState.value is Success + ? (loadingState.value as Success).response + : []; + List? handleList = handleListResponse(currentList, response.response); + loadingState.value = isRefresh + ? handleList != null + ? LoadingState.success(handleList) + : response + : LoadingState.success(currentList + response.response); + handleSuccess(currentList, response.response); + } currentPage++; - List currentList = loadingState.value is Success - ? (loadingState.value as Success).response - : []; - List? handleList = handleResponse(currentList, response.response); - loadingState.value = isRefresh - ? handleList != null - ? LoadingState.success(handleList) - : response - : LoadingState.success(currentList + response.response); - handleSuccess(currentList, response.response); } else { if (isRefresh) { loadingState.value = response; @@ -55,6 +61,11 @@ abstract class CommonController extends GetxController { scrollController.animToTop(); } + void onReload() { + loadingState.value = LoadingState.loading(); + queryData(); + } + @override void onClose() { scrollController.dispose(); diff --git a/lib/pages/common/reply_controller.dart b/lib/pages/common/reply_controller.dart new file mode 100644 index 00000000..40b165f5 --- /dev/null +++ b/lib/pages/common/reply_controller.dart @@ -0,0 +1,108 @@ +import 'package:PiliPalaX/http/loading_state.dart'; +import 'package:PiliPalaX/pages/common/common_controller.dart'; +import 'package:easy_debounce/easy_throttle.dart'; +import 'package:get/get.dart'; +import 'package:PiliPalaX/models/common/reply_sort_type.dart'; +import 'package:PiliPalaX/models/video/reply/item.dart'; +import 'package:PiliPalaX/utils/feed_back.dart'; +import 'package:PiliPalaX/utils/storage.dart'; + +abstract class ReplyController extends CommonController { + String nextOffset = ""; + bool isLoadingMore = false; + RxString noMore = ''.obs; + RxInt count = (-1).obs; + // 当前回复的回复 + ReplyItemModel? currentReplyItem; + + ReplySortType sortType = ReplySortType.time; + RxString sortTypeTitle = ReplySortType.time.titles.obs; + RxString sortTypeLabel = ReplySortType.time.labels.obs; + + @override + void onInit() { + super.onInit(); + int defaultReplySortIndex = GStorage.setting + .get(SettingBoxKey.replySortType, defaultValue: 1) as int; + if (defaultReplySortIndex == 2) { + GStorage.setting.put(SettingBoxKey.replySortType, 0); + defaultReplySortIndex = 0; + } + sortType = ReplySortType.values[defaultReplySortIndex]; + sortTypeTitle.value = sortType.titles; + sortTypeLabel.value = sortType.labels; + } + + @override + Future onRefresh() { + nextOffset = ''; + noMore.value = ''; + return super.onRefresh(); + } + + @override + Future queryData([bool isRefresh = true]) { + if (noMore.value == '没有更多了') return Future.value(); + return super.queryData(isRefresh); + } + + @override + bool customHandleResponse(Success response) { + List replies = response.response.replies; + nextOffset = response.response.cursor.paginationReply.nextOffset ?? ""; + if (replies.isNotEmpty) { + noMore.value = '加载中...'; + + /// 第一页回复数小于20 + if (currentPage == 1 && replies.length < 18) { + noMore.value = '没有更多了'; + } + } else { + // 未登录状态replies可能返回null + noMore.value = nextOffset == "" && currentPage == 1 ? '还没有评论' : '没有更多了'; + } + if (currentPage == 1) { + // 添加置顶回复 + if (response.response.upper.top != null) { + final bool flag = response.response.topReplies.any( + (ReplyItemModel reply) => + reply.rpid == response.response.upper.top.rpid) as bool; + if (!flag) { + replies.insert(0, response.response.upper.top); + } + } + replies.insertAll(0, response.response.topReplies); + count.value = response.response.cursor.allCount ?? 0; + } else { + replies.insertAll( + 0, + loadingState.value is Success + ? (loadingState.value as Success).response + : []); + } + loadingState.value = LoadingState.success(replies); + return true; + } + + // 排序搜索评论 + queryBySort() { + EasyThrottle.throttle('queryBySort', const Duration(seconds: 1), () { + feedBack(); + switch (sortType) { + case ReplySortType.time: + sortType = ReplySortType.like; + break; + case ReplySortType.like: + sortType = ReplySortType.time; + break; + default: + } + sortTypeTitle.value = sortType.titles; + sortTypeLabel.value = sortType.labels; + nextOffset = ""; + noMore.value = ''; + loadingState.value = LoadingState.loading(); + onRefresh(); + }); + } +} diff --git a/lib/pages/dynamics/detail/controller.dart b/lib/pages/dynamics/detail/controller.dart index 68cec698..9b492989 100644 --- a/lib/pages/dynamics/detail/controller.dart +++ b/lib/pages/dynamics/detail/controller.dart @@ -1,30 +1,15 @@ -import 'package:flutter/material.dart'; +import 'package:PiliPalaX/http/loading_state.dart'; +import 'package:PiliPalaX/pages/common/reply_controller.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; import 'package:PiliPalaX/http/html.dart'; import 'package:PiliPalaX/http/reply.dart'; -import 'package:PiliPalaX/models/common/reply_sort_type.dart'; -import 'package:PiliPalaX/models/video/reply/item.dart'; -import 'package:PiliPalaX/utils/feed_back.dart'; -import 'package:PiliPalaX/utils/storage.dart'; -class DynamicDetailController extends GetxController { +class DynamicDetailController extends ReplyController { DynamicDetailController(this.oid, this.type); int? oid; int? type; dynamic item; int? floor; - String nextOffset = ""; - bool isLoadingMore = false; - RxString noMore = ''.obs; - RxList replyList = [].obs; - RxInt acount = 0.obs; - final ScrollController scrollController = ScrollController(); - - ReplySortType _sortType = ReplySortType.time; - RxString sortTypeTitle = ReplySortType.time.titles.obs; - RxString sortTypeLabel = ReplySortType.time.labels.obs; - Box setting = GStorage.setting; @override void onInit() { @@ -32,83 +17,10 @@ class DynamicDetailController extends GetxController { item = Get.arguments['item']; floor = Get.arguments['floor']; if (floor == 1) { - acount.value = - int.parse(item!.modules!.moduleStat!.comment!.count ?? '0'); + count.value = int.parse(item!.modules!.moduleStat!.comment!.count ?? '0'); } - int defaultReplySortIndex = - setting.get(SettingBoxKey.replySortType, defaultValue: 1); - if (defaultReplySortIndex == 2) { - setting.put(SettingBoxKey.replySortType, 0); - defaultReplySortIndex = 0; - } - _sortType = ReplySortType.values[defaultReplySortIndex]; - sortTypeTitle.value = _sortType.titles; - sortTypeLabel.value = _sortType.labels; - } - Future queryReplyList({reqType = 'init'}) async { - if (reqType == 'init') { - nextOffset = ""; - noMore.value = ""; - } - if (isLoadingMore) return; - if (noMore.value == '没有更多了') return; - isLoadingMore = true; - var res = await ReplyHttp.replyList( - oid: oid!, - nextOffset: nextOffset, - type: type!, - sort: _sortType.index, - ); - isLoadingMore = false; - if (res['status']) { - List replies = res['data'].replies; - acount.value = res['data'].cursor.allCount ?? 0; - nextOffset = res['data'].cursor.paginationReply.nextOffset ?? ""; - if (replies.isNotEmpty) { - noMore.value = '加载中...'; - if (res['data'].cursor.isEnd == true) { - noMore.value = '没有更多了'; - } - } else { - noMore.value = - nextOffset == "" && reqType == 'init' ? '还没有评论' : '没有更多了'; - } - if (reqType == 'init') { - // 添加置顶回复 - if (res['data'].upper.top != null) { - bool flag = res['data'] - .topReplies - .any((reply) => reply.rpid == res['data'].upper.top.rpid); - if (!flag) { - replies.insert(0, res['data'].upper.top); - } - } - replies.insertAll(0, res['data'].topReplies); - replyList.value = replies; - } else { - replyList.addAll(replies); - } - } - return res; - } - - // 排序搜索评论 - queryBySort() { - feedBack(); - switch (_sortType) { - case ReplySortType.time: - _sortType = ReplySortType.like; - break; - case ReplySortType.like: - _sortType = ReplySortType.time; - break; - default: - } - sortTypeTitle.value = _sortType.titles; - sortTypeLabel.value = _sortType.labels; - replyList.clear(); - queryReplyList(reqType: 'init'); + queryData(); } // 根据jumpUrl获取动态html @@ -116,4 +28,12 @@ class DynamicDetailController extends GetxController { var res = await HtmlHttp.reqHtml(id, 'opus'); oid = res['commentId']; } + + @override + Future customGetData() => ReplyHttp.replyList( + oid: oid!, + nextOffset: nextOffset, + type: type!, + sort: sortType.index, + ); } diff --git a/lib/pages/dynamics/detail/view.dart b/lib/pages/dynamics/detail/view.dart index 793517ec..74556bae 100644 --- a/lib/pages/dynamics/detail/view.dart +++ b/lib/pages/dynamics/detail/view.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math'; +import 'package:PiliPalaX/http/loading_state.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -32,7 +33,6 @@ class _DynamicDetailPageState extends State with TickerProviderStateMixin { late DynamicDetailController _dynamicDetailController; late AnimationController fabAnimationCtr; - Future? _futureBuilderFuture; late StreamController titleStreamC; // appBar title late ScrollController scrollController; bool _visibleTitle = false; @@ -103,8 +103,6 @@ class _DynamicDetailPageState extends State _dynamicDetailController = Get.put(DynamicDetailController(oid, replyType), tag: oid.toString()); } - _futureBuilderFuture = - _dynamicDetailController.queryReplyList(reqType: 'init'); } // 查看二级评论 @@ -141,7 +139,7 @@ class _DynamicDetailPageState extends State if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 300) { EasyThrottle.throttle('replylist', const Duration(seconds: 2), () { - _dynamicDetailController.queryReplyList(reqType: 'onLoad'); + _dynamicDetailController.onLoadMore(); }); } @@ -185,7 +183,6 @@ class _DynamicDetailPageState extends State titleStreamC.close(); fabAnimationCtr.dispose(); scrollController.removeListener(() {}); - scrollController.dispose(); super.dispose(); } @@ -212,7 +209,7 @@ class _DynamicDetailPageState extends State ), body: RefreshIndicator( onRefresh: () async { - await _dynamicDetailController.queryReplyList(reqType: 'init'); + await _dynamicDetailController.onRefresh(); }, child: Stack( children: [ @@ -231,7 +228,10 @@ class _DynamicDetailPageState extends State ), ), replyPersistentHeader(context), - replyList(), + Obx( + () => replyList( + _dynamicDetailController.loadingState.value), + ), ] .map((e) => SliverPadding( padding: EdgeInsets.symmetric(horizontal: padding), @@ -265,8 +265,12 @@ class _DynamicDetailPageState extends State padding: EdgeInsets.only(right: padding / 2), sliver: replyPersistentHeader(context)), SliverPadding( - padding: EdgeInsets.only(right: padding / 2), - sliver: replyList()), + padding: EdgeInsets.only(right: padding / 2), + sliver: Obx( + () => replyList(_dynamicDetailController + .loadingState.value), + ), + ), ] // .map( // (e) => SliverPadding(padding: padding, sliver: e)) @@ -306,14 +310,22 @@ class _DynamicDetailPageState extends State ); }, ).then( - (value) => { + (value) { // 完成评论,数据添加 - if (value != null && value['data'] != null) - { - _dynamicDetailController.replyList - .add(value['data']), - _dynamicDetailController.acount.value++ + if (value != null && value['data'] != null) { + _dynamicDetailController.count.value++; + if (value != null && value['data'] != null) { + List list = _dynamicDetailController + .loadingState.value is Success + ? (_dynamicDetailController.loadingState.value + as Success) + .response + : []; + list.insert(0, value['data']); + _dynamicDetailController.loadingState.value = + LoadingState.success(list); } + } }, ); }, @@ -353,8 +365,8 @@ class _DynamicDetailPageState extends State return ScaleTransition(scale: animation, child: child); }, child: Text( - '${_dynamicDetailController.acount.value}条回复', - key: ValueKey(_dynamicDetailController.acount.value), + '${_dynamicDetailController.count.value}条回复', + key: ValueKey(_dynamicDetailController.count.value), ), ), ), @@ -378,85 +390,57 @@ class _DynamicDetailPageState extends State ); } - FutureBuilder replyList() { - return FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - Map data = snapshot.data as Map; - if (snapshot.data['status']) { - // 请求成功 - return Obx( - () => _dynamicDetailController.replyList.isEmpty && - _dynamicDetailController.isLoadingMore - ? SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return const VideoReplySkeleton(); - }, childCount: 8), - ) - : SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - if (index == - _dynamicDetailController.replyList.length) { - return Container( - padding: EdgeInsets.only( - bottom: - MediaQuery.of(context).padding.bottom), - height: - MediaQuery.of(context).padding.bottom + 100, - child: Center( - child: Obx( - () => Text( - _dynamicDetailController.noMore.value, - style: TextStyle( - fontSize: 12, - color: - Theme.of(context).colorScheme.outline, - ), - ), - ), - ), - ); - } else { - return ReplyItem( - replyItem: - _dynamicDetailController.replyList[index], - showReplyRow: true, - replyLevel: '1', - replyReply: replyReply, - replyType: ReplyType.values[replyType], - addReply: (replyItem) { - _dynamicDetailController - .replyList[index].replies! - .add(replyItem); - }, - ); - } - }, - childCount: - _dynamicDetailController.replyList.length + 1, + Widget replyList(LoadingState loadingState) { + return loadingState is Success + ? SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == loadingState.response.length) { + return Container( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom), + height: MediaQuery.of(context).padding.bottom + 100, + child: Center( + child: Obx( + () => Text( + _dynamicDetailController.noMore.value, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), + ), ), ), - ); - } else { - // 请求错误 - return HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), - ); - } - } else { - // 骨架屏 - return SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return const VideoReplySkeleton(); - }, childCount: 8), - ); - } - }, - ); + ); + } else { + return ReplyItem( + replyItem: loadingState.response[index], + showReplyRow: true, + replyLevel: '1', + replyReply: replyReply, + replyType: ReplyType.values[replyType], + addReply: (replyItem) { + // loadingState.response[index].replies!.add(replyItem); + }, + ); + } + }, + childCount: loadingState.response.length + 1, + ), + ) + : loadingState is Error + ? HttpError( + errMsg: loadingState.errMsg, + fn: _dynamicDetailController.onReload, + ) + : SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + return const VideoReplySkeleton(); + }, + childCount: 8, + ), + ); } } diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart index 6502980f..2129f847 100644 --- a/lib/pages/hot/view.dart +++ b/lib/pages/hot/view.dart @@ -90,11 +90,8 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { ? (_hotController.loadingState.value as Error) .errMsg : '没有相关数据', - fn: () { - _hotController.loadingState.value = - LoadingState.loading(); - _hotController.onRefresh(); - }), + fn: _hotController.onReload, + ), ), ), ], diff --git a/lib/pages/html/controller.dart b/lib/pages/html/controller.dart index 56c857ab..a876eb41 100644 --- a/lib/pages/html/controller.dart +++ b/lib/pages/html/controller.dart @@ -1,31 +1,18 @@ -import 'package:flutter/material.dart'; +import 'package:PiliPalaX/http/loading_state.dart'; +import 'package:PiliPalaX/pages/common/reply_controller.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; import 'package:PiliPalaX/http/html.dart'; import 'package:PiliPalaX/http/reply.dart'; -import 'package:PiliPalaX/models/common/reply_sort_type.dart'; -import 'package:PiliPalaX/models/video/reply/item.dart'; -import 'package:PiliPalaX/utils/feed_back.dart'; -import 'package:PiliPalaX/utils/storage.dart'; -class HtmlRenderController extends GetxController { +class HtmlRenderController extends ReplyController { late String id; late String dynamicType; late int type; RxInt oid = (-1).obs; late Map response; int? floor; - String nextOffset = ""; - bool isLoadingMore = false; - RxString noMore = ''.obs; - RxList replyList = [].obs; - RxInt acount = 0.obs; - final ScrollController scrollController = ScrollController(); - late ReplySortType _sortType; - late RxString sortTypeTitle; - late RxString sortTypeLabel; - Box setting = GStorage.setting; + RxBool loaded = false.obs; @override void onInit() { @@ -33,19 +20,12 @@ class HtmlRenderController extends GetxController { id = Get.parameters['id']!; dynamicType = Get.parameters['dynamicType']!; type = dynamicType == 'picture' ? 11 : 12; - int defaultReplySortIndex = - setting.get(SettingBoxKey.replySortType, defaultValue: 1) as int; - if (defaultReplySortIndex == 2) { - setting.put(SettingBoxKey.replySortType, 0); - defaultReplySortIndex = 0; - } - _sortType = ReplySortType.values[defaultReplySortIndex]; - sortTypeLabel = _sortType.labels.obs; - sortTypeTitle = _sortType.titles.obs; + + reqHtml(); } // 请求动态内容 - Future reqHtml(id) async { + Future reqHtml() async { late dynamic res; if (dynamicType == 'opus' || dynamicType == 'picture') { res = await HtmlHttp.reqHtml(id, dynamicType); @@ -54,72 +34,17 @@ class HtmlRenderController extends GetxController { } response = res; oid.value = res['commentId']; - queryReplyList(reqType: 'init'); - return res; + queryData(); + if (res['status'] == true) { + loaded.value = true; + } } - // 请求评论 - Future queryReplyList({reqType = 'init'}) async { - if (reqType == 'init') { - nextOffset = ""; - noMore.value = ""; - } - if (noMore.value == '没有更多了') return; - var res = await ReplyHttp.replyList( - oid: oid.value, - nextOffset: nextOffset, - type: type, - sort: _sortType.index, - ); - if (res['status']) { - List replies = res['data'].replies; - acount.value = res['data'].cursor.allCount ?? 0; - nextOffset = res['data'].cursor.paginationReply.nextOffset ?? ""; - if (replies.isNotEmpty) { - noMore.value = '加载中...'; - if (res['data'].cursor.isEnd == true) { - noMore.value = '没有更多了'; - } - } else { - noMore.value = - nextOffset == "" && reqType == 'init' ? '还没有评论' : '没有更多了'; - } - if (reqType == 'init') { - // 添加置顶回复 - if (res['data'].upper.top != null) { - bool flag = res['data'] - .topReplies - .any((reply) => reply.rpid == res['data'].upper.top.rpid); - if (!flag) { - replies.insert(0, res['data'].upper.top); - } - } - replies.insertAll(0, res['data'].topReplies); - replyList.value = replies; - } else { - replyList.addAll(replies); - } - } - isLoadingMore = false; - return res; - } - - // 排序搜索评论 - queryBySort() { - feedBack(); - switch (_sortType) { - case ReplySortType.time: - _sortType = ReplySortType.like; - break; - case ReplySortType.like: - _sortType = ReplySortType.time; - break; - default: - } - sortTypeTitle.value = _sortType.titles; - sortTypeLabel.value = _sortType.labels; - nextOffset = ""; - replyList.clear(); - queryReplyList(reqType: 'init'); - } + @override + Future customGetData() => ReplyHttp.replyList( + oid: oid.value, + nextOffset: nextOffset, + type: type, + sort: sortType.index, + ); } diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart index 4f6d69c6..54ebe3d9 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/html/view.dart @@ -1,5 +1,7 @@ import 'dart:math'; +import 'package:PiliPalaX/common/widgets/http_error.dart'; +import 'package:PiliPalaX/http/loading_state.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -34,8 +36,6 @@ class _HtmlRenderPageState extends State late String dynamicType; late int type; bool _isFabVisible = true; - late final Future _futureBuilderFuture; - late ScrollController scrollController; late AnimationController fabAnimationCtr; @override @@ -46,31 +46,29 @@ class _HtmlRenderPageState extends State url = Get.parameters['url']!; dynamicType = Get.parameters['dynamicType']!; type = dynamicType == 'picture' ? 11 : 12; - _futureBuilderFuture = _htmlRenderCtr.reqHtml(id); fabAnimationCtr = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); + fabAnimationCtr.forward(); scrollListener(); } @override void dispose() { fabAnimationCtr.dispose(); - scrollController.removeListener(() {}); - scrollController.dispose(); + _htmlRenderCtr.scrollController.removeListener(() {}); super.dispose(); } void scrollListener() { - scrollController = _htmlRenderCtr.scrollController; - scrollController.addListener( + _htmlRenderCtr.scrollController.addListener( () { // 分页加载 - if (scrollController.position.pixels >= - scrollController.position.maxScrollExtent - 300) { + if (_htmlRenderCtr.scrollController.position.pixels >= + _htmlRenderCtr.scrollController.position.maxScrollExtent - 300) { EasyThrottle.throttle('replylist', const Duration(seconds: 2), () { - _htmlRenderCtr.queryReplyList(reqType: 'onLoad'); + _htmlRenderCtr.onLoadMore(); }); } @@ -85,7 +83,7 @@ class _HtmlRenderPageState extends State // fab按钮 final ScrollDirection direction = - scrollController.position.userScrollDirection; + _htmlRenderCtr.scrollController.position.userScrollDirection; if (direction == ScrollDirection.forward) { _showFab(); } else if (direction == ScrollDirection.reverse) { @@ -161,7 +159,7 @@ class _HtmlRenderPageState extends State itemBuilder: (BuildContext context) => [ PopupMenuItem( onTap: () => { - _htmlRenderCtr.reqHtml(id), + _htmlRenderCtr.reqHtml(), }, child: const Row( mainAxisSize: MainAxisSize.min, @@ -225,23 +223,17 @@ class _HtmlRenderPageState extends State double padding = max(context.width / 2 - Grid.maxRowWidth, 0); return Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: SingleChildScrollView( - controller: orientation == Orientation.portrait - ? scrollController - : ScrollController(), - child: Padding( + child: SingleChildScrollView( + controller: orientation == Orientation.portrait + ? _htmlRenderCtr.scrollController + : ScrollController(), + child: Padding( padding: orientation == Orientation.portrait ? EdgeInsets.symmetric(horizontal: padding) : EdgeInsets.only(left: padding / 2), - child: FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - var data = snapshot.data; - // fabAnimationCtr.forward(); - if (data != null && data['status']) { - return Column( + child: Obx( + () => _htmlRenderCtr.loaded.value + ? Column( children: [ Padding( padding: @@ -306,35 +298,38 @@ class _HtmlRenderPageState extends State .dividerColor .withOpacity(0.05)), replyHeader(), - replyList(), + Obx( + () => replyList( + _htmlRenderCtr.loadingState.value), + ), ] ], - ); - } else { - return const Text('error'); - } - } else { - // 骨架屏 - return const SizedBox(); - } - }, - )), - )), + ) + : const SizedBox(), + ), + ), + ), + ), if (orientation == Orientation.landscape) ...[ VerticalDivider( thickness: 8, color: Theme.of(context).dividerColor.withOpacity(0.05)), Expanded( - child: SingleChildScrollView( - controller: scrollController, - child: Padding( - padding: EdgeInsets.only(right: padding / 2), - child: Column( - children: [ - replyHeader(), - replyList(), - ], - )))) + child: SingleChildScrollView( + controller: _htmlRenderCtr.scrollController, + child: Padding( + padding: EdgeInsets.only(right: padding / 2), + child: Column( + children: [ + replyHeader(), + Obx( + () => replyList(_htmlRenderCtr.loadingState.value), + ), + ], + ), + ), + ), + ), ] ]); }), @@ -365,13 +360,18 @@ class _HtmlRenderPageState extends State ); }, ).then( - (value) => { + (value) { // 完成评论,数据添加 - if (value != null && value['data'] != null) - { - _htmlRenderCtr.replyList.insert(0, value['data']), - _htmlRenderCtr.acount.value++ - } + if (value != null && value['data'] != null) { + _htmlRenderCtr.count.value++; + List list = _htmlRenderCtr.loadingState.value is Success + ? (_htmlRenderCtr.loadingState.value as Success) + .response + : []; + list.insert(0, value['data']); + _htmlRenderCtr.loadingState.value = + LoadingState.success(list); + } }, ); }, @@ -385,54 +385,59 @@ class _HtmlRenderPageState extends State ); } - Obx replyList() { - return Obx( - () => _htmlRenderCtr.replyList.isEmpty && _htmlRenderCtr.isLoadingMore - ? ListView.builder( - itemCount: 5, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - return const VideoReplySkeleton(); - }, - ) - : ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: _htmlRenderCtr.replyList.length + 1, - itemBuilder: (context, index) { - if (index == _htmlRenderCtr.replyList.length) { - return Container( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom), - height: MediaQuery.of(context).padding.bottom + 100, - child: Center( - child: Obx( - () => Text( - _htmlRenderCtr.noMore.value, - style: TextStyle( - fontSize: 12, - color: Theme.of(context).colorScheme.outline, - ), + Widget replyList(LoadingState loadingState) { + return loadingState is Success + ? ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: loadingState.response.length + 1, + itemBuilder: (context, index) { + if (index == loadingState.response.length) { + return Container( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom), + height: MediaQuery.of(context).padding.bottom + 100, + child: Center( + child: Obx( + () => Text( + _htmlRenderCtr.noMore.value, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, ), ), ), - ); - } else { - return ReplyItem( - replyItem: _htmlRenderCtr.replyList[index], - showReplyRow: true, - replyLevel: '1', - replyReply: (replyItem) => replyReply(replyItem), - replyType: ReplyType.values[type], - addReply: (replyItem) { - _htmlRenderCtr.replyList[index].replies!.add(replyItem); - }, - ); - } - }, - ), - ); + ), + ); + } else { + return ReplyItem( + replyItem: loadingState.response[index], + showReplyRow: true, + replyLevel: '1', + replyReply: (replyItem) => replyReply(replyItem), + replyType: ReplyType.values[type], + addReply: (replyItem) { + loadingState.response[index].replies!.add(replyItem); + }, + ); + } + }, + ) + : loadingState is Error + ? HttpError( + errMsg: _htmlRenderCtr.loadingState.value is Error + ? (_htmlRenderCtr.loadingState.value as Error).errMsg + : '没有相关数据', + fn: _htmlRenderCtr.onReload, + ) + : ListView.builder( + itemCount: 5, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return const VideoReplySkeleton(); + }, + ); } Container replyHeader() { diff --git a/lib/pages/rank/zone/view.dart b/lib/pages/rank/zone/view.dart index 0a644cd9..fda55eb8 100644 --- a/lib/pages/rank/zone/view.dart +++ b/lib/pages/rank/zone/view.dart @@ -90,11 +90,8 @@ class _ZonePageState extends State ? (_zoneController.loadingState.value as Error) .errMsg : '没有相关数据', - fn: () { - _zoneController.loadingState.value = - LoadingState.loading(); - _zoneController.onRefresh(); - }), + fn: _zoneController.onReload, + ), ), ), ], diff --git a/lib/pages/rcmd/controller.dart b/lib/pages/rcmd/controller.dart index fc783d10..1aea2084 100644 --- a/lib/pages/rcmd/controller.dart +++ b/lib/pages/rcmd/controller.dart @@ -34,7 +34,7 @@ class RcmdController extends PopupController { } @override - List? handleResponse(List currentList, List dataList) { + List? handleListResponse(List currentList, List dataList) { return currentPage == 1 && enableSaveLastData ? dataList + (currentList.isEmpty ? [] : currentList) diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index ffa71c57..32f9fe50 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -101,11 +101,8 @@ class _RcmdPageState extends State errMsg: _controller.loadingState.value is Error ? (_controller.loadingState.value as Error).errMsg : '没有相关数据', - fn: () { - _controller.loadingState.value = - LoadingState.loading(); - _controller.onRefresh(); - }), + fn: _controller.onReload, + ), ), ), ], diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index c29993be..63ee5417 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -488,7 +488,7 @@ class VideoIntroController extends GetxController { final VideoReplyController videoReplyCtr = Get.find(tag: heroTag); videoReplyCtr.aid = aid; - videoReplyCtr.queryReplyList(type: 'init'); + videoReplyCtr.queryData(); } catch (_) {} this.bvid = bvid; lastPlayCid.value = cid; diff --git a/lib/pages/video/detail/related/view.dart b/lib/pages/video/detail/related/view.dart index 2b444751..10e803ba 100644 --- a/lib/pages/video/detail/related/view.dart +++ b/lib/pages/video/detail/related/view.dart @@ -74,11 +74,8 @@ class _RelatedVideoPanelState extends State : loadingState is Error ? HttpError( errMsg: '出错了', - fn: () { - _relatedController.loadingState.value = - LoadingState.loading(); - _relatedController.queryData(); - }) + fn: _relatedController.onReload, + ) : SliverGrid( gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: StyleString.safeSpace, diff --git a/lib/pages/video/detail/reply/controller.dart b/lib/pages/video/detail/reply/controller.dart index cbcd2bce..f815d85b 100644 --- a/lib/pages/video/detail/reply/controller.dart +++ b/lib/pages/video/detail/reply/controller.dart @@ -1,142 +1,26 @@ -import 'package:PiliPalaX/utils/extension.dart'; -import 'package:easy_debounce/easy_throttle.dart'; -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:hive/hive.dart'; +import 'package:PiliPalaX/http/loading_state.dart'; +import 'package:PiliPalaX/pages/common/reply_controller.dart'; import 'package:PiliPalaX/http/reply.dart'; -import 'package:PiliPalaX/models/common/reply_sort_type.dart'; import 'package:PiliPalaX/models/common/reply_type.dart'; -import 'package:PiliPalaX/models/video/reply/item.dart'; -import 'package:PiliPalaX/utils/feed_back.dart'; -import 'package:PiliPalaX/utils/storage.dart'; -class VideoReplyController extends GetxController { +class VideoReplyController extends ReplyController { VideoReplyController( this.aid, this.rpid, this.replyLevel, ); - final ScrollController scrollController = ScrollController(); // 视频aid 请求时使用的oid int? aid; // 层级 2为楼中楼 String? replyLevel; // rpid 请求楼中楼回复 String? rpid; - RxList replyList = [].obs; - String nextOffset = ""; - bool isLoadingMore = false; - RxString noMore = ''.obs; - RxInt count = (-1).obs; - // 当前回复的回复 - ReplyItemModel? currentReplyItem; - - ReplySortType _sortType = ReplySortType.time; - RxString sortTypeTitle = ReplySortType.time.titles.obs; - RxString sortTypeLabel = ReplySortType.time.labels.obs; - - Box setting = GStorage.setting; - - Future? futureBuilderFuture; @override - void onInit() { - super.onInit(); - int defaultReplySortIndex = - setting.get(SettingBoxKey.replySortType, defaultValue: 1) as int; - if (defaultReplySortIndex == 2) { - setting.put(SettingBoxKey.replySortType, 0); - defaultReplySortIndex = 0; - } - _sortType = ReplySortType.values[defaultReplySortIndex]; - sortTypeTitle.value = _sortType.titles; - sortTypeLabel.value = _sortType.labels; - } - - Future queryReplyList({type = 'init'}) async { - if (isLoadingMore) { - return; - } - if (type == 'init') { - nextOffset = ''; - noMore.value = ''; - } - if (noMore.value == '没有更多了') return; - isLoadingMore = true; - final res = await ReplyHttp.replyList( - oid: aid!, - nextOffset: nextOffset, - type: ReplyType.video.index, - sort: _sortType.index, - ); - isLoadingMore = false; - if (res['status']) { - final List replies = res['data'].replies; - nextOffset = res['data'].cursor.paginationReply.nextOffset ?? ""; - if (replies.isNotEmpty) { - noMore.value = '加载中...'; - - /// 第一页回复数小于20 - if (res['data'].cursor.isEnd == true) { - noMore.value = '没有更多了'; - } - } else { - // 未登录状态replies可能返回null - noMore.value = nextOffset == "" && type == 'init' ? '还没有评论' : '没有更多了'; - } - if (type == 'init') { - // 添加置顶回复 - if (res['data'].upper.top != null) { - final bool flag = res['data'].topReplies.any((ReplyItemModel reply) => - reply.rpid == res['data'].upper.top.rpid) as bool; - if (!flag) { - replies.insert(0, res['data'].upper.top); - } - } - replies.insertAll(0, res['data'].topReplies); - count.value = res['data'].cursor.allCount ?? 0; - replyList.value = replies; - } else { - replyList.addAll(replies); - } - } - return res; - } - - // 上拉加载 - Future onLoad() async { - queryReplyList(type: 'onLoad'); - } - - // 排序搜索评论 - queryBySort() { - EasyThrottle.throttle('queryBySort', const Duration(seconds: 1), () { - feedBack(); - switch (_sortType) { - case ReplySortType.time: - _sortType = ReplySortType.like; - break; - case ReplySortType.like: - _sortType = ReplySortType.time; - break; - default: - } - sortTypeTitle.value = _sortType.titles; - sortTypeLabel.value = _sortType.labels; - nextOffset = ""; - noMore.value = ''; - replyList.clear(); - queryReplyList(type: 'init'); - }); - } - - void animToTop() { - scrollController.animToTop(); - } - - @override - void onClose() { - scrollController.dispose(); - super.onClose(); - } + Future customGetData() => ReplyHttp.replyList( + oid: aid!, + nextOffset: nextOffset, + type: ReplyType.video.index, + sort: sortType.index, + ); } diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index 2d39a9fd..a5f090f6 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPalaX/common/widgets/http_error.dart'; +import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/pages/video/detail/reply_new/reply_page.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; @@ -74,7 +75,6 @@ class _VideoReplyPanelState extends State void dispose() { _videoReplyController.scrollController.removeListener(() {}); fabAnimationCtr.dispose(); - // _videoReplyController.scrollController.dispose(); super.dispose(); } @@ -86,7 +86,7 @@ class _VideoReplyPanelState extends State 300) { EasyThrottle.throttle('replylist', const Duration(milliseconds: 200), () { - _videoReplyController.onLoad(); + _videoReplyController.onLoadMore(); }); } @@ -136,7 +136,7 @@ class _VideoReplyPanelState extends State super.build(context); return RefreshIndicator( onRefresh: () async { - await _videoReplyController.queryReplyList(type: 'init'); + await _videoReplyController.onRefresh(); }, child: Stack( children: [ @@ -181,175 +181,7 @@ class _VideoReplyPanelState extends State ), ), ), - FutureBuilder( - future: _videoReplyController.futureBuilderFuture, - builder: (BuildContext context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - var data = snapshot.data; - if (data['status']) { - // 请求成功 - return Obx( - () => _videoReplyController.isLoadingMore && - _videoReplyController.replyList.isEmpty - ? SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, index) { - return const VideoReplySkeleton(); - }, childCount: 5), - ) - : SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, index) { - double bottom = - MediaQuery.of(context).padding.bottom; - if (index == - _videoReplyController - .replyList.length) { - return Container( - padding: - EdgeInsets.only(bottom: bottom), - height: bottom + 100, - child: Center( - child: Obx( - () => Text( - _videoReplyController - .noMore.value, - style: TextStyle( - fontSize: 12, - color: Theme.of(context) - .colorScheme - .outline, - ), - ), - ), - ), - ); - } else { - return ReplyItem( - replyItem: _videoReplyController - .replyList[index], - showReplyRow: true, - replyLevel: replyLevel, - replyReply: (replyItem) => - replyReply(replyItem), - replyType: ReplyType.video, - onReply: () { - dynamic oid = _videoReplyController - .replyList[index].oid; - dynamic root = _videoReplyController - .replyList[index].rpid; - dynamic parent = _videoReplyController - .replyList[index].rpid; - dynamic key = oid + root + parent; - Navigator.of(context) - .push( - GetDialogRoute( - pageBuilder: (buildContext, - animation, - secondaryAnimation) { - return ReplyPage( - oid: oid, - root: root, - parent: parent, - replyType: ReplyType.video, - replyItem: - _videoReplyController - .replyList[index], - savedReply: - _savedReplies[key], - onSaveReply: (reply) { - _savedReplies[key] = reply; - }, - ); - }, - transitionDuration: - const Duration( - milliseconds: 500), - transitionBuilder: (context, - animation, - secondaryAnimation, - child) { - const begin = Offset(0.0, 1.0); - const end = Offset.zero; - const curve = Curves.linear; - - var tween = Tween( - begin: begin, end: end) - .chain(CurveTween( - curve: curve)); - - return SlideTransition( - position: - animation.drive(tween), - child: child, - ); - }, - ), - ) - .then((value) { - // 完成评论,数据添加 - if (value != null && - value['data'] != null) { - _savedReplies[key] = null; - } - }); - }, - onDelete: (rpid, frpid) { - _videoReplyController.replyList.value = - frpid == null - ? _videoReplyController - .replyList - .where((item) => - item.rpid != rpid) - .toList() - : _videoReplyController - .replyList - .map((item) { - if (item.rpid == frpid) { - return item - ..replies = item - .replies - ?.where((reply) => - reply.rpid != - rpid) - .toList(); - } else { - return item; - } - }).toList(); - }, - ); - } - }, - childCount: - _videoReplyController.replyList.length + - 1, - ), - ), - ); - } else { - // 请求错误 - return HttpError( - errMsg: data['msg'], - fn: () { - setState(() { - _videoReplyController.futureBuilderFuture = - _videoReplyController.queryReplyList(); - }); - }, - ); - } - } else { - // 骨架屏 - return SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, index) { - return const VideoReplySkeleton(); - }, childCount: 5), - ); - } - }, - ), + Obx(() => _buildBody(_videoReplyController.loadingState.value)), ], ), Positioned( @@ -407,8 +239,15 @@ class _VideoReplyPanelState extends State // 完成评论,数据添加 if (value != null && value['data'] != null) { _savedReplies[oid] = null; - _videoReplyController.replyList - .insert(0, value['data']); + List list = + _videoReplyController.loadingState.value is Success + ? (_videoReplyController.loadingState.value + as Success) + .response + : []; + list.insert(0, value['data']); + _videoReplyController.loadingState.value = + LoadingState.success(list); } }, ); @@ -444,6 +283,121 @@ class _VideoReplyPanelState extends State ), ); } + + Widget _buildBody(LoadingState loadingState) { + return loadingState is Success + ? SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, index) { + double bottom = MediaQuery.of(context).padding.bottom; + if (index == loadingState.response.length) { + return Container( + padding: EdgeInsets.only(bottom: bottom), + height: bottom + 100, + child: Center( + child: Obx( + () => Text( + _videoReplyController.noMore.value, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + ), + ); + } else { + return ReplyItem( + replyItem: loadingState.response[index], + showReplyRow: true, + replyLevel: replyLevel, + replyReply: (replyItem) => replyReply(replyItem), + replyType: ReplyType.video, + onReply: () { + dynamic oid = loadingState.response[index].oid; + dynamic root = loadingState.response[index].rpid; + dynamic parent = loadingState.response[index].rpid; + dynamic key = oid + root + parent; + Navigator.of(context) + .push( + GetDialogRoute( + pageBuilder: + (buildContext, animation, secondaryAnimation) { + return ReplyPage( + oid: oid, + root: root, + parent: parent, + replyType: ReplyType.video, + replyItem: loadingState.response[index], + savedReply: _savedReplies[key], + onSaveReply: (reply) { + _savedReplies[key] = reply; + }, + ); + }, + transitionDuration: const Duration(milliseconds: 500), + transitionBuilder: + (context, animation, secondaryAnimation, child) { + const begin = Offset(0.0, 1.0); + const end = Offset.zero; + const curve = Curves.linear; + + var tween = Tween(begin: begin, end: end) + .chain(CurveTween(curve: curve)); + + return SlideTransition( + position: animation.drive(tween), + child: child, + ); + }, + ), + ) + .then((value) { + // 完成评论,数据添加 + if (value != null && value['data'] != null) { + _savedReplies[key] = null; + } + }); + }, + onDelete: (rpid, frpid) { + List list = + (_videoReplyController.loadingState.value as Success) + .response; + list = frpid == null + ? list.where((item) => item.rpid != rpid).toList() + : list.map((item) { + if (item.rpid == frpid) { + return item + ..replies = item.replies + ?.where((reply) => reply.rpid != rpid) + .toList(); + } else { + return item; + } + }).toList(); + _videoReplyController.loadingState.value = + LoadingState.success(list); + }, + ); + } + }, + childCount: loadingState.response.length + 1, + ), + ) + : loadingState is Error + ? HttpError( + errMsg: loadingState.errMsg, + fn: _videoReplyController.onReload, + ) + : SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, index) { + return const VideoReplySkeleton(); + }, + childCount: 5, + ), + ); + } } class _MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { diff --git a/lib/pages/video/detail/reply_reply/controller.dart b/lib/pages/video/detail/reply_reply/controller.dart index 6a8377b1..ced0bcee 100644 --- a/lib/pages/video/detail/reply_reply/controller.dart +++ b/lib/pages/video/detail/reply_reply/controller.dart @@ -1,21 +1,19 @@ +import 'package:PiliPalaX/http/loading_state.dart'; +import 'package:PiliPalaX/pages/common/common_controller.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/http/reply.dart'; import 'package:PiliPalaX/models/common/reply_type.dart'; import 'package:PiliPalaX/models/video/reply/item.dart'; -class VideoReplyReplyController extends GetxController { +class VideoReplyReplyController extends CommonController { VideoReplyReplyController(this.aid, this.rpid, this.replyType); - final ScrollController scrollController = ScrollController(); // 视频aid 请求时使用的oid int? aid; // rpid 请求楼中楼回复 String? rpid; ReplyType replyType; // = ReplyType.video; - RxList replyList = [].obs; // 当前页 - int currentPage = 0; - bool isLoadingMore = false; RxString noMore = ''.obs; // 当前回复的回复 ReplyItemModel? currentReplyItem; @@ -24,54 +22,42 @@ class VideoReplyReplyController extends GetxController { @override void onInit() { super.onInit(); - currentPage = 0; - } - - Future queryReplyList({type = 'init'}) async { - if (type == 'init') { - currentPage = 0; - } - if (isLoadingMore) { - return; - } - isLoadingMore = true; - final res = await ReplyHttp.replyReplyList( - oid: aid!, - root: rpid!, - pageNum: currentPage + 1, - type: replyType.index, - ); - if (res['status']) { - if (res['data'].root != null) root = res['data'].root; - final List replies = res['data'].replies; - if (replies.isNotEmpty) { - noMore.value = '加载中...'; - if (replies.length == res['data'].page.count) { - noMore.value = '没有更多了'; - } - currentPage++; - } else { - // 未登录状态replies可能返回null - noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了'; - } - if (type == 'init') { - replyList.value = replies; - } else { - // 每次回复之后,翻页请求有且只有相同的一条回复数据 - if (replies.length == 1 && replies.last.rpid == replyList.last.rpid) { - return; - } - replyList.addAll(replies); - // res['data'].replies.addAll(replyList); - } - } - isLoadingMore = false; - return res; + queryData(); } @override - void onClose() { - currentPage = 0; - super.onClose(); + bool customHandleResponse(Success response) { + if (response.response.root != null) root = response.response.root; + List replies = response.response.replies; + if (replies.isNotEmpty) { + noMore.value = '加载中...'; + if (replies.length == response.response.page.count) { + noMore.value = '没有更多了'; + } + } else { + // 未登录状态replies可能返回null + noMore.value = currentPage == 1 ? '还没有评论' : '没有更多了'; + } + if (currentPage != 1) { + List list = loadingState.value is Success + ? (loadingState.value as Success).response + : []; + // 每次回复之后,翻页请求有且只有相同的一条回复数据 + if (replies.length == 1 && replies.last.rpid == list.last.rpid) { + return true; + } else { + replies.insertAll(0, list); + } + } + loadingState.value = LoadingState.success(replies); + return true; } + + @override + Future customGetData() => ReplyHttp.replyReplyList( + oid: aid!, + root: rpid!, + pageNum: currentPage, + type: replyType.index, + ); } diff --git a/lib/pages/video/detail/reply_reply/view.dart b/lib/pages/video/detail/reply_reply/view.dart index d4d8f16a..37996f92 100644 --- a/lib/pages/video/detail/reply_reply/view.dart +++ b/lib/pages/video/detail/reply_reply/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/pages/video/detail/reply_new/reply_page.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; @@ -37,7 +38,6 @@ class VideoReplyReplyPanel extends StatefulWidget { class _VideoReplyReplyPanelState extends State { late VideoReplyReplyController _videoReplyReplyController; - Future? _futureBuilderFuture; late final _savedReplies = {}; @override @@ -57,13 +57,11 @@ class _VideoReplyReplyPanelState extends State { 300) { EasyThrottle.throttle('replylist', const Duration(milliseconds: 200), () { - _videoReplyReplyController.queryReplyList(type: 'onLoad'); + _videoReplyReplyController.onLoadMore(); }); } }, ); - - _futureBuilderFuture = _videoReplyReplyController.queryReplyList(); } void replyReply(replyItem) {} @@ -71,7 +69,6 @@ class _VideoReplyReplyPanelState extends State { @override void dispose() { _videoReplyReplyController.scrollController.removeListener(() {}); - _videoReplyReplyController.scrollController.dispose(); super.dispose(); } @@ -95,7 +92,6 @@ class _VideoReplyReplyPanelState extends State { tooltip: '关闭', icon: const Icon(Icons.close, size: 20), onPressed: () { - _videoReplyReplyController.currentPage = 0; widget.closePanel!(); Navigator.pop(context); }, @@ -110,9 +106,7 @@ class _VideoReplyReplyPanelState extends State { Expanded( child: RefreshIndicator( onRefresh: () async { - setState(() {}); - _videoReplyReplyController.currentPage = 0; - return await _videoReplyReplyController.queryReplyList(); + await _videoReplyReplyController.onRefresh(); }, child: CustomScrollView( controller: _videoReplyReplyController.scrollController, @@ -126,7 +120,7 @@ class _VideoReplyReplyPanelState extends State { replyLevel: '2', showReplyRow: false, addReply: (replyItem) { - _videoReplyReplyController.replyList.add(replyItem); + // _videoReplyReplyController.replyList.add(replyItem); }, replyType: widget.replyType, replyReply: replyReply, @@ -144,129 +138,8 @@ class _VideoReplyReplyPanelState extends State { ), ), ], - FutureBuilder( - future: _futureBuilderFuture, - builder: (BuildContext context, snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - final Map data = snapshot.data as Map; - if (data['status']) { - // 请求成功 - return SliverMainAxisGroup( - slivers: [ - if (widget.firstFloor == null && - _videoReplyReplyController.root != null) ...[ - SliverToBoxAdapter( - child: ReplyItem( - replyItem: _videoReplyReplyController.root, - replyLevel: '2', - showReplyRow: false, - addReply: (replyItem) { - _videoReplyReplyController.replyList - .add(replyItem); - }, - replyType: widget.replyType, - replyReply: replyReply, - needDivider: false, - onReply: () { - _onReply(_videoReplyReplyController.root); - }, - ), - ), - SliverToBoxAdapter( - child: Divider( - height: 20, - color: Theme.of(context) - .dividerColor - .withOpacity(0.1), - thickness: 6, - ), - ), - ], - Obx( - () => SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - if (index == - _videoReplyReplyController - .replyList.length) { - return Container( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context) - .padding - .bottom), - height: MediaQuery.of(context) - .padding - .bottom + - 100, - child: Center( - child: Obx( - () => Text( - _videoReplyReplyController - .noMore.value, - style: TextStyle( - fontSize: 12, - color: Theme.of(context) - .colorScheme - .outline, - ), - ), - ), - ), - ); - } else { - return ReplyItem( - replyItem: _videoReplyReplyController - .replyList[index], - replyLevel: '2', - showReplyRow: false, - addReply: (replyItem) { - _videoReplyReplyController.replyList - .add(replyItem); - }, - replyType: widget.replyType, - onReply: () { - _onReply(_videoReplyReplyController - .replyList[index]); - }, - onDelete: (rpid, frpid) { - _videoReplyReplyController - .replyList.value = - _videoReplyReplyController - .replyList - .where((item) => - item.rpid != rpid) - .toList(); - }, - ); - } - }, - childCount: _videoReplyReplyController - .replyList.length + - 1, - ), - ), - ), - ], - ); - } else { - // 请求错误 - return HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), - ); - } - } else { - // 骨架屏 - return SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - return const VideoReplySkeleton(); - }, childCount: 8), - ); - } - }, - ) + Obx(() => _buildBody( + _videoReplyReplyController.loadingState.value)), ], ), ), @@ -320,4 +193,95 @@ class _VideoReplyReplyPanelState extends State { } }); } + + Widget _buildBody(LoadingState loadingState) { + return loadingState is Success + ? SliverMainAxisGroup( + slivers: [ + if (widget.firstFloor == null && + _videoReplyReplyController.root != null) ...[ + SliverToBoxAdapter( + child: ReplyItem( + replyItem: _videoReplyReplyController.root, + replyLevel: '2', + showReplyRow: false, + addReply: (replyItem) { + // _videoReplyReplyController.replyList.add(replyItem); + }, + replyType: widget.replyType, + replyReply: replyReply, + needDivider: false, + onReply: () { + _onReply(_videoReplyReplyController.root); + }, + ), + ), + SliverToBoxAdapter( + child: Divider( + height: 20, + color: Theme.of(context).dividerColor.withOpacity(0.1), + thickness: 6, + ), + ), + ], + SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + if (index == loadingState.response.length) { + return Container( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom), + height: MediaQuery.of(context).padding.bottom + 100, + child: Center( + child: Obx( + () => Text( + _videoReplyReplyController.noMore.value, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + ), + ); + } else { + return ReplyItem( + replyItem: loadingState.response[index], + replyLevel: '2', + showReplyRow: false, + addReply: (replyItem) { + // _videoReplyReplyController.replyList.add(replyItem); + }, + replyType: widget.replyType, + onReply: () { + _onReply(loadingState.response[index]); + }, + onDelete: (rpid, frpid) { + // _videoReplyReplyController.replyList.value = + // _videoReplyReplyController.replyList + // .where((item) => item.rpid != rpid) + // .toList(); + }, + ); + } + }, + childCount: loadingState.response.length + 1, + ), + ), + ], + ) + : loadingState is Error + ? HttpError( + errMsg: loadingState.errMsg, + fn: _videoReplyReplyController.onReload, + ) + : SliverList( + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return const VideoReplySkeleton(); + }, + childCount: 8, + ), + ); + } } diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index b1fa9326..469bb7f3 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -148,8 +148,7 @@ class _VideoDetailPageState extends State // 获取视频资源,初始化播放器 Future videoSourceInit() async { _futureBuilderFuture = videoDetailController.queryVideoUrl(); - _videoReplyController.futureBuilderFuture = - _videoReplyController.queryReplyList(type: 'init'); + _videoReplyController.queryData(); if (videoDetailController.autoPlay.value) { plPlayerController = videoDetailController.plPlayerController; plPlayerController!.addStatusLister(playerListener); @@ -429,7 +428,7 @@ class _VideoDetailPageState extends State if (value == 0) { _introController.animToTop(); } else { - _videoReplyController.animToTop(); + _videoReplyController.animateToTop(); } } },