import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo, DetailListReply, Mode; import 'package:PiliPlus/grpc/reply.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/pages/video/reply_new/view.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/request_utils.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:fixnum/fixnum.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:get/get.dart'; import 'package:get/get_navigation/src/dialog/dialog_route.dart'; import 'package:super_sliver_list/super_sliver_list.dart'; class VideoReplyReplyController extends ReplyController with GetSingleTickerProviderStateMixin { VideoReplyReplyController({ required this.hasRoot, required this.id, required this.oid, required this.rpid, required this.dialog, required this.replyType, }); final int? dialog; int? id; // 视频aid 请求时使用的oid int oid; // rpid 请求楼中楼回复 int rpid; int replyType; bool hasRoot = false; final Rx firstFloor = Rx(null); final index = RxnInt(); final listController = ListController(); AnimationController? _controller; AnimationController get animController => _controller ??= AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); late final horizontalPreview = Pref.horizontalPreview; @override dynamic get sourceId => replyType == 1 ? IdUtils.av2bv(oid) : oid; @override void onInit() { super.onInit(); mode.value = Mode.MAIN_LIST_TIME; queryData(); } @override List? getDataList(response) { return dialog != null ? response.replies : response.root.replies; } @override bool customHandleResponse(bool isRefresh, Success response) { final data = response.response; subjectControl = data.subjectControl; upMid ??= data.subjectControl.upMid; paginationReply = data.paginationReply; isEnd = data.cursor.isEnd; // reply2Reply // isDialogue.not if (data is DetailListReply) { count.value = data.root.count.toInt(); if (isRefresh && !hasRoot) { firstFloor.value ??= data.root; } if (id != null) { setIndexById(Int64(id!), data.root.replies); id = null; } } return false; } bool setIndexById(Int64 id64, [List? replies]) { final index = (replies ?? loadingState.value.data!).indexWhere( (item) => item.id == id64, ); if (index != -1) { this.index.value = index; jumpToItem(index); return true; } return false; } ExtendedNestedScrollController? nestedController; void jumpToItem(int index) { SchedulerBinding.instance.addPostFrameCallback((_) { animController.forward(from: 0); try { // ignore: invalid_use_of_visible_for_testing_member final offset = listController.getOffsetToReveal(index, 0.25); if (offset.isFinite) { if (nestedController case final nestedController?) { nestedController.nestedPositions.last.localJumpTo(offset); } else { scrollController.jumpTo(offset); } } } catch (_) { if (kDebugMode) rethrow; } }); } @override Future customGetData() => dialog != null ? ReplyGrpc.dialogList( type: replyType, oid: oid, root: rpid, dialog: dialog!, offset: paginationReply?.nextOffset, ) : ReplyGrpc.detailList( type: replyType, oid: oid, root: rpid, rpid: id ?? 0, mode: mode.value, offset: paginationReply?.nextOffset, ); @override void queryBySort() { if (isLoading) return; mode.value = mode.value == Mode.MAIN_LIST_HOT ? Mode.MAIN_LIST_TIME : Mode.MAIN_LIST_HOT; onReload(); } @override Future onReload() { if (loadingState.value.isSuccess) { index.value = null; } return super.onReload(); } @override void onReply( BuildContext context, { int? oid, ReplyInfo? replyItem, int? replyType, int? index, }) { assert(replyItem != null && index != null); final (bool inputDisable, bool canUploadPic, String? hint) = replyHint; if (inputDisable) { return; } final oid = replyItem!.oid.toInt(); final root = replyItem.id.toInt(); final key = oid + root; Navigator.of(context) .push( GetDialogRoute( pageBuilder: (buildContext, animation, secondaryAnimation) { return ReplyPage( hint: hint, oid: oid, root: root, parent: root, replyType: this.replyType, replyItem: replyItem, items: savedReplies[key], onSave: (reply) { if (reply.isEmpty) { savedReplies.remove(key); } else { savedReplies[key] = reply.toList(); } }, ); }, transitionDuration: const Duration(milliseconds: 500), transitionBuilder: (context, animation, secondaryAnimation, child) { return SlideTransition( position: animation.drive( Tween( begin: const Offset(0.0, 1.0), end: Offset.zero, ).chain(CurveTween(curve: Curves.linear)), ), child: child, ); }, ), ) .then((res) { if (res != null) { savedReplies.remove(key); ReplyInfo replyInfo = RequestUtils.replyCast(res); count.value += 1; loadingState ..value.dataOrNull?.insert(index! + 1, replyInfo) ..refresh(); if (enableCommAntifraud) { onCheckReply(replyInfo, isManual: false); } } }); } @override void onClose() { _controller?.dispose(); _controller = null; super.dispose(); } }