feat: 动态主楼评论

This commit is contained in:
guozhigq
2023-09-12 18:54:56 +08:00
parent 7867af0f85
commit 838467451b
4 changed files with 281 additions and 170 deletions

View File

@ -1,3 +1,4 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/http/reply.dart'; import 'package:pilipala/http/reply.dart';
@ -17,6 +18,7 @@ class DynamicDetailController extends GetxController {
RxString noMore = ''.obs; RxString noMore = ''.obs;
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs; RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
RxInt acount = 0.obs; RxInt acount = 0.obs;
final ScrollController scrollController = ScrollController();
ReplySortType _sortType = ReplySortType.time; ReplySortType _sortType = ReplySortType.time;
RxString sortTypeTitle = ReplySortType.time.titles.obs; RxString sortTypeTitle = ReplySortType.time.titles.obs;

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:easy_debounce/easy_throttle.dart'; import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/skeleton/video_reply.dart'; import 'package:pilipala/common/skeleton/video_reply.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
@ -9,7 +10,10 @@ import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/pages/dynamics/deatil/index.dart'; import 'package:pilipala/pages/dynamics/deatil/index.dart';
import 'package:pilipala/pages/dynamics/widgets/author_panel.dart'; import 'package:pilipala/pages/dynamics/widgets/author_panel.dart';
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart'; import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
import 'package:pilipala/pages/video/detail/replyReply/index.dart'; import 'package:pilipala/pages/video/detail/replyReply/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/id_utils.dart';
import '../widgets/dynamic_panel.dart'; import '../widgets/dynamic_panel.dart';
@ -21,15 +25,18 @@ class DynamicDetailPage extends StatefulWidget {
State<DynamicDetailPage> createState() => _DynamicDetailPageState(); State<DynamicDetailPage> createState() => _DynamicDetailPageState();
} }
class _DynamicDetailPageState extends State<DynamicDetailPage> { class _DynamicDetailPageState extends State<DynamicDetailPage>
late DynamicDetailController? _dynamicDetailController; with TickerProviderStateMixin {
late DynamicDetailController _dynamicDetailController;
late AnimationController fabAnimationCtr;
Future? _futureBuilderFuture; Future? _futureBuilderFuture;
late StreamController<bool> titleStreamC; // appBar title late StreamController<bool> titleStreamC; // appBar title
final ScrollController scrollController = ScrollController(); late ScrollController scrollController;
bool _visibleTitle = false; bool _visibleTitle = false;
String? action; String? action;
// 回复类型 // 回复类型
late int type; late int type;
bool _isFabVisible = true;
@override @override
void initState() { void initState() {
@ -50,37 +57,30 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
} }
} catch (_) {} } catch (_) {}
} }
int commentType = Get.arguments['item'].basic!['comment_type'] ?? 11; int commentType = 11;
try {
commentType = Get.arguments['item'].basic!['comment_type'];
} catch (_) {}
type = (commentType == 0) ? 11 : commentType; type = (commentType == 0) ? 11 : commentType;
action = action =
Get.arguments.containsKey('action') ? Get.arguments['action'] : null; Get.arguments.containsKey('action') ? Get.arguments['action'] : null;
_dynamicDetailController = _dynamicDetailController =
Get.put(DynamicDetailController(oid, type), tag: oid.toString()); Get.put(DynamicDetailController(oid, type), tag: oid.toString());
_futureBuilderFuture = _dynamicDetailController!.queryReplyList(); _futureBuilderFuture = _dynamicDetailController.queryReplyList();
titleStreamC = StreamController<bool>(); titleStreamC = StreamController<bool>();
scrollController.addListener(_listen);
if (action == 'comment') { if (action == 'comment') {
_visibleTitle = true; _visibleTitle = true;
titleStreamC.add(true); titleStreamC.add(true);
} }
}
void _listen() async { fabAnimationCtr = AnimationController(
if (scrollController.position.pixels >= vsync: this,
scrollController.position.maxScrollExtent - 300) { duration: const Duration(milliseconds: 300),
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () { );
_dynamicDetailController!.queryReplyList(reqType: 'onLoad'); fabAnimationCtr.forward();
}); // 滚动事件监听
} scrollListener();
if (scrollController.offset > 55 && !_visibleTitle) {
_visibleTitle = true;
titleStreamC.add(true);
} else if (scrollController.offset <= 55 && _visibleTitle) {
_visibleTitle = false;
titleStreamC.add(false);
}
} }
void replyReply(replyItem) { void replyReply(replyItem) {
@ -107,9 +107,58 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
); );
} }
void scrollListener() {
scrollController = _dynamicDetailController.scrollController;
scrollController.addListener(
() {
// 分页加载
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 300) {
EasyThrottle.throttle('replylist', const Duration(seconds: 2), () {
_dynamicDetailController.queryReplyList(reqType: 'onLoad');
});
}
// 标题
if (scrollController.offset > 55 && !_visibleTitle) {
_visibleTitle = true;
titleStreamC.add(true);
} else if (scrollController.offset <= 55 && _visibleTitle) {
_visibleTitle = false;
titleStreamC.add(false);
}
// fab按钮
final ScrollDirection direction =
scrollController.position.userScrollDirection;
if (direction == ScrollDirection.forward) {
_showFab();
} else if (direction == ScrollDirection.reverse) {
_hideFab();
}
},
);
}
void _showFab() {
if (!_isFabVisible) {
_isFabVisible = true;
fabAnimationCtr.forward();
}
}
void _hideFab() {
if (_isFabVisible) {
_isFabVisible = false;
fabAnimationCtr.reverse();
}
}
@override @override
void dispose() { void dispose() {
scrollController.removeListener(() {}); scrollController.removeListener(() {});
fabAnimationCtr.dispose();
scrollController.dispose();
super.dispose(); super.dispose();
} }
@ -136,15 +185,17 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
), ),
body: RefreshIndicator( body: RefreshIndicator(
onRefresh: () async { onRefresh: () async {
await _dynamicDetailController!.queryReplyList(); await _dynamicDetailController.queryReplyList();
}, },
child: CustomScrollView( child: Stack(
children: [
CustomScrollView(
controller: scrollController, controller: scrollController,
slivers: [ slivers: [
if (action != 'comment') if (action != 'comment')
SliverToBoxAdapter( SliverToBoxAdapter(
child: DynamicPanel( child: DynamicPanel(
item: _dynamicDetailController!.item, item: _dynamicDetailController.item,
source: 'detail', source: 'detail',
), ),
), ),
@ -156,7 +207,9 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
border: Border( border: Border(
top: BorderSide( top: BorderSide(
width: 0.6, width: 0.6,
color: Theme.of(context).dividerColor.withOpacity(0.05), color: Theme.of(context)
.dividerColor
.withOpacity(0.05),
), ),
), ),
), ),
@ -173,9 +226,9 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
scale: animation, child: child); scale: animation, child: child);
}, },
child: Text( child: Text(
'${_dynamicDetailController!.acount.value}', '${_dynamicDetailController.acount.value}',
key: ValueKey<int>( key: ValueKey<int>(
_dynamicDetailController!.acount.value), _dynamicDetailController.acount.value),
), ),
), ),
), ),
@ -185,10 +238,11 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
height: 35, height: 35,
child: TextButton.icon( child: TextButton.icon(
onPressed: () => onPressed: () =>
_dynamicDetailController!.queryBySort(), _dynamicDetailController.queryBySort(),
icon: const Icon(Icons.sort, size: 16), icon: const Icon(Icons.sort, size: 16),
label: Obx(() => Text( label: Obx(() => Text(
_dynamicDetailController!.sortTypeLabel.value, _dynamicDetailController
.sortTypeLabel.value,
style: const TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
)), )),
), ),
@ -207,11 +261,11 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
if (snapshot.data['status']) { if (snapshot.data['status']) {
// 请求成功 // 请求成功
return Obx( return Obx(
() => _dynamicDetailController!.replyList.isEmpty && () => _dynamicDetailController.replyList.isEmpty &&
_dynamicDetailController!.isLoadingMore _dynamicDetailController.isLoadingMore
? SliverList( ? SliverList(
delegate: delegate: SliverChildBuilderDelegate(
SliverChildBuilderDelegate((context, index) { (context, index) {
return const VideoReplySkeleton(); return const VideoReplySkeleton();
}, childCount: 8), }, childCount: 8),
) )
@ -219,7 +273,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(context, index) { (context, index) {
if (index == if (index ==
_dynamicDetailController! _dynamicDetailController
.replyList.length) { .replyList.length) {
return Container( return Container(
padding: EdgeInsets.only( padding: EdgeInsets.only(
@ -233,7 +287,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
child: Center( child: Center(
child: Obx( child: Obx(
() => Text( () => Text(
_dynamicDetailController! _dynamicDetailController
.noMore.value, .noMore.value,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
@ -247,7 +301,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
); );
} else { } else {
return ReplyItem( return ReplyItem(
replyItem: _dynamicDetailController! replyItem: _dynamicDetailController
.replyList[index], .replyList[index],
showReplyRow: true, showReplyRow: true,
replyLevel: '1', replyLevel: '1',
@ -255,15 +309,15 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
replyReply(replyItem), replyReply(replyItem),
replyType: ReplyType.values[type], replyType: ReplyType.values[type],
addReply: (replyItem) { addReply: (replyItem) {
_dynamicDetailController! _dynamicDetailController
.replyList[index].replies! .replyList[index].replies!
.add(replyItem); .add(replyItem);
}, },
); );
} }
}, },
childCount: childCount: _dynamicDetailController
_dynamicDetailController!.replyList.length + .replyList.length +
1, 1,
), ),
), ),
@ -287,6 +341,52 @@ class _DynamicDetailPageState extends State<DynamicDetailPage> {
) )
], ],
), ),
Positioned(
bottom: MediaQuery.of(context).padding.bottom + 14,
right: 14,
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 2),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: fabAnimationCtr,
curve: Curves.easeInOut,
)),
child: FloatingActionButton(
heroTag: null,
onPressed: () {
feedBack();
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return VideoReplyNewDialog(
oid: _dynamicDetailController.oid ??
IdUtils.bv2av(Get.parameters['bvid']!),
root: 0,
parent: 0,
replyType: ReplyType.values[type],
);
},
).then(
(value) => {
// 完成评论,数据添加
if (value != null && value['data'] != null)
{
_dynamicDetailController.replyList
.add(value['data']),
_dynamicDetailController.acount.value++
}
},
);
},
tooltip: '评论动态',
child: const Icon(Icons.reply),
),
),
),
],
),
), ),
); );
} }

View File

@ -62,6 +62,12 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
vsync: this, duration: const Duration(milliseconds: 300)); vsync: this, duration: const Duration(milliseconds: 300));
_futureBuilderFuture = _videoReplyController.queryReplyList(); _futureBuilderFuture = _videoReplyController.queryReplyList();
fabAnimationCtr.forward();
scrollListener();
}
void scrollListener() {
scrollController = _videoReplyController.scrollController; scrollController = _videoReplyController.scrollController;
scrollController.addListener( scrollController.addListener(
() { () {
@ -81,7 +87,6 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
} }
}, },
); );
fabAnimationCtr.forward();
} }
void _showFab() { void _showFab() {
@ -112,9 +117,10 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
@override @override
void dispose() { void dispose() {
super.dispose(); scrollController.removeListener(() {});
fabAnimationCtr.dispose(); fabAnimationCtr.dispose();
scrollController.dispose(); scrollController.dispose();
super.dispose();
} }
@override @override
@ -128,7 +134,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
child: Stack( child: Stack(
children: [ children: [
CustomScrollView( CustomScrollView(
controller: _videoReplyController.scrollController, controller: scrollController,
key: const PageStorageKey<String>('评论'), key: const PageStorageKey<String>('评论'),
slivers: <Widget>[ slivers: <Widget>[
SliverPersistentHeader( SliverPersistentHeader(

View File

@ -744,11 +744,14 @@ InlineSpan buildContent(
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
// 跳转到指定位置 // 跳转到指定位置
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']) try {
Get.find<VideoDetailController>(
tag: Get.arguments['heroTag'])
.plPlayerController .plPlayerController
.seekTo( .seekTo(
Duration(seconds: Utils.duration(matchStr)), Duration(seconds: Utils.duration(matchStr)),
); );
} catch (_) {}
}, },
), ),
); );