Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-06-21 22:38:47 +08:00
parent a1555826c3
commit 17568c8c27
43 changed files with 818 additions and 932 deletions

View File

@@ -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,
),
);
}

View File

@@ -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']);

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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,
),
);
}

View File

@@ -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,
),

View File

@@ -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),
),
);
},

View File

@@ -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,

View File

@@ -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

View File

@@ -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 ? '追番' : '追剧',

View File

@@ -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

View File

@@ -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,

View File

@@ -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();
},
);
},

View File

@@ -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']);

View File

@@ -179,8 +179,7 @@ class _FollowPageState extends State<FollowPage> {
),
onConfirm: () {
if (tagName.isNotEmpty) {
_followController.onUpdateTag(
index, item.tagid, tagName);
_followController.onUpdateTag(item, tagName);
}
},
);

View File

@@ -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();

View File

@@ -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),
);

View File

@@ -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);
}
},
),

View File

@@ -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();

View File

@@ -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,

View File

@@ -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),
],
),
),

View File

@@ -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,
),
);
},

View File

@@ -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),

View File

@@ -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 {

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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: [

View File

@@ -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,
);
}

View File

@@ -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),

View File

@@ -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(

View File

@@ -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']}',

View File

@@ -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(),
),
),
);
}
}

View File

@@ -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,
))
),
],
),
),

View File

@@ -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(

View File

@@ -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(),
),
);
}
},
),
),
],
);
},
);
}
}

View File

@@ -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,
),
);
}

View File

@@ -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,

View File

@@ -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,
),
);
}

View File

@@ -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,
),
),
]
);
}),
],
),
),

View File

@@ -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);
},
);
}),
),
);
),
);
}
}

View File

@@ -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),
);
},