mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-22 01:56:47 +08:00
refactor: reply
This commit is contained in:
@@ -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<ReplyItemModel> replyList = <ReplyItemModel>[].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<ReplyItemModel> 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<LoadingState> customGetData() => ReplyHttp.replyList(
|
||||
oid: oid.value,
|
||||
nextOffset: nextOffset,
|
||||
type: type,
|
||||
sort: sortType.index,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<HtmlRenderPage>
|
||||
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<HtmlRenderPage>
|
||||
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<HtmlRenderPage>
|
||||
|
||||
// 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<HtmlRenderPage>
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
PopupMenuItem(
|
||||
onTap: () => {
|
||||
_htmlRenderCtr.reqHtml(id),
|
||||
_htmlRenderCtr.reqHtml(),
|
||||
},
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -225,23 +223,17 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
||||
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<HtmlRenderPage>
|
||||
.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<HtmlRenderPage>
|
||||
);
|
||||
},
|
||||
).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<HtmlRenderPage>
|
||||
);
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
Reference in New Issue
Block a user