mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-23 02:26:52 +08:00
@@ -619,20 +619,18 @@ class _ArticlePageState extends State<ArticlePage>
|
||||
onReply: (replyItem) => _articleCtr.onReply(
|
||||
context,
|
||||
replyItem: replyItem,
|
||||
index: index,
|
||||
),
|
||||
onDelete: (subIndex) =>
|
||||
_articleCtr.onRemove(index, subIndex),
|
||||
onDelete: (item, subIndex) =>
|
||||
_articleCtr.onRemove(index, item, subIndex),
|
||||
upMid: _articleCtr.upMid,
|
||||
callback: _getImageCallback,
|
||||
onCheckReply: (item) =>
|
||||
_articleCtr.onCheckReply(context, item, isManual: true),
|
||||
onToggleTop: (isUpTop, rpid) => _articleCtr.onToggleTop(
|
||||
onToggleTop: (item) => _articleCtr.onToggleTop(
|
||||
item,
|
||||
index,
|
||||
_articleCtr.commentId,
|
||||
_articleCtr.commentType,
|
||||
isUpTop,
|
||||
rpid,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,14 +24,15 @@ abstract class CommonWhisperController<R>
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onSetTop(int index, bool isTop, SessionId sessionId) async {
|
||||
Future<void> onSetTop(
|
||||
Session item, int index, bool isTop, SessionId sessionId) async {
|
||||
var res = isTop
|
||||
? await ImGrpc.unpinSession(sessionId: sessionId)
|
||||
: await ImGrpc.pinSession(sessionId: sessionId);
|
||||
|
||||
if (res.isSuccess) {
|
||||
List<Session> list = loadingState.value.data!;
|
||||
list[index].isPinned = isTop ? false : true;
|
||||
item.isPinned = isTop ? false : true;
|
||||
if (!isTop) {
|
||||
list.insert(0, list.removeAt(index));
|
||||
}
|
||||
@@ -42,16 +43,15 @@ abstract class CommonWhisperController<R>
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onSetMute(int index, bool isMuted, Int64 talkerUid) async {
|
||||
Future<void> onSetMute(Session item, bool isMuted, Int64 talkerUid) async {
|
||||
var res = await MsgHttp.setMsgDnd(
|
||||
uid: Accounts.main.mid,
|
||||
setting: isMuted ? 0 : 1,
|
||||
dndUid: talkerUid,
|
||||
);
|
||||
if (res['status']) {
|
||||
loadingState
|
||||
..value.data![index].isMuted = !isMuted
|
||||
..refresh();
|
||||
item.isMuted = !isMuted;
|
||||
loadingState.refresh();
|
||||
SmartDialog.showToast('操作成功');
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
|
||||
@@ -11,9 +11,9 @@ abstract class MultiSelectController<R, T extends MultiSelectData>
|
||||
late final RxInt checkedCount = 0.obs;
|
||||
late final allSelected = false.obs;
|
||||
|
||||
void onSelect(int index, [bool disableSelect = true]) {
|
||||
void onSelect(T item, [bool disableSelect = true]) {
|
||||
List<T> list = loadingState.value.data!;
|
||||
list[index].checked = !(list[index].checked ?? false);
|
||||
item.checked = !(item.checked ?? false);
|
||||
checkedCount.value = list.where((item) => item.checked == true).length;
|
||||
loadingState.refresh();
|
||||
if (disableSelect) {
|
||||
|
||||
@@ -107,7 +107,6 @@ abstract class ReplyController<R> extends CommonListController<R, ReplyInfo> {
|
||||
BuildContext context, {
|
||||
int? oid,
|
||||
ReplyInfo? replyItem,
|
||||
int index = 0,
|
||||
int? replyType,
|
||||
}) {
|
||||
assert(replyItem != null || (oid != null && replyType != null));
|
||||
@@ -168,7 +167,7 @@ abstract class ReplyController<R> extends CommonListController<R, ReplyInfo> {
|
||||
if (oid != null) {
|
||||
list.insert(hasUpTop ? 1 : 0, replyInfo);
|
||||
} else {
|
||||
list[index]
|
||||
replyItem!
|
||||
..count += 1
|
||||
..replies.add(replyInfo);
|
||||
}
|
||||
@@ -188,12 +187,12 @@ abstract class ReplyController<R> extends CommonListController<R, ReplyInfo> {
|
||||
);
|
||||
}
|
||||
|
||||
void onRemove(int index, int? subIndex) {
|
||||
void onRemove(int index, ReplyInfo item, int? subIndex) {
|
||||
List<ReplyInfo> list = loadingState.value.data!;
|
||||
if (subIndex == null) {
|
||||
list.removeAt(index);
|
||||
} else {
|
||||
list[index]
|
||||
item
|
||||
..count -= 1
|
||||
..replies.removeAt(subIndex);
|
||||
}
|
||||
@@ -212,21 +211,21 @@ abstract class ReplyController<R> extends CommonListController<R, ReplyInfo> {
|
||||
}
|
||||
|
||||
Future<void> onToggleTop(
|
||||
ReplyInfo item,
|
||||
int index,
|
||||
oid,
|
||||
int type,
|
||||
bool isUpTop,
|
||||
int rpid,
|
||||
) async {
|
||||
bool isUpTop = item.replyControl.isUpTop;
|
||||
final res = await ReplyHttp.replyTop(
|
||||
oid: oid,
|
||||
type: type,
|
||||
rpid: rpid,
|
||||
rpid: item.id,
|
||||
isUpTop: isUpTop,
|
||||
);
|
||||
if (res['status']) {
|
||||
List<ReplyInfo> list = loadingState.value.data!;
|
||||
list[index].replyControl.isUpTop = !isUpTop;
|
||||
item.replyControl.isUpTop = !isUpTop;
|
||||
if (!isUpTop && index != 0) {
|
||||
list[0].replyControl.isUpTop = false;
|
||||
final item = list.removeAt(index);
|
||||
|
||||
@@ -730,20 +730,18 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||
onReply: (replyItem) => _controller.onReply(
|
||||
context,
|
||||
replyItem: replyItem,
|
||||
index: index,
|
||||
),
|
||||
onDelete: (subIndex) =>
|
||||
_controller.onRemove(index, subIndex),
|
||||
onDelete: (item, subIndex) =>
|
||||
_controller.onRemove(index, item, subIndex),
|
||||
upMid: _controller.upMid,
|
||||
callback: _getImageCallback,
|
||||
onCheckReply: (item) =>
|
||||
_controller.onCheckReply(context, item, isManual: true),
|
||||
onToggleTop: (isUpTop, rpid) => _controller.onToggleTop(
|
||||
onToggleTop: (item) => _controller.onToggleTop(
|
||||
item,
|
||||
index,
|
||||
_controller.oid,
|
||||
_controller.replyType,
|
||||
isUpTop,
|
||||
rpid,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,27 +61,28 @@ class _EmotePanelState extends State<EmotePanel>
|
||||
),
|
||||
itemCount: e.emote!.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = e.emote![index];
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
onTap: () => widget.onChoose(e.emote![index]),
|
||||
onTap: () => widget.onChoose(item),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: type == 4
|
||||
? Center(
|
||||
child: Text(
|
||||
e.emote![index].text!,
|
||||
item.text!,
|
||||
overflow: TextOverflow.clip,
|
||||
maxLines: 1,
|
||||
),
|
||||
)
|
||||
: NetworkImgLayer(
|
||||
src: e.emote![index].url!,
|
||||
src: item.url!,
|
||||
width: size * 38,
|
||||
height: size * 38,
|
||||
semanticsLabel: e.emote![index].text!,
|
||||
semanticsLabel: item.text!,
|
||||
type: ImageType.emote,
|
||||
boxFit: BoxFit.contain,
|
||||
),
|
||||
|
||||
@@ -67,17 +67,14 @@ class _FavArticlePageState extends State<FavArticlePage>
|
||||
if (index == response.length - 1) {
|
||||
_favArticleController.onLoadMore();
|
||||
}
|
||||
final item = response[index];
|
||||
return FavArticleItem(
|
||||
item: response[index],
|
||||
item: item,
|
||||
onDelete: () => showConfirmDialog(
|
||||
context: context,
|
||||
title: '确定取消收藏?',
|
||||
onConfirm: () {
|
||||
_favArticleController.onRemove(
|
||||
index,
|
||||
response[index].opusId,
|
||||
);
|
||||
},
|
||||
onConfirm: () =>
|
||||
_favArticleController.onRemove(index, item.opusId),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -151,10 +151,11 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
|
||||
if (index == response.length - 1) {
|
||||
_favNoteController.onLoadMore();
|
||||
}
|
||||
final item = response[index];
|
||||
return FavNoteItem(
|
||||
item: response[index],
|
||||
item: item,
|
||||
ctr: _favNoteController,
|
||||
onSelect: () => _favNoteController.onSelect(index),
|
||||
onSelect: () => _favNoteController.onSelect(item),
|
||||
);
|
||||
},
|
||||
childCount: response!.length,
|
||||
|
||||
@@ -17,8 +17,8 @@ class FavNoteController
|
||||
}
|
||||
|
||||
@override
|
||||
void onSelect(int index, [bool disableSelect = true]) {
|
||||
super.onSelect(index, false);
|
||||
void onSelect(FavNoteItemModel item, [bool disableSelect = true]) {
|
||||
super.onSelect(item, false);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -178,7 +178,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
|
||||
return FavPgcItem(
|
||||
item: item,
|
||||
ctr: _favPgcController,
|
||||
onSelect: () => _favPgcController.onSelect(index),
|
||||
onSelect: () => _favPgcController.onSelect(item),
|
||||
onUpdateStatus: () => showPgcFollowDialog(
|
||||
context: context,
|
||||
type: widget.type == 0 ? '追番' : '追剧',
|
||||
|
||||
@@ -24,8 +24,8 @@ class FavPgcController
|
||||
}
|
||||
|
||||
@override
|
||||
void onSelect(int index, [bool disableSelect = true]) {
|
||||
super.onSelect(index, false);
|
||||
void onSelect(FavPgcItemModel item, [bool disableSelect = true]) {
|
||||
super.onSelect(item, false);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -477,7 +477,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
);
|
||||
},
|
||||
onTap: _favDetailController.enableMultiSelect.value
|
||||
? () => _favDetailController.onSelect(index)
|
||||
? () => _favDetailController.onSelect(item)
|
||||
: null,
|
||||
onLongPress:
|
||||
_favDetailController.isOwner.value == true
|
||||
@@ -486,7 +486,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
.enableMultiSelect.value) {
|
||||
_favDetailController
|
||||
.enableMultiSelect.value = true;
|
||||
_favDetailController.onSelect(index);
|
||||
_favDetailController.onSelect(item);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
|
||||
@@ -100,14 +100,14 @@ class _FollowChildPageState extends State<FollowChildPage>
|
||||
if (index == response.length - 1) {
|
||||
_followController.onLoadMore();
|
||||
}
|
||||
final item = response[index];
|
||||
return FollowItem(
|
||||
item: response[index],
|
||||
item: item,
|
||||
isOwner: widget.controller?.isOwner,
|
||||
onSelect: widget.onSelect,
|
||||
callback: (attr) {
|
||||
_followController.loadingState
|
||||
..value.data![index].attribute = attr == 0 ? -1 : 0
|
||||
..refresh();
|
||||
item.attribute = attr == 0 ? -1 : 0;
|
||||
_followController.loadingState.refresh();
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -70,12 +70,11 @@ class FollowController extends GetxController with GetTickerProviderStateMixin {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onUpdateTag(int index, tagid, String tagName) async {
|
||||
final res = await MemberHttp.updateFollowTag(tagid, tagName);
|
||||
Future<void> onUpdateTag(MemberTagItemModel item, String tagName) async {
|
||||
final res = await MemberHttp.updateFollowTag(item.tagid, tagName);
|
||||
if (res['status']) {
|
||||
tabs
|
||||
..[index].name = tagName
|
||||
..refresh();
|
||||
item.name = tagName;
|
||||
tabs.refresh();
|
||||
SmartDialog.showToast('修改成功');
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
|
||||
@@ -179,8 +179,7 @@ class _FollowPageState extends State<FollowPage> {
|
||||
),
|
||||
onConfirm: () {
|
||||
if (tagName.isNotEmpty) {
|
||||
_followController.onUpdateTag(
|
||||
index, item.tagid, tagName);
|
||||
_followController.onUpdateTag(item, tagName);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -41,9 +41,9 @@ class HistoryController
|
||||
}
|
||||
|
||||
@override
|
||||
void onSelect(int index, [bool disableSelect = true]) {
|
||||
void onSelect(HistoryItemModel item, [bool disableSelect = true]) {
|
||||
List<HistoryItemModel> list = loadingState.value.data!;
|
||||
list[index].checked = !(list[index].checked ?? false);
|
||||
item.checked = !(item.checked ?? false);
|
||||
baseCtr.checkedCount.value =
|
||||
list.where((item) => item.checked == true).length;
|
||||
loadingState.refresh();
|
||||
|
||||
@@ -273,7 +273,7 @@ class _HistoryPageState extends State<HistoryPage>
|
||||
return HistoryItem(
|
||||
item: item,
|
||||
ctr: _historyController.baseCtr,
|
||||
onChoose: () => _historyController.onSelect(index),
|
||||
onChoose: () => _historyController.onSelect(item),
|
||||
onDelete: (kid, business) =>
|
||||
_historyController.delHistory(item),
|
||||
);
|
||||
|
||||
@@ -101,13 +101,13 @@ class _LaterViewChildPageState extends State<LaterViewChildPage>
|
||||
},
|
||||
onTap: !_laterController.baseCtr.enableMultiSelect.value
|
||||
? null
|
||||
: () => _laterController.onSelect(index),
|
||||
: () => _laterController.onSelect(videoItem),
|
||||
onLongPress: () {
|
||||
if (!_laterController
|
||||
.baseCtr.enableMultiSelect.value) {
|
||||
_laterController.baseCtr.enableMultiSelect.value =
|
||||
true;
|
||||
_laterController.onSelect(index);
|
||||
_laterController.onSelect(videoItem);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -35,9 +35,9 @@ class LaterController extends MultiSelectController<LaterData, LaterItemModel> {
|
||||
);
|
||||
|
||||
@override
|
||||
void onSelect(int index, [bool disableSelect = true]) {
|
||||
void onSelect(LaterItemModel item, [bool disableSelect = true]) {
|
||||
List<LaterItemModel> list = loadingState.value.data!;
|
||||
list[index].checked = !(list[index].checked ?? false);
|
||||
item.checked = !(item.checked ?? false);
|
||||
baseCtr.checkedCount.value =
|
||||
list.where((item) => item.checked == true).length;
|
||||
loadingState.refresh();
|
||||
|
||||
@@ -73,6 +73,7 @@ class _LiveEmotePanelState extends State<LiveEmotePanel>
|
||||
),
|
||||
itemCount: item.emoticons!.length,
|
||||
itemBuilder: (context, index) {
|
||||
final e = item.emoticons![index];
|
||||
return Material(
|
||||
type: MaterialType.transparency,
|
||||
child: InkWell(
|
||||
@@ -80,18 +81,16 @@ class _LiveEmotePanelState extends State<LiveEmotePanel>
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
onTap: () {
|
||||
if (item.pkgType == 3) {
|
||||
widget.onChoose(item.emoticons![index]);
|
||||
widget.onChoose(e);
|
||||
} else {
|
||||
widget.onSendEmoticonUnique(
|
||||
item.emoticons![index],
|
||||
);
|
||||
widget.onSendEmoticonUnique(e);
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(6),
|
||||
child: NetworkImgLayer(
|
||||
boxFit: BoxFit.contain,
|
||||
src: item.emoticons![index].url!,
|
||||
src: e.url!,
|
||||
width: widthFac * 38,
|
||||
height: heightFac * 38,
|
||||
type: ImageType.emote,
|
||||
|
||||
@@ -32,6 +32,7 @@ class LiveRoomChat extends StatelessWidget {
|
||||
itemCount: liveRoomController.messages.length,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
final item = liveRoomController.messages[index];
|
||||
return Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
@@ -48,8 +49,7 @@ class LiveRoomChat extends StatelessWidget {
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text:
|
||||
'${liveRoomController.messages[index]['name']}: ',
|
||||
text: '${item['name']}: ',
|
||||
style: TextStyle(
|
||||
color: Colors.white.withValues(alpha: 0.6),
|
||||
fontSize: 14,
|
||||
@@ -57,14 +57,13 @@ class LiveRoomChat extends StatelessWidget {
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
try {
|
||||
Get.toNamed(
|
||||
'/member?mid=${liveRoomController.messages[index]['uid']}');
|
||||
Get.toNamed('/member?mid=${item['uid']}');
|
||||
} catch (err) {
|
||||
if (kDebugMode) debugPrint(err.toString());
|
||||
}
|
||||
},
|
||||
),
|
||||
_buildMsg(liveRoomController.messages[index]),
|
||||
_buildMsg(item),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -213,19 +213,17 @@ class _MatchInfoPageState extends State<MatchInfoPage> {
|
||||
onReply: (replyItem) => _controller.onReply(
|
||||
context,
|
||||
replyItem: replyItem,
|
||||
index: index,
|
||||
),
|
||||
onDelete: (subIndex) =>
|
||||
_controller.onRemove(index, subIndex),
|
||||
onDelete: (item, subIndex) =>
|
||||
_controller.onRemove(index, item, subIndex),
|
||||
upMid: _controller.upMid,
|
||||
onCheckReply: (item) => _controller
|
||||
.onCheckReply(context, item, isManual: true),
|
||||
onToggleTop: (isUpTop, rpid) => _controller.onToggleTop(
|
||||
onToggleTop: (item) => _controller.onToggleTop(
|
||||
item,
|
||||
index,
|
||||
_controller.cid,
|
||||
_controller.replyType,
|
||||
isUpTop,
|
||||
rpid,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -207,9 +207,8 @@ class _MediaPageState extends CommonPageState<MediaPage, MediaController>
|
||||
),
|
||||
);
|
||||
} else {
|
||||
String heroTag = Utils.makeHeroTag(response.list[index].fid);
|
||||
return FavFolderItem(
|
||||
heroTag: heroTag,
|
||||
heroTag: Utils.generateRandomString(8),
|
||||
item: response.list[index],
|
||||
callback: () => Future.delayed(
|
||||
const Duration(milliseconds: 150),
|
||||
|
||||
@@ -78,17 +78,11 @@ class LikeMeController extends CommonDataController<MsgLikeData,
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> onSetNotice(
|
||||
int? id, int index, bool isNotice, bool isLatest) async {
|
||||
Future<void> onSetNotice(MsgLikeItem item, bool isNotice) async {
|
||||
int noticeState = isNotice ? 1 : 0;
|
||||
var res = await MsgHttp.msgSetNotice(id: id, noticeState: noticeState);
|
||||
var res = await MsgHttp.msgSetNotice(id: item.id, noticeState: noticeState);
|
||||
if (res['status']) {
|
||||
Pair<List<MsgLikeItem>, List<MsgLikeItem>> pair = loadingState.value.data;
|
||||
if (isLatest) {
|
||||
pair.first[index].noticeState = noticeState;
|
||||
} else {
|
||||
pair.second[index].noticeState = noticeState;
|
||||
}
|
||||
item.noticeState = noticeState;
|
||||
loadingState.refresh();
|
||||
SmartDialog.showToast('操作成功');
|
||||
} else {
|
||||
|
||||
@@ -99,10 +99,6 @@ class _LikeMePageState extends State<LikeMePage> {
|
||||
(id) {
|
||||
_likeMeController.onRemove(id, index, true);
|
||||
},
|
||||
(isNotice, id) {
|
||||
_likeMeController.onSetNotice(
|
||||
id, index, isNotice, true);
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: latest.length,
|
||||
@@ -122,10 +118,6 @@ class _LikeMePageState extends State<LikeMePage> {
|
||||
(id) {
|
||||
_likeMeController.onRemove(id, index, false);
|
||||
},
|
||||
(isNotice, id) {
|
||||
_likeMeController.onSetNotice(
|
||||
id, index, isNotice, false);
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: total.length,
|
||||
@@ -167,7 +159,6 @@ class _LikeMePageState extends State<LikeMePage> {
|
||||
ThemeData theme,
|
||||
MsgLikeItem item,
|
||||
ValueChanged<int?> onRemove,
|
||||
Function(bool isNotice, int? id) onSetNotice,
|
||||
) {
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
@@ -223,10 +214,11 @@ class _LikeMePageState extends State<LikeMePage> {
|
||||
context: context,
|
||||
title: '不再通知',
|
||||
content: '这条内容的点赞将不再通知,但仍可在列表内查看,是否继续?',
|
||||
onConfirm: () => onSetNotice(isNotice, item.id),
|
||||
onConfirm: () =>
|
||||
_likeMeController.onSetNotice(item, isNotice),
|
||||
);
|
||||
} else {
|
||||
onSetNotice(isNotice, item.id);
|
||||
_likeMeController.onSetNotice(item, isNotice);
|
||||
}
|
||||
},
|
||||
dense: true,
|
||||
|
||||
@@ -58,13 +58,12 @@ class PgcReviewController
|
||||
sort: sortType.value.sort,
|
||||
);
|
||||
|
||||
Future<void> onLike(int index, bool isLike, reviewId) async {
|
||||
Future<void> onLike(PgcReviewItemModel item, bool isLike, reviewId) async {
|
||||
var res = await PgcHttp.pgcReviewLike(
|
||||
mediaId: mediaId,
|
||||
reviewId: reviewId,
|
||||
);
|
||||
if (res['status']) {
|
||||
final item = loadingState.value.data![index];
|
||||
int likes = item.stat?.likes ?? 0;
|
||||
item.stat
|
||||
?..liked = isLike ? 0 : 1
|
||||
@@ -78,13 +77,13 @@ class PgcReviewController
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onDislike(int index, bool isDislike, reviewId) async {
|
||||
Future<void> onDislike(
|
||||
PgcReviewItemModel item, bool isDislike, reviewId) async {
|
||||
var res = await PgcHttp.pgcReviewDislike(
|
||||
mediaId: mediaId,
|
||||
reviewId: reviewId,
|
||||
);
|
||||
if (res['status']) {
|
||||
final item = loadingState.value.data![index];
|
||||
item.stat?.disliked = isDislike ? 0 : 1;
|
||||
if (!isDislike) {
|
||||
if (item.stat?.liked == 1) {
|
||||
|
||||
@@ -312,7 +312,7 @@ class _PgcReviewChildPageState extends State<PgcReviewChildPage>
|
||||
child: TextButton(
|
||||
style: style,
|
||||
onPressed: () => _controller.onDislike(
|
||||
index, isDislike, item.reviewId),
|
||||
item, isDislike, item.reviewId),
|
||||
child: Icon(
|
||||
isDislike
|
||||
? FontAwesomeIcons.solidThumbsDown
|
||||
@@ -329,7 +329,7 @@ class _PgcReviewChildPageState extends State<PgcReviewChildPage>
|
||||
onPressed: isLongReview
|
||||
? null
|
||||
: () => _controller.onLike(
|
||||
index, isLike, item.reviewId),
|
||||
item, isLike, item.reviewId),
|
||||
child: Row(
|
||||
spacing: 4,
|
||||
children: [
|
||||
|
||||
@@ -35,17 +35,20 @@ class SelectDialog<T> extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: List.generate(
|
||||
values.length,
|
||||
(index) => RadioListTile<T>(
|
||||
dense: true,
|
||||
value: values[index].$1,
|
||||
title: Text(
|
||||
values[index].$2,
|
||||
style: Theme.of(context).textTheme.titleMedium!,
|
||||
),
|
||||
subtitle: subtitleBuilder?.call(context, index),
|
||||
groupValue: value,
|
||||
onChanged: Navigator.of(context).pop,
|
||||
),
|
||||
(index) {
|
||||
final item = values[index];
|
||||
return RadioListTile<T>(
|
||||
dense: true,
|
||||
value: item.$1,
|
||||
title: Text(
|
||||
item.$2,
|
||||
style: Theme.of(context).textTheme.titleMedium!,
|
||||
),
|
||||
subtitle: subtitleBuilder?.call(context, index),
|
||||
groupValue: value,
|
||||
onChanged: Navigator.of(context).pop,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -166,7 +169,8 @@ class _CdnSelectDialogState extends State<CdnSelectDialog> {
|
||||
}
|
||||
|
||||
void _handleSpeedTestError(dynamic error, int index) {
|
||||
if (_cdnResList[index].value != null) return;
|
||||
final item = _cdnResList[index];
|
||||
if (item.value != null) return;
|
||||
|
||||
if (kDebugMode) debugPrint('CDN speed test error: $error');
|
||||
if (!mounted) return;
|
||||
@@ -174,7 +178,7 @@ class _CdnSelectDialogState extends State<CdnSelectDialog> {
|
||||
if (message.isEmpty) {
|
||||
message = '测速失败';
|
||||
}
|
||||
_cdnResList[index].value = message;
|
||||
item.value = message;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -184,17 +188,20 @@ class _CdnSelectDialogState extends State<CdnSelectDialog> {
|
||||
values: CDNService.values.map((i) => (i.code, i.desc)).toList(),
|
||||
value: VideoUtils.cdnService,
|
||||
subtitleBuilder: _cdnSpeedTest
|
||||
? (context, index) => ValueListenableBuilder(
|
||||
valueListenable: _cdnResList[index],
|
||||
? (context, index) {
|
||||
final item = _cdnResList[index];
|
||||
return ValueListenableBuilder(
|
||||
valueListenable: item,
|
||||
builder: (context, value, _) {
|
||||
return Text(
|
||||
_cdnResList[index].value ?? '---',
|
||||
item.value ?? '---',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ class _SharePanelState extends State<SharePanel> {
|
||||
itemCount: _userList.length,
|
||||
controller: _scrollController,
|
||||
childBuilder: (index) {
|
||||
final item = _userList[index];
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
_selectedIndex = index;
|
||||
@@ -131,13 +132,13 @@ class _SharePanelState extends State<SharePanel> {
|
||||
child: NetworkImgLayer(
|
||||
width: 40,
|
||||
height: 40,
|
||||
src: _userList[index].avatar,
|
||||
src: item.avatar,
|
||||
type: ImageType.avatar,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
_userList[index].name,
|
||||
item.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(fontSize: 12),
|
||||
|
||||
@@ -88,12 +88,13 @@ class _AiDetailState extends CommonCollapseSlidePageState<AiConclusionPanel> {
|
||||
sliver: SliverList.builder(
|
||||
itemCount: widget.item.outline!.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = widget.item.outline![index];
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (index != 0) const SizedBox(height: 10),
|
||||
SelectableText(
|
||||
widget.item.outline![index].title!,
|
||||
item.title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -101,9 +102,8 @@ class _AiDetailState extends CommonCollapseSlidePageState<AiConclusionPanel> {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
if (widget.item.outline![index].partOutline?.isNotEmpty ==
|
||||
true)
|
||||
...widget.item.outline![index].partOutline!.map(
|
||||
if (item.partOutline?.isNotEmpty == true)
|
||||
...item.partOutline!.map(
|
||||
(item) => Wrap(
|
||||
children: [
|
||||
SelectableText.rich(
|
||||
|
||||
@@ -384,17 +384,16 @@ class VideoDetailController extends GetxController
|
||||
: null,
|
||||
onDelete: sourceType == 'watchLater' ||
|
||||
(sourceType == 'fav' && Get.arguments?['isOwner'] == true)
|
||||
? (index) async {
|
||||
? (item, index) async {
|
||||
if (sourceType == 'watchLater') {
|
||||
var res = await UserHttp.toViewDel(
|
||||
aids: [mediaList[index].aid],
|
||||
aids: [item.aid],
|
||||
);
|
||||
if (res['status']) {
|
||||
mediaList.removeAt(index);
|
||||
}
|
||||
SmartDialog.showToast(res['msg']);
|
||||
} else {
|
||||
final item = mediaList[index];
|
||||
var res = await FavHttp.delFav(
|
||||
ids: ['${item.aid}:${item.type}'],
|
||||
delIds: '${Get.arguments?['mediaId']}',
|
||||
|
||||
@@ -8,6 +8,8 @@ import 'package:PiliPlus/common/widgets/stat/stat.dart';
|
||||
import 'package:PiliPlus/models/common/image_type.dart';
|
||||
import 'package:PiliPlus/models/common/stat_type.dart';
|
||||
import 'package:PiliPlus/models_new/video/video_detail/data.dart';
|
||||
import 'package:PiliPlus/models_new/video/video_detail/staff.dart';
|
||||
import 'package:PiliPlus/models_new/video/video_tag/data.dart';
|
||||
import 'package:PiliPlus/pages/mine/controller.dart';
|
||||
import 'package:PiliPlus/pages/search/widgets/search_text.dart';
|
||||
import 'package:PiliPlus/pages/video/controller.dart';
|
||||
@@ -292,73 +294,7 @@ class _VideoInfoState extends State<VideoInfo> {
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: GestureDetector(
|
||||
onTap: onPushMember,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Obx(() => PendantAvatar(
|
||||
avatar: videoIntroController
|
||||
.userStat['card']?['face'],
|
||||
size: 35,
|
||||
badgeSize: 14,
|
||||
isVip: (videoIntroController
|
||||
.userStat['card']
|
||||
?['vip']?['status'] ??
|
||||
-1) >
|
||||
0,
|
||||
officialType: videoIntroController
|
||||
.userStat['card']
|
||||
?['official_verify']?['type'],
|
||||
// garbPendantImage: videoIntroController.userStat.value['card']?['pendant']?['image'],
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Obx(
|
||||
() => Text(
|
||||
videoIntroController
|
||||
.userStat['card']
|
||||
?['name'] ??
|
||||
"",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: (videoIntroController.userStat[
|
||||
'card']
|
||||
?['vip']
|
||||
?['status'] ??
|
||||
-1) >
|
||||
0 &&
|
||||
videoIntroController
|
||||
.userStat[
|
||||
'card']?[
|
||||
'vip']?['type'] ==
|
||||
2
|
||||
? context.vipColor
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 0),
|
||||
Obx(
|
||||
() => Text(
|
||||
'${NumUtil.numFormat(videoIntroController.userStat['follower'])}粉丝 ${videoIntroController.userStat['archive_count'] != null ? '${NumUtil.numFormat(videoIntroController.userStat['archive_count'])}视频' : ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
child: _buildAvatar(theme),
|
||||
),
|
||||
),
|
||||
followButton(context, theme),
|
||||
@@ -367,152 +303,10 @@ class _VideoInfoState extends State<VideoInfo> {
|
||||
child: SelfSizedHorizontalList(
|
||||
gapSize: 25,
|
||||
itemCount: videoItem['staff'].length,
|
||||
childBuilder: (index) => GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
int? ownerMid = !widget.isLoading
|
||||
? videoDetail.owner?.mid
|
||||
: videoItem['owner']?.mid;
|
||||
if (videoItem['staff'][index].mid ==
|
||||
ownerMid &&
|
||||
context.orientation ==
|
||||
Orientation.landscape &&
|
||||
_horizontalMemberPage) {
|
||||
widget.onShowMemberPage(ownerMid);
|
||||
} else {
|
||||
Get.toNamed(
|
||||
'/member?mid=${videoItem['staff'][index].mid}&from_view_aid=${videoDetailCtr.oid.value}',
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: ImageType.avatar,
|
||||
src: videoItem['staff'][index].face,
|
||||
width: 35,
|
||||
height: 35,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
if ((videoItem['staff'][index]
|
||||
.official
|
||||
?.type ??
|
||||
-1) !=
|
||||
-1)
|
||||
Positioned(
|
||||
right: -2,
|
||||
bottom: -2,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color:
|
||||
theme.colorScheme.surface,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.offline_bolt,
|
||||
color: videoItem['staff'][index]
|
||||
.official
|
||||
?.type ==
|
||||
0
|
||||
? const Color(0xFFFFCC00)
|
||||
: Colors.lightBlueAccent,
|
||||
size: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: -6,
|
||||
child: Obx(() => videoIntroController
|
||||
.staffRelations[
|
||||
'status'] ==
|
||||
true &&
|
||||
videoIntroController
|
||||
.staffRelations[
|
||||
'${videoItem['staff'][index].mid}'] ==
|
||||
null
|
||||
? Material(
|
||||
type:
|
||||
MaterialType.transparency,
|
||||
shape: const CircleBorder(),
|
||||
child: InkWell(
|
||||
customBorder:
|
||||
const CircleBorder(),
|
||||
onTap: () => RequestUtils
|
||||
.actionRelationMod(
|
||||
context: context,
|
||||
mid: videoItem['staff']
|
||||
[index]
|
||||
.mid,
|
||||
isFollow: false,
|
||||
callback: (val) {
|
||||
videoIntroController
|
||||
.staffRelations[
|
||||
'${videoItem['staff'][index].mid}'] = true;
|
||||
},
|
||||
),
|
||||
child: Ink(
|
||||
padding:
|
||||
const EdgeInsets.all(
|
||||
2),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme
|
||||
.secondaryContainer,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
MdiIcons.plus,
|
||||
size: 16,
|
||||
color: theme.colorScheme
|
||||
.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
videoItem['staff'][index].name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: videoItem['staff'][index]
|
||||
.vip
|
||||
.status >
|
||||
0 &&
|
||||
videoItem['staff'][index]
|
||||
.vip
|
||||
.type ==
|
||||
2
|
||||
? context.vipColor
|
||||
: null,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
videoItem['staff'][index].title,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
childBuilder: (index) {
|
||||
return _buildStaff(
|
||||
theme, videoItem['staff'][index]);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (isHorizontal) ...[
|
||||
@@ -554,88 +348,8 @@ class _VideoInfoState extends State<VideoInfo> {
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
StatWidget(
|
||||
type: StatType.play,
|
||||
value: !widget.isLoading
|
||||
? videoDetail.stat?.view
|
||||
: videoItem['stat']?.view,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
StatWidget(
|
||||
type: StatType.danmaku,
|
||||
value: !widget.isLoading
|
||||
? videoDetail.stat?.danmaku
|
||||
: videoItem['stat']?.danmu,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
Text(
|
||||
DateUtil.format(
|
||||
!widget.isLoading
|
||||
? videoDetail.pubdate
|
||||
: videoItem['pubdate'],
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
if (MineController.anonymity.value)
|
||||
Icon(
|
||||
MdiIcons.incognito,
|
||||
size: 15,
|
||||
color: theme.colorScheme.outline,
|
||||
semanticLabel: '无痕',
|
||||
),
|
||||
if (videoIntroController.isShowOnlineTotal)
|
||||
Obx(
|
||||
() => Text(
|
||||
'${videoIntroController.total.value}人在看',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (videoIntroController.enableAi)
|
||||
Positioned(
|
||||
right: 10,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Center(
|
||||
child: Semantics(
|
||||
label: 'AI总结',
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
if (videoIntroController.aiConclusionResult ==
|
||||
null) {
|
||||
await videoIntroController.aiConclusion();
|
||||
}
|
||||
if (videoIntroController.aiConclusionResult ==
|
||||
null) {
|
||||
return;
|
||||
}
|
||||
if (videoIntroController.aiConclusionResult!
|
||||
.summary?.isNotEmpty ==
|
||||
true ||
|
||||
videoIntroController.aiConclusionResult!
|
||||
.outline?.isNotEmpty ==
|
||||
true) {
|
||||
widget.showAiBottomSheet();
|
||||
} else {
|
||||
SmartDialog.showToast("当前视频不支持AI视频总结");
|
||||
}
|
||||
},
|
||||
child: Image.asset('assets/images/ai.png',
|
||||
height: 22),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
_buildInfo(theme),
|
||||
if (videoIntroController.enableAi) _aiBtn,
|
||||
],
|
||||
),
|
||||
if (videoIntroController.videoDetail.value.argueInfo?.argueMsg
|
||||
@@ -704,31 +418,7 @@ class _VideoInfoState extends State<VideoInfo> {
|
||||
],
|
||||
if (videoIntroController.videoTags?.isNotEmpty ==
|
||||
true) ...[
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: videoIntroController.videoTags!
|
||||
.map(
|
||||
(item) => SearchText(
|
||||
fontSize: 13,
|
||||
text: item.tagName!,
|
||||
onTap: (tagName) => Get.toNamed(
|
||||
'/searchResult',
|
||||
parameters: {'keyword': tagName},
|
||||
),
|
||||
onLongPress: Utils.copyText,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildTags(videoIntroController.videoTags!),
|
||||
],
|
||||
],
|
||||
),
|
||||
@@ -806,7 +496,7 @@ class _VideoInfoState extends State<VideoInfo> {
|
||||
);
|
||||
}
|
||||
|
||||
Obx followButton(BuildContext context, ThemeData t) {
|
||||
Widget followButton(BuildContext context, ThemeData t) {
|
||||
return Obx(
|
||||
() {
|
||||
int attr = videoIntroController.followStatus['attribute'] ?? 0;
|
||||
@@ -1029,4 +719,285 @@ class _VideoInfoState extends State<VideoInfo> {
|
||||
});
|
||||
return TextSpan(children: spanChildren);
|
||||
}
|
||||
|
||||
Widget _buildStaff(ThemeData theme, Staff item) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
int? ownerMid = !widget.isLoading
|
||||
? videoDetail.owner?.mid
|
||||
: videoItem['owner']?.mid;
|
||||
if (item.mid == ownerMid &&
|
||||
context.orientation == Orientation.landscape &&
|
||||
_horizontalMemberPage) {
|
||||
widget.onShowMemberPage(ownerMid);
|
||||
} else {
|
||||
Get.toNamed(
|
||||
'/member?mid=${item.mid}&from_view_aid=${videoDetailCtr.oid.value}',
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: ImageType.avatar,
|
||||
src: item.face,
|
||||
width: 35,
|
||||
height: 35,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
if ((item.official?.type ?? -1) != -1)
|
||||
Positioned(
|
||||
right: -2,
|
||||
bottom: -2,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: theme.colorScheme.surface,
|
||||
),
|
||||
child: Icon(
|
||||
Icons.offline_bolt,
|
||||
color: item.official?.type == 0
|
||||
? const Color(0xFFFFCC00)
|
||||
: Colors.lightBlueAccent,
|
||||
size: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: -6,
|
||||
child: Obx(() =>
|
||||
videoIntroController.staffRelations['status'] == true &&
|
||||
videoIntroController
|
||||
.staffRelations['${item.mid}'] ==
|
||||
null
|
||||
? Material(
|
||||
type: MaterialType.transparency,
|
||||
shape: const CircleBorder(),
|
||||
child: InkWell(
|
||||
customBorder: const CircleBorder(),
|
||||
onTap: () => RequestUtils.actionRelationMod(
|
||||
context: context,
|
||||
mid: item.mid,
|
||||
isFollow: false,
|
||||
callback: (val) {
|
||||
videoIntroController
|
||||
.staffRelations['${item.mid}'] = true;
|
||||
},
|
||||
),
|
||||
child: Ink(
|
||||
padding: const EdgeInsets.all(2),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.secondaryContainer,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
MdiIcons.plus,
|
||||
size: 16,
|
||||
color: theme.colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
item.name!,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: (item.vip?.status ?? 0) > 0 && item.vip?.type == 2
|
||||
? context.vipColor
|
||||
: null,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
item.title!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAvatar(ThemeData theme) => GestureDetector(
|
||||
onTap: onPushMember,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Obx(() => PendantAvatar(
|
||||
avatar: videoIntroController.userStat['card']?['face'],
|
||||
size: 35,
|
||||
badgeSize: 14,
|
||||
isVip: (videoIntroController.userStat['card']?['vip']
|
||||
?['status'] ??
|
||||
-1) >
|
||||
0,
|
||||
officialType: videoIntroController.userStat['card']
|
||||
?['official_verify']?['type'],
|
||||
// garbPendantImage: videoIntroController.userStat.value['card']?['pendant']?['image'],
|
||||
)),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Obx(
|
||||
() => Text(
|
||||
videoIntroController.userStat['card']?['name'] ?? "",
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: (videoIntroController.userStat['card']?['vip']
|
||||
?['status'] ??
|
||||
-1) >
|
||||
0 &&
|
||||
videoIntroController.userStat['card']?['vip']
|
||||
?['type'] ==
|
||||
2
|
||||
? context.vipColor
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 0),
|
||||
Obx(
|
||||
() => Text(
|
||||
'${NumUtil.numFormat(videoIntroController.userStat['follower'])}粉丝 ${videoIntroController.userStat['archive_count'] != null ? '${NumUtil.numFormat(videoIntroController.userStat['archive_count'])}视频' : ''}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildInfo(ThemeData theme) => Row(
|
||||
spacing: 10,
|
||||
children: [
|
||||
StatWidget(
|
||||
type: StatType.play,
|
||||
value: !widget.isLoading
|
||||
? videoDetail.stat?.view
|
||||
: videoItem['stat']?.view,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
StatWidget(
|
||||
type: StatType.danmaku,
|
||||
value: !widget.isLoading
|
||||
? videoDetail.stat?.danmaku
|
||||
: videoItem['stat']?.danmu,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
Text(
|
||||
DateUtil.format(
|
||||
!widget.isLoading ? videoDetail.pubdate : videoItem['pubdate'],
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
if (MineController.anonymity.value)
|
||||
Icon(
|
||||
MdiIcons.incognito,
|
||||
size: 15,
|
||||
color: theme.colorScheme.outline,
|
||||
semanticLabel: '无痕',
|
||||
),
|
||||
if (videoIntroController.isShowOnlineTotal)
|
||||
Obx(
|
||||
() => Text(
|
||||
'${videoIntroController.total.value}人在看',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
Widget get _aiBtn => Positioned(
|
||||
right: 10,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Center(
|
||||
child: Semantics(
|
||||
label: 'AI总结',
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
if (videoIntroController.aiConclusionResult == null) {
|
||||
await videoIntroController.aiConclusion();
|
||||
}
|
||||
if (videoIntroController.aiConclusionResult == null) {
|
||||
return;
|
||||
}
|
||||
if (videoIntroController
|
||||
.aiConclusionResult!.summary?.isNotEmpty ==
|
||||
true ||
|
||||
videoIntroController
|
||||
.aiConclusionResult!.outline?.isNotEmpty ==
|
||||
true) {
|
||||
widget.showAiBottomSheet();
|
||||
} else {
|
||||
SmartDialog.showToast("当前视频不支持AI视频总结");
|
||||
}
|
||||
},
|
||||
child: Image.asset('assets/images/ai.png', height: 22),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Widget _buildTags(List<VideoTagItem> tags) {
|
||||
return GestureDetector(
|
||||
onTap: () {},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: tags
|
||||
.map(
|
||||
(item) => SearchText(
|
||||
fontSize: 13,
|
||||
text: item.tagName!,
|
||||
onTap: (tagName) => Get.toNamed(
|
||||
'/searchResult',
|
||||
parameters: {'keyword': tagName},
|
||||
),
|
||||
onLongPress: Utils.copyText,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,7 @@ class _PagesPanelState extends State<PagesPanel> {
|
||||
itemExtent: 150,
|
||||
itemBuilder: (BuildContext context, int i) {
|
||||
bool isCurrentIndex = pageIndex == i;
|
||||
final item = pages[i];
|
||||
return Container(
|
||||
width: 150,
|
||||
margin: EdgeInsets.only(
|
||||
@@ -156,7 +157,7 @@ class _PagesPanelState extends State<PagesPanel> {
|
||||
widget.videoIntroController.changeSeasonOrbangu(
|
||||
null,
|
||||
widget.bvid,
|
||||
pages[i].cid,
|
||||
item.cid,
|
||||
IdUtils.bv2av(widget.bvid),
|
||||
widget.cover,
|
||||
);
|
||||
@@ -168,8 +169,7 @@ class _PagesPanelState extends State<PagesPanel> {
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 8),
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
if (isCurrentIndex) ...<Widget>[
|
||||
@@ -182,17 +182,18 @@ class _PagesPanelState extends State<PagesPanel> {
|
||||
const SizedBox(width: 6)
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
pages[i].pagePart!,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isCurrentIndex
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.onSurface,
|
||||
child: Text(
|
||||
item.pagePart!,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: isCurrentIndex
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.onSurface,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -42,7 +42,7 @@ class MediaListPanel extends CommonCollapseSlidePage {
|
||||
final bool desc;
|
||||
final VoidCallback onReverse;
|
||||
final RefreshCallback? loadPrevious;
|
||||
final ValueChanged<int>? onDelete;
|
||||
final Function(dynamic item, int index)? onDelete;
|
||||
|
||||
@override
|
||||
State<MediaListPanel> createState() => _MediaListPanelState();
|
||||
@@ -298,9 +298,9 @@ class _MediaListPanelState
|
||||
onTap: () => showConfirmDialog(
|
||||
context: context,
|
||||
title: '确定移除该视频?',
|
||||
onConfirm: () => widget.onDelete!(index),
|
||||
onConfirm: () => widget.onDelete!(item, index),
|
||||
),
|
||||
onLongPress: () => widget.onDelete!(index),
|
||||
onLongPress: () => widget.onDelete!(item, index),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(9),
|
||||
child: Icon(
|
||||
|
||||
@@ -106,302 +106,9 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
children: [
|
||||
...List.generate(
|
||||
list!.length,
|
||||
(index) => Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 5,
|
||||
),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (list![index].actionType !=
|
||||
ActionType.full) ...[
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 16,
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: segmentWidget(
|
||||
context,
|
||||
theme,
|
||||
isFirst: true,
|
||||
index: index,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (list![index].category !=
|
||||
SegmentType.poi_highlight)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: segmentWidget(
|
||||
context,
|
||||
theme,
|
||||
isFirst: false,
|
||||
index: index,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 16,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('分类: '),
|
||||
PopupMenuButton<SegmentType>(
|
||||
initialValue:
|
||||
list![index].category,
|
||||
onSelected: (item) {
|
||||
list![index].category = item;
|
||||
List<ActionType>
|
||||
constraintList =
|
||||
item.toActionType;
|
||||
if (!constraintList.contains(
|
||||
list![index].actionType)) {
|
||||
list![index].actionType =
|
||||
constraintList.first;
|
||||
}
|
||||
switch (item) {
|
||||
case SegmentType
|
||||
.poi_highlight:
|
||||
updateSegment(
|
||||
isFirst: false,
|
||||
index: index,
|
||||
value: list![index]
|
||||
.segment
|
||||
.first,
|
||||
);
|
||||
break;
|
||||
case SegmentType
|
||||
.exclusive_access:
|
||||
updateSegment(
|
||||
isFirst: true,
|
||||
index: index,
|
||||
value: 0,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
(context as Element)
|
||||
.markNeedsBuild();
|
||||
},
|
||||
itemBuilder: (context) =>
|
||||
SegmentType.values
|
||||
.map((item) =>
|
||||
PopupMenuItem<
|
||||
SegmentType>(
|
||||
value: item,
|
||||
child: Text(
|
||||
item.title),
|
||||
))
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
list![index].category.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: theme.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
strutStyle:
|
||||
const StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons
|
||||
.unfoldMoreHorizontal,
|
||||
size:
|
||||
MediaQuery.textScalerOf(
|
||||
context)
|
||||
.scale(14),
|
||||
color: theme
|
||||
.colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('行为类别: '),
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return PopupMenuButton<
|
||||
ActionType>(
|
||||
initialValue:
|
||||
list![index].actionType,
|
||||
onSelected: (item) {
|
||||
list![index].actionType =
|
||||
item;
|
||||
if (item ==
|
||||
ActionType.full) {
|
||||
updateSegment(
|
||||
isFirst: true,
|
||||
index: index,
|
||||
value: 0,
|
||||
);
|
||||
}
|
||||
(context as Element)
|
||||
.markNeedsBuild();
|
||||
},
|
||||
itemBuilder: (context) =>
|
||||
ActionType.values
|
||||
.map(
|
||||
(item) =>
|
||||
PopupMenuItem<
|
||||
ActionType>(
|
||||
enabled: list![
|
||||
index]
|
||||
.category
|
||||
.toActionType
|
||||
.contains(
|
||||
item),
|
||||
value: item,
|
||||
child: Text(
|
||||
item.title),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize:
|
||||
MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
list![index]
|
||||
.actionType
|
||||
.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: theme
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
strutStyle:
|
||||
const StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons
|
||||
.unfoldMoreHorizontal,
|
||||
size: MediaQuery
|
||||
.textScalerOf(
|
||||
context)
|
||||
.scale(14),
|
||||
color: theme.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 4,
|
||||
child: iconButton(
|
||||
context: context,
|
||||
size: 26,
|
||||
tooltip: '移除',
|
||||
icon: Icons.clear,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
list!.removeAt(index);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 4,
|
||||
child: iconButton(
|
||||
context: context,
|
||||
size: 26,
|
||||
tooltip: '预览',
|
||||
icon: Icons.preview_outlined,
|
||||
onPressed: () async {
|
||||
if (widget.plPlayerController
|
||||
.videoPlayerController !=
|
||||
null) {
|
||||
int start = max(
|
||||
0,
|
||||
(list![index].segment.first * 1000)
|
||||
.round() -
|
||||
2000,
|
||||
);
|
||||
await widget
|
||||
.plPlayerController.videoPlayerController!
|
||||
.seek(
|
||||
Duration(milliseconds: start),
|
||||
);
|
||||
if (!widget.plPlayerController
|
||||
.videoPlayerController!.state.playing) {
|
||||
await widget.plPlayerController
|
||||
.videoPlayerController!
|
||||
.play();
|
||||
}
|
||||
if (start != 0) {
|
||||
await Future.delayed(
|
||||
const Duration(seconds: 2));
|
||||
}
|
||||
widget
|
||||
.plPlayerController.videoPlayerController!
|
||||
.seek(
|
||||
Duration(
|
||||
milliseconds:
|
||||
(list![index].segment.second * 1000)
|
||||
.round(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
(index) {
|
||||
return _buildItem(theme, index, list![index]);
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 88 + MediaQuery.paddingOf(context).bottom,
|
||||
@@ -448,28 +155,28 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
|
||||
void updateSegment({
|
||||
required bool isFirst,
|
||||
required int index,
|
||||
required PostSegmentModel item,
|
||||
required double value,
|
||||
}) {
|
||||
if (isFirst) {
|
||||
list![index].segment.first = value;
|
||||
item.segment.first = value;
|
||||
} else {
|
||||
list![index].segment.second = value;
|
||||
item.segment.second = value;
|
||||
}
|
||||
if (list![index].category == SegmentType.poi_highlight ||
|
||||
list![index].actionType == ActionType.full) {
|
||||
list![index].segment.second = value;
|
||||
if (item.category == SegmentType.poi_highlight ||
|
||||
item.actionType == ActionType.full) {
|
||||
item.segment.second = value;
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> segmentWidget(
|
||||
BuildContext context,
|
||||
ThemeData theme, {
|
||||
required int index,
|
||||
required PostSegmentModel item,
|
||||
required bool isFirst,
|
||||
}) {
|
||||
String value = DurationUtil.formatDuration(
|
||||
isFirst ? list![index].segment.first : list![index].segment.second);
|
||||
isFirst ? item.segment.first : item.segment.second);
|
||||
return [
|
||||
Text(
|
||||
'${isFirst ? '开始' : '结束'}: $value',
|
||||
@@ -483,7 +190,7 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
onPressed: () {
|
||||
updateSegment(
|
||||
isFirst: isFirst,
|
||||
index: index,
|
||||
item: item,
|
||||
value: currentPos,
|
||||
);
|
||||
(context as Element).markNeedsBuild();
|
||||
@@ -498,7 +205,7 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
onPressed: () {
|
||||
updateSegment(
|
||||
isFirst: isFirst,
|
||||
index: index,
|
||||
item: item,
|
||||
value: isFirst ? 0 : videoDuration,
|
||||
);
|
||||
(context as Element).markNeedsBuild();
|
||||
@@ -553,7 +260,7 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
if (duration <= videoDuration) {
|
||||
updateSegment(
|
||||
isFirst: isFirst,
|
||||
index: index,
|
||||
item: item,
|
||||
value: duration,
|
||||
);
|
||||
(context as Element).markNeedsBuild();
|
||||
@@ -629,4 +336,241 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(ThemeData theme, int index, PostSegmentModel item) {
|
||||
return Builder(
|
||||
builder: (context) {
|
||||
return Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
Container(
|
||||
width: double.infinity,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 5,
|
||||
),
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (item.actionType != ActionType.full) ...[
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 16,
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: segmentWidget(
|
||||
context,
|
||||
theme,
|
||||
isFirst: true,
|
||||
item: item,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (item.category != SegmentType.poi_highlight)
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: segmentWidget(
|
||||
context,
|
||||
theme,
|
||||
isFirst: false,
|
||||
item: item,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 16,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('分类: '),
|
||||
PopupMenuButton<SegmentType>(
|
||||
initialValue: item.category,
|
||||
onSelected: (e) {
|
||||
item.category = e;
|
||||
List<ActionType> constraintList = e.toActionType;
|
||||
if (!constraintList.contains(item.actionType)) {
|
||||
item.actionType = constraintList.first;
|
||||
}
|
||||
switch (e) {
|
||||
case SegmentType.poi_highlight:
|
||||
updateSegment(
|
||||
isFirst: false,
|
||||
item: item,
|
||||
value: item.segment.first,
|
||||
);
|
||||
break;
|
||||
case SegmentType.exclusive_access:
|
||||
updateSegment(
|
||||
isFirst: true,
|
||||
item: item,
|
||||
value: 0,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
itemBuilder: (context) => SegmentType.values
|
||||
.map((e) => PopupMenuItem<SegmentType>(
|
||||
value: e,
|
||||
child: Text(e.title),
|
||||
))
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
item.category.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: theme.colorScheme.secondary,
|
||||
),
|
||||
strutStyle: const StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
size: MediaQuery.textScalerOf(context)
|
||||
.scale(14),
|
||||
color: theme.colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text('行为类别: '),
|
||||
PopupMenuButton<ActionType>(
|
||||
initialValue: item.actionType,
|
||||
onSelected: (e) {
|
||||
item.actionType = e;
|
||||
if (e == ActionType.full) {
|
||||
updateSegment(
|
||||
isFirst: true,
|
||||
item: item,
|
||||
value: 0,
|
||||
);
|
||||
}
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
itemBuilder: (context) => ActionType.values
|
||||
.map(
|
||||
(e) => PopupMenuItem<ActionType>(
|
||||
enabled:
|
||||
item.category.toActionType.contains(e),
|
||||
value: e,
|
||||
child: Text(e.title),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
item.actionType.title,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: theme.colorScheme.secondary,
|
||||
),
|
||||
strutStyle: const StrutStyle(
|
||||
height: 1,
|
||||
leading: 0,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
size: MediaQuery.textScalerOf(context)
|
||||
.scale(14),
|
||||
color: theme.colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
right: 4,
|
||||
child: iconButton(
|
||||
context: context,
|
||||
size: 26,
|
||||
tooltip: '移除',
|
||||
icon: Icons.clear,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
list!.removeAt(index);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 4,
|
||||
child: iconButton(
|
||||
context: context,
|
||||
size: 26,
|
||||
tooltip: '预览',
|
||||
icon: Icons.preview_outlined,
|
||||
onPressed: () async {
|
||||
if (widget.plPlayerController.videoPlayerController != null) {
|
||||
int start = max(
|
||||
0,
|
||||
(item.segment.first * 1000).round() - 2000,
|
||||
);
|
||||
await widget.plPlayerController.videoPlayerController!.seek(
|
||||
Duration(milliseconds: start),
|
||||
);
|
||||
if (!widget.plPlayerController.videoPlayerController!.state
|
||||
.playing) {
|
||||
await widget.plPlayerController.videoPlayerController!
|
||||
.play();
|
||||
}
|
||||
if (start != 0) {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
}
|
||||
widget.plPlayerController.videoPlayerController!.seek(
|
||||
Duration(
|
||||
milliseconds: (item.segment.second * 1000).round(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,10 +220,9 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
onReply: (replyItem) => _videoReplyController.onReply(
|
||||
context,
|
||||
replyItem: replyItem,
|
||||
index: index,
|
||||
),
|
||||
onDelete: (subIndex) =>
|
||||
_videoReplyController.onRemove(index, subIndex),
|
||||
onDelete: (item, subIndex) =>
|
||||
_videoReplyController.onRemove(index, item, subIndex),
|
||||
upMid: _videoReplyController.upMid,
|
||||
getTag: () => heroTag,
|
||||
onViewImage: widget.onViewImage,
|
||||
@@ -231,13 +230,11 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
callback: widget.callback,
|
||||
onCheckReply: (item) => _videoReplyController
|
||||
.onCheckReply(context, item, isManual: true),
|
||||
onToggleTop: (isUpTop, rpid) =>
|
||||
_videoReplyController.onToggleTop(
|
||||
onToggleTop: (item) => _videoReplyController.onToggleTop(
|
||||
item,
|
||||
index,
|
||||
_videoReplyController.aid,
|
||||
1,
|
||||
isUpTop,
|
||||
rpid,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
final Function(ReplyInfo replyItem, int? rpid)? replyReply;
|
||||
final bool needDivider;
|
||||
final ValueChanged<ReplyInfo>? onReply;
|
||||
final ValueChanged<int?>? onDelete;
|
||||
final Function(ReplyInfo replyItem, int? subIndex)? onDelete;
|
||||
final Int64? upMid;
|
||||
final VoidCallback? showDialogue;
|
||||
final Function? getTag;
|
||||
@@ -66,7 +66,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
final ValueChanged<int>? onDismissed;
|
||||
final Function(List<String>, int)? callback;
|
||||
final ValueChanged<ReplyInfo>? onCheckReply;
|
||||
final Function(bool isUpTop, int rpid)? onToggleTop;
|
||||
final ValueChanged<ReplyInfo>? onToggleTop;
|
||||
|
||||
static final _voteRegExp = RegExp(r"\{vote:\d+?\}");
|
||||
static final _timeRegExp = RegExp(r'^\b(?:\d+[::])?\d+[::]\d+\b$');
|
||||
@@ -96,7 +96,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
return morePanel(
|
||||
context: context,
|
||||
item: replyItem,
|
||||
onDelete: () => onDelete?.call(null),
|
||||
onDelete: () => onDelete?.call(replyItem, null),
|
||||
isSubReply: false,
|
||||
);
|
||||
},
|
||||
@@ -463,7 +463,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
return morePanel(
|
||||
context: context,
|
||||
item: childReply,
|
||||
onDelete: () => onDelete?.call(index),
|
||||
onDelete: () => onDelete?.call(replyItem, index),
|
||||
isSubReply: true,
|
||||
);
|
||||
},
|
||||
@@ -963,7 +963,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
Widget morePanel({
|
||||
required BuildContext context,
|
||||
required ReplyInfo item,
|
||||
required onDelete,
|
||||
required VoidCallback onDelete,
|
||||
required bool isSubReply,
|
||||
}) {
|
||||
final ownerMid = Int64(Accounts.main.mid);
|
||||
@@ -994,7 +994,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
Options(contentType: Headers.formUrlEncodedContentType),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
onDelete?.call();
|
||||
onDelete();
|
||||
}
|
||||
return res.data as Map;
|
||||
},
|
||||
@@ -1074,7 +1074,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
SmartDialog.dismiss();
|
||||
if (result['status']) {
|
||||
SmartDialog.showToast('删除成功');
|
||||
onDelete?.call();
|
||||
onDelete();
|
||||
} else {
|
||||
SmartDialog.showToast('删除失败, ${result["msg"]}');
|
||||
}
|
||||
@@ -1085,7 +1085,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
break;
|
||||
case 'top':
|
||||
Get.back();
|
||||
onToggleTop?.call(item.replyControl.isUpTop, item.id.toInt());
|
||||
onToggleTop?.call(item);
|
||||
break;
|
||||
case 'saveReply':
|
||||
Get.back();
|
||||
@@ -1167,7 +1167,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
leading: const Icon(Icons.save_alt, size: 19),
|
||||
title: Text('保存评论', style: style),
|
||||
),
|
||||
if (kDebugMode || item.mid == ownerMid)
|
||||
if (item.mid == ownerMid)
|
||||
ListTile(
|
||||
onTap: () => menuActionHandler('checkReply'),
|
||||
minLeadingWidth: 0,
|
||||
|
||||
@@ -188,14 +188,6 @@ class _VideoReplyReplyPanelState
|
||||
callback: _getImageCallback,
|
||||
onCheckReply: (item) => _videoReplyReplyController
|
||||
.onCheckReply(context, item, isManual: true),
|
||||
onToggleTop: (isUpTop, rpid) =>
|
||||
_videoReplyReplyController.onToggleTop(
|
||||
index,
|
||||
_videoReplyReplyController.oid,
|
||||
_videoReplyReplyController.replyType,
|
||||
isUpTop,
|
||||
rpid,
|
||||
),
|
||||
);
|
||||
} else if (index == 1) {
|
||||
return Divider(
|
||||
@@ -431,8 +423,8 @@ class _VideoReplyReplyPanelState
|
||||
replyItem: replyItem,
|
||||
replyLevel: widget.isDialogue ? 3 : 2,
|
||||
onReply: (replyItem) => _onReply(replyItem, index),
|
||||
onDelete: (subIndex) {
|
||||
_videoReplyReplyController.onRemove(index, null);
|
||||
onDelete: (item, subIndex) {
|
||||
_videoReplyReplyController.onRemove(index, item, null);
|
||||
},
|
||||
upMid: _videoReplyReplyController.upMid,
|
||||
showDialogue: () => _key.currentState?.showBottomSheet(
|
||||
@@ -451,13 +443,6 @@ class _VideoReplyReplyPanelState
|
||||
callback: _getImageCallback,
|
||||
onCheckReply: (item) => _videoReplyReplyController
|
||||
.onCheckReply(context, item, isManual: true),
|
||||
onToggleTop: (isUpTop, rpid) => _videoReplyReplyController.onToggleTop(
|
||||
index,
|
||||
_videoReplyReplyController.oid,
|
||||
_videoReplyReplyController.replyType,
|
||||
isUpTop,
|
||||
rpid,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -593,15 +593,16 @@ class HeaderControlState extends State<HeaderControl> {
|
||||
),
|
||||
),
|
||||
),
|
||||
for (int i = 0; i < totalQaSam; i++) ...[
|
||||
ListTile(
|
||||
...List.generate(totalQaSam, (index) {
|
||||
final item = videoFormat[index];
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: () async {
|
||||
if (currentVideoQa.code == videoFormat[i].quality) {
|
||||
if (currentVideoQa.code == item.quality) {
|
||||
return;
|
||||
}
|
||||
Get.back();
|
||||
final int quality = videoFormat[i].quality!;
|
||||
final int quality = item.quality!;
|
||||
videoDetailCtr
|
||||
..currentVideoQa = VideoQuality.fromCode(quality)
|
||||
..updatePlayer();
|
||||
@@ -631,20 +632,20 @@ class HeaderControlState extends State<HeaderControl> {
|
||||
);
|
||||
},
|
||||
// 可能包含会员解锁画质
|
||||
enabled: i >= totalQaSam - userfulQaSam,
|
||||
enabled: index >= totalQaSam - userfulQaSam,
|
||||
contentPadding: const EdgeInsets.only(left: 20, right: 20),
|
||||
title: Text(videoFormat[i].newDesc!),
|
||||
trailing: currentVideoQa.code == videoFormat[i].quality
|
||||
title: Text(item.newDesc!),
|
||||
trailing: currentVideoQa.code == item.quality
|
||||
? Icon(
|
||||
Icons.done,
|
||||
color: theme.colorScheme.primary,
|
||||
)
|
||||
: Text(
|
||||
videoFormat[i].format!,
|
||||
item.format!,
|
||||
style: subTitleStyle,
|
||||
),
|
||||
),
|
||||
]
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -106,12 +106,13 @@ class _WhisperPageState extends State<WhisperPage> {
|
||||
if (index == response.length - 1) {
|
||||
_controller.onLoadMore();
|
||||
}
|
||||
final item = response[index];
|
||||
return WhisperSessionItem(
|
||||
item: response[index],
|
||||
item: item,
|
||||
onSetTop: (isTop, id) =>
|
||||
_controller.onSetTop(index, isTop, id),
|
||||
_controller.onSetTop(item, index, isTop, id),
|
||||
onSetMute: (isMuted, talkerUid) =>
|
||||
_controller.onSetMute(index, isMuted, talkerUid),
|
||||
_controller.onSetMute(item, isMuted, talkerUid),
|
||||
onRemove: (talkerId) => _controller.onRemove(index, talkerId),
|
||||
);
|
||||
},
|
||||
@@ -127,61 +128,64 @@ class _WhisperPageState extends State<WhisperPage> {
|
||||
};
|
||||
}
|
||||
|
||||
Widget get _buildTopItems => SliverSafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children:
|
||||
List.generate(_controller.msgFeedTopItems.length, (index) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Obx(
|
||||
() => Badge(
|
||||
isLabelVisible: _controller.unreadCounts[index] > 0,
|
||||
label: Text(" ${_controller.unreadCounts[index]} "),
|
||||
Widget get _buildTopItems {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
return SliverSafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: List.generate(_controller.msgFeedTopItems.length, (index) {
|
||||
final item = _controller.msgFeedTopItems[index];
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Obx(
|
||||
() {
|
||||
final count = _controller.unreadCounts[index];
|
||||
return Badge(
|
||||
isLabelVisible: count > 0,
|
||||
label: Text(" $count "),
|
||||
alignment: Alignment.topRight,
|
||||
child: CircleAvatar(
|
||||
radius: 22,
|
||||
backgroundColor: theme.colorScheme.onInverseSurface,
|
||||
child: Icon(
|
||||
_controller.msgFeedTopItems[index].icon,
|
||||
item.icon,
|
||||
size: 20,
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
_controller.msgFeedTopItems[index].name,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
item.name,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
if (!_controller.msgFeedTopItems[index].enabled) {
|
||||
SmartDialog.showToast('已禁用');
|
||||
return;
|
||||
}
|
||||
_controller.unreadCounts[index] = 0;
|
||||
Get.toNamed(
|
||||
_controller.msgFeedTopItems[index].route,
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
if (!item.enabled) {
|
||||
SmartDialog.showToast('已禁用');
|
||||
return;
|
||||
}
|
||||
_controller.unreadCounts[index] = 0;
|
||||
Get.toNamed(item.route);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,12 +97,13 @@ class _WhisperSecPageState extends State<WhisperSecPage> {
|
||||
if (index == response.length - 1) {
|
||||
_controller.onLoadMore();
|
||||
}
|
||||
final item = response[index];
|
||||
return WhisperSessionItem(
|
||||
item: response[index],
|
||||
item: item,
|
||||
onSetTop: (isTop, talkerId) =>
|
||||
_controller.onSetTop(index, isTop, talkerId),
|
||||
_controller.onSetTop(item, index, isTop, talkerId),
|
||||
onSetMute: (isMuted, talkerUid) =>
|
||||
_controller.onSetMute(index, isMuted, talkerUid),
|
||||
_controller.onSetMute(item, isMuted, talkerUid),
|
||||
onRemove: (talkerId) => _controller.onRemove(index, talkerId),
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user