feat: 评论
This commit is contained in:
@ -91,6 +91,10 @@ class Api {
|
|||||||
// 楼中楼
|
// 楼中楼
|
||||||
static const String replyReplyList = '/x/v2/reply/reply';
|
static const String replyReplyList = '/x/v2/reply/reply';
|
||||||
|
|
||||||
|
// 发表评论
|
||||||
|
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/comment/action.md
|
||||||
|
static const String replyAdd = '/x/v2/reply/add';
|
||||||
|
|
||||||
// 用户(被)关注数、投稿数
|
// 用户(被)关注数、投稿数
|
||||||
// https://api.bilibili.com/x/relation/stat?vmid=697166795
|
// https://api.bilibili.com/x/relation/stat?vmid=697166795
|
||||||
static const String userStat = '/x/relation/stat';
|
static const String userStat = '/x/relation/stat';
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:pilipala/http/api.dart';
|
import 'package:pilipala/http/api.dart';
|
||||||
import 'package:pilipala/http/init.dart';
|
import 'package:pilipala/http/init.dart';
|
||||||
|
import 'package:pilipala/models/common/reply_type.dart';
|
||||||
import 'package:pilipala/models/model_hot_video_item.dart';
|
import 'package:pilipala/models/model_hot_video_item.dart';
|
||||||
import 'package:pilipala/models/model_rec_video_item.dart';
|
import 'package:pilipala/models/model_rec_video_item.dart';
|
||||||
import 'package:pilipala/models/user/fav_folder.dart';
|
import 'package:pilipala/models/user/fav_folder.dart';
|
||||||
@ -183,4 +186,42 @@ class VideoHttp {
|
|||||||
return {'status': false, 'data': []};
|
return {'status': false, 'data': []};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 发表评论 replyAdd
|
||||||
|
|
||||||
|
// type num 评论区类型代码 必要 类型代码见表
|
||||||
|
// oid num 目标评论区id 必要
|
||||||
|
// root num 根评论rpid 非必要 二级评论以上使用
|
||||||
|
// parent num 父评论rpid 非必要 二级评论同根评论id 大于二级评论为要回复的评论id
|
||||||
|
// message str 发送评论内容 必要 最大1000字符
|
||||||
|
// plat num 发送平台标识 非必要 1:web端 2:安卓客户端 3:ios客户端 4:wp客户端
|
||||||
|
static Future replyAdd({
|
||||||
|
required ReplyType type,
|
||||||
|
required int oid,
|
||||||
|
required String message,
|
||||||
|
int? root,
|
||||||
|
int? parent,
|
||||||
|
}) async {
|
||||||
|
if(message == ''){
|
||||||
|
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
||||||
|
}
|
||||||
|
print('root:$root');
|
||||||
|
print('parent: $parent');
|
||||||
|
|
||||||
|
var res = await Request()
|
||||||
|
.post(Api.replyAdd, queryParameters: {
|
||||||
|
'type': type.index,
|
||||||
|
'oid': oid,
|
||||||
|
'root': root ?? '',
|
||||||
|
'parent': parent == null || parent == 0 ? '' : parent,
|
||||||
|
'message': message,
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
});
|
||||||
|
log(res.toString());
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'data': res.data['data']};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'data': []};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
46
lib/models/common/reply_type.dart
Normal file
46
lib/models/common/reply_type.dart
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
enum ReplyType {
|
||||||
|
unset,
|
||||||
|
// 视频
|
||||||
|
video,
|
||||||
|
// 话题
|
||||||
|
topic,
|
||||||
|
// 活动
|
||||||
|
activity,
|
||||||
|
// 小视频
|
||||||
|
videoS,
|
||||||
|
// 小黑屋封禁信息
|
||||||
|
blockMsg,
|
||||||
|
// 公告信息
|
||||||
|
publicMsg,
|
||||||
|
// 直播活动
|
||||||
|
liveActivity,
|
||||||
|
// 活动稿件
|
||||||
|
activityFile,
|
||||||
|
// 直播公告
|
||||||
|
livePublic,
|
||||||
|
// 相簿
|
||||||
|
album,
|
||||||
|
// 专栏
|
||||||
|
column,
|
||||||
|
// 票务
|
||||||
|
ticket,
|
||||||
|
// 音频
|
||||||
|
audio,
|
||||||
|
|
||||||
|
// 点评
|
||||||
|
comment,
|
||||||
|
// 动态
|
||||||
|
dynamics,
|
||||||
|
// 播单
|
||||||
|
playList,
|
||||||
|
// 音乐播单
|
||||||
|
musicPlayList,
|
||||||
|
// 漫画
|
||||||
|
comics1,
|
||||||
|
// 漫画
|
||||||
|
comics2,
|
||||||
|
// 漫画
|
||||||
|
comics3,
|
||||||
|
// 课程
|
||||||
|
course,
|
||||||
|
}
|
@ -30,6 +30,7 @@ class ReplyItemModel {
|
|||||||
this.replyControl,
|
this.replyControl,
|
||||||
this.isUp,
|
this.isUp,
|
||||||
this.isTop,
|
this.isTop,
|
||||||
|
this.cardLabel,
|
||||||
});
|
});
|
||||||
|
|
||||||
int? rpid;
|
int? rpid;
|
||||||
@ -59,6 +60,7 @@ class ReplyItemModel {
|
|||||||
ReplyControl? replyControl;
|
ReplyControl? replyControl;
|
||||||
bool? isUp;
|
bool? isUp;
|
||||||
bool? isTop = false;
|
bool? isTop = false;
|
||||||
|
List? cardLabel;
|
||||||
|
|
||||||
ReplyItemModel.fromJson(Map<String, dynamic> json, upperMid,
|
ReplyItemModel.fromJson(Map<String, dynamic> json, upperMid,
|
||||||
{isTopStatus = false}) {
|
{isTopStatus = false}) {
|
||||||
@ -95,6 +97,9 @@ class ReplyItemModel {
|
|||||||
: ReplyControl.fromJson(json['reply_control']);
|
: ReplyControl.fromJson(json['reply_control']);
|
||||||
isUp = upperMid.toString() == json['member']['mid'];
|
isUp = upperMid.toString() == json['member']['mid'];
|
||||||
isTop = isTopStatus;
|
isTop = isTopStatus;
|
||||||
|
cardLabel = json['card_label'] != null
|
||||||
|
? json['card_label'].map((e) => e['text_content']).toList()
|
||||||
|
: [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/http/reply.dart';
|
import 'package:pilipala/http/reply.dart';
|
||||||
|
import 'package:pilipala/http/video.dart';
|
||||||
|
import 'package:pilipala/models/common/reply_type.dart';
|
||||||
import 'package:pilipala/models/video/reply/data.dart';
|
import 'package:pilipala/models/video/reply/data.dart';
|
||||||
import 'package:pilipala/models/video/reply/item.dart';
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
|
|
||||||
@ -10,7 +13,7 @@ class VideoReplyController extends GetxController {
|
|||||||
VideoReplyController(
|
VideoReplyController(
|
||||||
this.aid,
|
this.aid,
|
||||||
this.rpid,
|
this.rpid,
|
||||||
this.level,
|
this.level
|
||||||
);
|
);
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
// 视频aid 请求时使用的oid
|
// 视频aid 请求时使用的oid
|
||||||
@ -24,6 +27,15 @@ class VideoReplyController extends GetxController {
|
|||||||
int currentPage = 0;
|
int currentPage = 0;
|
||||||
bool isLoadingMore = false;
|
bool isLoadingMore = false;
|
||||||
RxBool noMore = false.obs;
|
RxBool noMore = false.obs;
|
||||||
|
RxBool autoFocus = false.obs;
|
||||||
|
// 当前回复的回复
|
||||||
|
ReplyItemModel? currentReplyItem;
|
||||||
|
// 回复来源
|
||||||
|
String replySource = 'main';
|
||||||
|
// 根评论 id 回复楼中楼回复使用
|
||||||
|
int? rPid;
|
||||||
|
// 默认回复主楼
|
||||||
|
String replyLevel = '0';
|
||||||
|
|
||||||
Future queryReplyList({type = 'init'}) async {
|
Future queryReplyList({type = 'init'}) async {
|
||||||
isLoadingMore = true;
|
isLoadingMore = true;
|
||||||
@ -77,4 +89,29 @@ class VideoReplyController extends GetxController {
|
|||||||
Future onLoad() async {
|
Future onLoad() async {
|
||||||
queryReplyList(type: 'onLoad');
|
queryReplyList(type: 'onLoad');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wakeUpReply() {
|
||||||
|
autoFocus.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发表评论
|
||||||
|
Future submitReplyAdd() async {
|
||||||
|
print('replyLevel: $replyLevel');
|
||||||
|
// print('rpid: $rpid');
|
||||||
|
// print('currentReplyItem!.rpid: ${currentReplyItem!.rpid}');
|
||||||
|
|
||||||
|
|
||||||
|
var result = await VideoHttp.replyAdd(
|
||||||
|
type: ReplyType.video,
|
||||||
|
oid: int.parse(aid!),
|
||||||
|
root: replyLevel == '0' ? 0 : replyLevel == '1' ? currentReplyItem!.rpid : rPid,
|
||||||
|
parent: replyLevel == '0' ? 0 : replyLevel == '1' ? currentReplyItem!.rpid : currentReplyItem!.rpid,
|
||||||
|
message: replyLevel == '2' ? ' 回复 @${currentReplyItem!.member!.uname!} : 2楼31' : '2楼31',
|
||||||
|
);
|
||||||
|
if(result['status']){
|
||||||
|
SmartDialog.showToast(result['data']['success_toast']);
|
||||||
|
}else{
|
||||||
|
SmartDialog.showToast(result['message']);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
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_card_h.dart';
|
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||||
import 'package:pilipala/common/skeleton/video_reply.dart';
|
import 'package:pilipala/common/skeleton/video_reply.dart';
|
||||||
@ -10,11 +13,11 @@ import 'widgets/reply_item.dart';
|
|||||||
class VideoReplyPanel extends StatefulWidget {
|
class VideoReplyPanel extends StatefulWidget {
|
||||||
int oid;
|
int oid;
|
||||||
int rpid;
|
int rpid;
|
||||||
String level;
|
String? level;
|
||||||
VideoReplyPanel({
|
VideoReplyPanel({
|
||||||
this.oid = 0,
|
this.oid = 0,
|
||||||
this.rpid = 0,
|
this.rpid = 0,
|
||||||
this.level = '',
|
this.level,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -23,11 +26,16 @@ class VideoReplyPanel extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _VideoReplyPanelState extends State<VideoReplyPanel>
|
class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
|
||||||
late VideoReplyController _videoReplyController;
|
late VideoReplyController _videoReplyController;
|
||||||
|
late AnimationController fabAnimationCtr;
|
||||||
|
late AnimationController replyAnimationCtl;
|
||||||
|
|
||||||
// List<ReplyItemModel>? replyList;
|
// List<ReplyItemModel>? replyList;
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
|
bool _isFabVisible = true;
|
||||||
|
String replyLevel = '1';
|
||||||
|
|
||||||
// 添加页面缓存
|
// 添加页面缓存
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@ -35,16 +43,28 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (widget.level == '2') {
|
replyLevel = widget.level ?? '1';
|
||||||
|
if (widget.level != null && widget.level == '2') {
|
||||||
_videoReplyController = Get.put(
|
_videoReplyController = Get.put(
|
||||||
VideoReplyController(
|
VideoReplyController(
|
||||||
widget.oid.toString(), widget.rpid.toString(), '2'),
|
widget.oid.toString(), widget.rpid.toString(), '2'),
|
||||||
tag: widget.rpid.toString());
|
tag: widget.rpid.toString());
|
||||||
|
_videoReplyController.rPid = widget.rpid;
|
||||||
} else {
|
} else {
|
||||||
_videoReplyController = Get.put(
|
_videoReplyController = Get.put(
|
||||||
VideoReplyController(Get.parameters['aid']!, '', '1'),
|
VideoReplyController(Get.parameters['aid']!, '', '1'),
|
||||||
tag: Get.arguments['heroTag']);
|
tag: Get.arguments['heroTag']);
|
||||||
}
|
}
|
||||||
|
// if(replyLevel != ''){
|
||||||
|
// _videoReplyController.replyLevel = replyLevel;
|
||||||
|
// }
|
||||||
|
print(
|
||||||
|
'_videoReplyController.replyLevel: ${_videoReplyController.replyLevel}');
|
||||||
|
|
||||||
|
fabAnimationCtr = AnimationController(
|
||||||
|
vsync: this, duration: const Duration(milliseconds: 300));
|
||||||
|
replyAnimationCtl = AnimationController(
|
||||||
|
vsync: this, duration: const Duration(milliseconds: 500));
|
||||||
|
|
||||||
_futureBuilderFuture = _videoReplyController.queryReplyList();
|
_futureBuilderFuture = _videoReplyController.queryReplyList();
|
||||||
_videoReplyController.scrollController.addListener(
|
_videoReplyController.scrollController.addListener(
|
||||||
@ -56,10 +76,55 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
_videoReplyController.onLoad();
|
_videoReplyController.onLoad();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final ScrollDirection direction =
|
||||||
|
_videoReplyController.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showReply(source, {ReplyItemModel? replyItem, replyLevel}) async {
|
||||||
|
// source main 直接回复 floor 楼中楼回复
|
||||||
|
if (source == 'floor') {
|
||||||
|
_videoReplyController.currentReplyItem = replyItem;
|
||||||
|
_videoReplyController.replySource = source;
|
||||||
|
_videoReplyController.replyLevel = replyLevel ?? '1';
|
||||||
|
} else {
|
||||||
|
_videoReplyController.replyLevel = '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
replyAnimationCtl.forward();
|
||||||
|
await Future.delayed(const Duration(microseconds: 100));
|
||||||
|
_videoReplyController.wakeUpReply();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
// TODO: implement dispose
|
||||||
|
super.dispose();
|
||||||
|
fabAnimationCtr.dispose();
|
||||||
|
_videoReplyController.scrollController.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
@ -68,63 +133,147 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
_videoReplyController.currentPage = 0;
|
_videoReplyController.currentPage = 0;
|
||||||
return await _videoReplyController.queryReplyList();
|
return await _videoReplyController.queryReplyList();
|
||||||
},
|
},
|
||||||
child: CustomScrollView(
|
child: Scaffold(
|
||||||
controller: _videoReplyController.scrollController,
|
resizeToAvoidBottomInset: false,
|
||||||
key: const PageStorageKey<String>('评论'),
|
body: Stack(
|
||||||
slivers: <Widget>[
|
children: [
|
||||||
FutureBuilder(
|
CustomScrollView(
|
||||||
future: _futureBuilderFuture,
|
controller: _videoReplyController.scrollController,
|
||||||
builder: (context, snapshot) {
|
key: const PageStorageKey<String>('评论'),
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
slivers: <Widget>[
|
||||||
Map data = snapshot.data as Map;
|
FutureBuilder(
|
||||||
if (data['status']) {
|
future: _futureBuilderFuture,
|
||||||
// 请求成功
|
builder: (context, snapshot) {
|
||||||
return Obx(
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
() => SliverList(
|
Map data = snapshot.data as Map;
|
||||||
delegate: SliverChildBuilderDelegate(
|
if (data['status']) {
|
||||||
(context, index) {
|
// 请求成功
|
||||||
if (index == _videoReplyController.replyList.length) {
|
return Obx(
|
||||||
return Container(
|
() => SliverList(
|
||||||
padding: EdgeInsets.only(
|
delegate: SliverChildBuilderDelegate(
|
||||||
bottom:
|
(context, index) {
|
||||||
MediaQuery.of(context).padding.bottom),
|
if (index ==
|
||||||
height:
|
_videoReplyController.replyList.length) {
|
||||||
MediaQuery.of(context).padding.bottom + 60,
|
return Container(
|
||||||
child: Center(
|
padding: EdgeInsets.only(
|
||||||
child: Obx(() => Text(
|
bottom: MediaQuery.of(context)
|
||||||
_videoReplyController.noMore.value
|
.padding
|
||||||
? '没有更多了'
|
.bottom),
|
||||||
: '加载中')),
|
height:
|
||||||
),
|
MediaQuery.of(context).padding.bottom +
|
||||||
);
|
60,
|
||||||
} else {
|
child: Center(
|
||||||
return ReplyItem(
|
child: Obx(() => Text(
|
||||||
replyItem: _videoReplyController.replyList[index],
|
_videoReplyController.noMore.value
|
||||||
);
|
? '没有更多了'
|
||||||
}
|
: '加载中')),
|
||||||
},
|
),
|
||||||
childCount: _videoReplyController.replyList.length + 1,
|
);
|
||||||
|
} else {
|
||||||
|
return ReplyItem(
|
||||||
|
replyItem: _videoReplyController
|
||||||
|
.replyList[index],
|
||||||
|
weakUpReply: (replyItem, replyLevel) =>
|
||||||
|
_showReply(
|
||||||
|
'floor',
|
||||||
|
replyItem: replyItem,
|
||||||
|
replyLevel: replyLevel,
|
||||||
|
),
|
||||||
|
replyLevel: replyLevel);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
childCount:
|
||||||
|
_videoReplyController.replyList.length + 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 请求错误
|
||||||
|
return HttpError(
|
||||||
|
errMsg: data['msg'],
|
||||||
|
fn: () => setState(() {}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
|
return const VideoReplySkeleton();
|
||||||
|
}, childCount: 5),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 14,
|
||||||
|
right: 14,
|
||||||
|
child: SlideTransition(
|
||||||
|
position: Tween<Offset>(
|
||||||
|
// begin: const Offset(0, 2),
|
||||||
|
// 评论内容为空/不足一屏
|
||||||
|
begin: const Offset(0, 0),
|
||||||
|
end: const Offset(0, 0),
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: fabAnimationCtr,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
)),
|
||||||
|
child: FloatingActionButton(
|
||||||
|
heroTag: null,
|
||||||
|
onPressed: () => _showReply('main'),
|
||||||
|
tooltip: '发表评论',
|
||||||
|
child: const Icon(Icons.reply),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Obx(
|
||||||
|
() => Positioned(
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: SlideTransition(
|
||||||
|
position: Tween<Offset>(
|
||||||
|
begin: const Offset(0, 2),
|
||||||
|
end: const Offset(0, 0),
|
||||||
|
).animate(CurvedAnimation(
|
||||||
|
parent: replyAnimationCtl,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
)),
|
||||||
|
child: Container(
|
||||||
|
height: 100 + MediaQuery.of(context).padding.bottom,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom),
|
||||||
|
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Visibility(
|
||||||
|
visible: _videoReplyController.autoFocus.value,
|
||||||
|
child: const TextField(
|
||||||
|
autofocus: true,
|
||||||
|
maxLines: null,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: "友善评论", border: InputBorder.none),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () =>
|
||||||
|
_videoReplyController.submitReplyAdd(),
|
||||||
|
child: const Text('发送'),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
} else {
|
),
|
||||||
// 请求错误
|
),
|
||||||
return HttpError(
|
),
|
||||||
errMsg: data['msg'],
|
],
|
||||||
fn: () => setState(() {}),
|
),
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 骨架屏
|
|
||||||
return SliverList(
|
|
||||||
delegate: SliverChildBuilderDelegate((context, index) {
|
|
||||||
return const VideoReplySkeleton();
|
|
||||||
}, childCount: 5),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,10 @@ import 'package:pilipala/pages/video/detail/reply/index.dart';
|
|||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class ReplyItem extends StatelessWidget {
|
class ReplyItem extends StatelessWidget {
|
||||||
ReplyItem({super.key, this.replyItem});
|
ReplyItem({super.key, this.replyItem, this.weakUpReply, this.replyLevel});
|
||||||
ReplyItemModel? replyItem;
|
ReplyItemModel? replyItem;
|
||||||
|
Function? weakUpReply;
|
||||||
|
String? replyLevel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -173,6 +175,7 @@ class ReplyItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
// 操作区域
|
// 操作区域
|
||||||
bottonAction(context, replyItem!.replyControl),
|
bottonAction(context, replyItem!.replyControl),
|
||||||
|
const SizedBox(height: 3),
|
||||||
if (replyItem!.replies!.isNotEmpty) ...[
|
if (replyItem!.replies!.isNotEmpty) ...[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 2, bottom: 12),
|
padding: const EdgeInsets.only(top: 2, bottom: 12),
|
||||||
@ -193,6 +196,15 @@ class ReplyItem extends StatelessWidget {
|
|||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: 48),
|
const SizedBox(width: 48),
|
||||||
|
if (replyItem!.cardLabel!.isNotEmpty &&
|
||||||
|
replyItem!.cardLabel!.contains('热评'))
|
||||||
|
Text(
|
||||||
|
'热评 • ',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelMedium!
|
||||||
|
.copyWith(color: Theme.of(context).colorScheme.primary),
|
||||||
|
),
|
||||||
Text(
|
Text(
|
||||||
Utils.dateFormat(replyItem!.ctime),
|
Utils.dateFormat(replyItem!.ctime),
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
@ -210,10 +222,22 @@ class ReplyItem extends StatelessWidget {
|
|||||||
.copyWith(color: Theme.of(context).colorScheme.outline),
|
.copyWith(color: Theme.of(context).colorScheme.outline),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (replyControl!.isUpTop!)
|
if (replyItem!.upAction!.like!)
|
||||||
Icon(Icons.favorite, color: Colors.red[400], size: 18),
|
Icon(Icons.favorite, color: Colors.red[400], size: 18),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 35,
|
height: 28,
|
||||||
|
width: 42,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
child: Text('回复', style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelMedium),
|
||||||
|
onPressed: () => weakUpReply!(replyItem, replyLevel),
|
||||||
|
)),
|
||||||
|
SizedBox(
|
||||||
|
height: 32,
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -314,6 +338,10 @@ class ReplyItemRow extends StatelessWidget {
|
|||||||
: TextOverflow.visible,
|
: TextOverflow.visible,
|
||||||
maxLines: extraRow == 1 ? 2 : null,
|
maxLines: extraRow == 1 ? 2 : null,
|
||||||
TextSpan(
|
TextSpan(
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
replyReply(context);
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: replies![index].member.uname + ' ',
|
text: replies![index].member.uname + ' ',
|
||||||
@ -395,7 +423,11 @@ InlineSpan buildContent(BuildContext context, content) {
|
|||||||
content.jumpUrl.isEmpty &&
|
content.jumpUrl.isEmpty &&
|
||||||
content.vote.isEmpty &&
|
content.vote.isEmpty &&
|
||||||
content.pictures.isEmpty) {
|
content.pictures.isEmpty) {
|
||||||
return TextSpan(text: content.message);
|
return TextSpan(text: content.message,
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = ()=> {
|
||||||
|
print('点击')
|
||||||
|
},);
|
||||||
}
|
}
|
||||||
List<InlineSpan> spanChilds = [];
|
List<InlineSpan> spanChilds = [];
|
||||||
// 匹配表情
|
// 匹配表情
|
||||||
@ -635,7 +667,7 @@ class UpTag extends StatelessWidget {
|
|||||||
Color primary = Theme.of(context).colorScheme.primary;
|
Color primary = Theme.of(context).colorScheme.primary;
|
||||||
return Container(
|
return Container(
|
||||||
width: 24,
|
width: 24,
|
||||||
height: 15,
|
height: 14,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(3),
|
borderRadius: BorderRadius.circular(3),
|
||||||
color: tagText == 'UP' ? primary : null,
|
color: tagText == 'UP' ? primary : null,
|
||||||
@ -645,7 +677,7 @@ class UpTag extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
tagText!,
|
tagText!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 9,
|
||||||
color: tagText == 'UP'
|
color: tagText == 'UP'
|
||||||
? Theme.of(context).colorScheme.onPrimary
|
? Theme.of(context).colorScheme.onPrimary
|
||||||
: primary,
|
: primary,
|
||||||
|
Reference in New Issue
Block a user