diff --git a/lib/common/widgets/image/image_view.dart b/lib/common/widgets/image/image_view.dart index d7c0b2aea..118954cb4 100644 --- a/lib/common/widgets/image/image_view.dart +++ b/lib/common/widgets/image/image_view.dart @@ -126,63 +126,65 @@ Widget imageView( height: picArr.length == 1 ? imageHeight : null, width: picArr.length == 1 ? imageWidth : maxWidth, itemCount: picArr.length, - itemBuilder: (context, index) => Hero( - tag: picArr[index].url, - child: GestureDetector( - onTap: () => onTap(context, index), - child: Stack( - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - ClipRRect( - borderRadius: borderRadius(index), - child: NetworkImgLayer( - radius: 0, - src: picArr[index].url, - width: imageWidth, - height: imageHeight, - isLongPic: () => picArr[index].isLongPic, - callback: () => - picArr[index].safeWidth <= picArr[index].safeHeight, - getPlaceHolder: () { - return Container( - width: imageWidth, - height: imageHeight, - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .onInverseSurface - .withValues(alpha: 0.4), - borderRadius: borderRadius(index), - ), - child: Center( - child: Image.asset( - 'assets/images/loading.png', - width: imageWidth, - height: imageHeight, - cacheWidth: imageWidth.cacheSize(context), + itemBuilder: (context, index) { + final item = picArr[index]; + return Hero( + tag: item.url, + child: GestureDetector( + onTap: () => onTap(context, index), + child: Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + ClipRRect( + borderRadius: borderRadius(index), + child: NetworkImgLayer( + radius: 0, + src: item.url, + width: imageWidth, + height: imageHeight, + isLongPic: () => item.isLongPic, + callback: () => item.safeWidth <= item.safeHeight, + getPlaceHolder: () { + return Container( + width: imageWidth, + height: imageHeight, + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .onInverseSurface + .withValues(alpha: 0.4), + borderRadius: borderRadius(index), ), - ), - ); - }, + child: Center( + child: Image.asset( + 'assets/images/loading.png', + width: imageWidth, + height: imageHeight, + cacheWidth: imageWidth.cacheSize(context), + ), + ), + ); + }, + ), ), - ), - if (picArr[index].isLivePhoto) - const PBadge( - text: 'Live', - right: 8, - bottom: 8, - type: PBadgeType.gray, - ) - else if (picArr[index].isLongPic) - const PBadge( - text: '长图', - right: 8, - bottom: 8, - ), - ], + if (item.isLivePhoto) + const PBadge( + text: 'Live', + right: 8, + bottom: 8, + type: PBadgeType.gray, + ) + else if (item.isLongPic) + const PBadge( + text: '长图', + right: 8, + bottom: 8, + ), + ], + ), ), - ), - ), + ); + }, ); } diff --git a/lib/pages/article/view.dart b/lib/pages/article/view.dart index 0c6a0de45..6b78ebc14 100644 --- a/lib/pages/article/view.dart +++ b/lib/pages/article/view.dart @@ -619,20 +619,18 @@ class _ArticlePageState extends State 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, ), ); } diff --git a/lib/pages/common/common_whisper_controller.dart b/lib/pages/common/common_whisper_controller.dart index 1b830d3de..62d953a9d 100644 --- a/lib/pages/common/common_whisper_controller.dart +++ b/lib/pages/common/common_whisper_controller.dart @@ -24,14 +24,15 @@ abstract class CommonWhisperController } } - Future onSetTop(int index, bool isTop, SessionId sessionId) async { + Future 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 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 } } - Future onSetMute(int index, bool isMuted, Int64 talkerUid) async { + Future 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']); diff --git a/lib/pages/common/multi_select_controller.dart b/lib/pages/common/multi_select_controller.dart index 73f9b5991..04705675b 100644 --- a/lib/pages/common/multi_select_controller.dart +++ b/lib/pages/common/multi_select_controller.dart @@ -11,9 +11,9 @@ abstract class MultiSelectController 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 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) { diff --git a/lib/pages/common/reply_controller.dart b/lib/pages/common/reply_controller.dart index e2d7f8a95..1b6f7c4b1 100644 --- a/lib/pages/common/reply_controller.dart +++ b/lib/pages/common/reply_controller.dart @@ -107,7 +107,6 @@ abstract class ReplyController extends CommonListController { 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 extends CommonListController { 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 extends CommonListController { ); } - void onRemove(int index, int? subIndex) { + void onRemove(int index, ReplyInfo item, int? subIndex) { List 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 extends CommonListController { } Future 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 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); diff --git a/lib/pages/dynamics_detail/view.dart b/lib/pages/dynamics_detail/view.dart index 67719c066..61740e50e 100644 --- a/lib/pages/dynamics_detail/view.dart +++ b/lib/pages/dynamics_detail/view.dart @@ -730,20 +730,18 @@ class _DynamicDetailPageState extends State 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, ), ); } diff --git a/lib/pages/emote/view.dart b/lib/pages/emote/view.dart index cef6836ae..50c5fe8bb 100644 --- a/lib/pages/emote/view.dart +++ b/lib/pages/emote/view.dart @@ -61,27 +61,28 @@ class _EmotePanelState extends State ), 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, ), diff --git a/lib/pages/fav/article/view.dart b/lib/pages/fav/article/view.dart index e1506bce8..8acd36cf4 100644 --- a/lib/pages/fav/article/view.dart +++ b/lib/pages/fav/article/view.dart @@ -67,17 +67,14 @@ class _FavArticlePageState extends State 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), ), ); }, diff --git a/lib/pages/fav/note/child_view.dart b/lib/pages/fav/note/child_view.dart index 689f61b80..2d707786e 100644 --- a/lib/pages/fav/note/child_view.dart +++ b/lib/pages/fav/note/child_view.dart @@ -151,10 +151,11 @@ class _FavNoteChildPageState extends State 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, diff --git a/lib/pages/fav/note/controller.dart b/lib/pages/fav/note/controller.dart index d740ae5c8..0cc8018ab 100644 --- a/lib/pages/fav/note/controller.dart +++ b/lib/pages/fav/note/controller.dart @@ -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 diff --git a/lib/pages/fav/pgc/child_view.dart b/lib/pages/fav/pgc/child_view.dart index adaa9e391..bab9cb25c 100644 --- a/lib/pages/fav/pgc/child_view.dart +++ b/lib/pages/fav/pgc/child_view.dart @@ -178,7 +178,7 @@ class _FavPgcChildPageState extends State return FavPgcItem( item: item, ctr: _favPgcController, - onSelect: () => _favPgcController.onSelect(index), + onSelect: () => _favPgcController.onSelect(item), onUpdateStatus: () => showPgcFollowDialog( context: context, type: widget.type == 0 ? '追番' : '追剧', diff --git a/lib/pages/fav/pgc/controller.dart b/lib/pages/fav/pgc/controller.dart index 1535fc77b..c38da3e38 100644 --- a/lib/pages/fav/pgc/controller.dart +++ b/lib/pages/fav/pgc/controller.dart @@ -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 diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index b3c440241..7d92b9c22 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -477,7 +477,7 @@ class _FavDetailPageState extends State { ); }, onTap: _favDetailController.enableMultiSelect.value - ? () => _favDetailController.onSelect(index) + ? () => _favDetailController.onSelect(item) : null, onLongPress: _favDetailController.isOwner.value == true @@ -486,7 +486,7 @@ class _FavDetailPageState extends State { .enableMultiSelect.value) { _favDetailController .enableMultiSelect.value = true; - _favDetailController.onSelect(index); + _favDetailController.onSelect(item); } } : null, diff --git a/lib/pages/follow/child/child_view.dart b/lib/pages/follow/child/child_view.dart index e3ab883b0..1d5768235 100644 --- a/lib/pages/follow/child/child_view.dart +++ b/lib/pages/follow/child/child_view.dart @@ -100,14 +100,14 @@ class _FollowChildPageState extends State 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(); }, ); }, diff --git a/lib/pages/follow/controller.dart b/lib/pages/follow/controller.dart index 8f9b44574..489d781d7 100644 --- a/lib/pages/follow/controller.dart +++ b/lib/pages/follow/controller.dart @@ -70,12 +70,11 @@ class FollowController extends GetxController with GetTickerProviderStateMixin { } } - Future onUpdateTag(int index, tagid, String tagName) async { - final res = await MemberHttp.updateFollowTag(tagid, tagName); + Future 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']); diff --git a/lib/pages/follow/view.dart b/lib/pages/follow/view.dart index ee25416ae..71a4cdc98 100644 --- a/lib/pages/follow/view.dart +++ b/lib/pages/follow/view.dart @@ -179,8 +179,7 @@ class _FollowPageState extends State { ), onConfirm: () { if (tagName.isNotEmpty) { - _followController.onUpdateTag( - index, item.tagid, tagName); + _followController.onUpdateTag(item, tagName); } }, ); diff --git a/lib/pages/history/controller.dart b/lib/pages/history/controller.dart index 83a658cf5..9e7992031 100644 --- a/lib/pages/history/controller.dart +++ b/lib/pages/history/controller.dart @@ -41,9 +41,9 @@ class HistoryController } @override - void onSelect(int index, [bool disableSelect = true]) { + void onSelect(HistoryItemModel item, [bool disableSelect = true]) { List 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(); diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index 40168b8a3..a9b84a3d4 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -273,7 +273,7 @@ class _HistoryPageState extends State return HistoryItem( item: item, ctr: _historyController.baseCtr, - onChoose: () => _historyController.onSelect(index), + onChoose: () => _historyController.onSelect(item), onDelete: (kid, business) => _historyController.delHistory(item), ); diff --git a/lib/pages/later/child_view.dart b/lib/pages/later/child_view.dart index 5f455b3fe..73c079c86 100644 --- a/lib/pages/later/child_view.dart +++ b/lib/pages/later/child_view.dart @@ -101,13 +101,13 @@ class _LaterViewChildPageState extends State }, 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); } }, ), diff --git a/lib/pages/later/controller.dart b/lib/pages/later/controller.dart index efc3f46a0..827456f32 100644 --- a/lib/pages/later/controller.dart +++ b/lib/pages/later/controller.dart @@ -35,9 +35,9 @@ class LaterController extends MultiSelectController { ); @override - void onSelect(int index, [bool disableSelect = true]) { + void onSelect(LaterItemModel item, [bool disableSelect = true]) { List 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(); diff --git a/lib/pages/live_emote/view.dart b/lib/pages/live_emote/view.dart index e57884cb2..d16c27d9b 100644 --- a/lib/pages/live_emote/view.dart +++ b/lib/pages/live_emote/view.dart @@ -73,6 +73,7 @@ class _LiveEmotePanelState extends State ), 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 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, diff --git a/lib/pages/live_room/widgets/chat.dart b/lib/pages/live_room/widgets/chat.dart index e9477bb59..637501e29 100644 --- a/lib/pages/live_room/widgets/chat.dart +++ b/lib/pages/live_room/widgets/chat.dart @@ -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), ], ), ), diff --git a/lib/pages/match_info/view.dart b/lib/pages/match_info/view.dart index 71b1dfc08..14336af58 100644 --- a/lib/pages/match_info/view.dart +++ b/lib/pages/match_info/view.dart @@ -213,19 +213,17 @@ class _MatchInfoPageState extends State { 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, ), ); }, diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index 9c31f13a5..133c991d6 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -207,9 +207,8 @@ class _MediaPageState extends CommonPageState ), ); } 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), diff --git a/lib/pages/msg_feed_top/like_me/controller.dart b/lib/pages/msg_feed_top/like_me/controller.dart index 2c8647547..37f1f7018 100644 --- a/lib/pages/msg_feed_top/like_me/controller.dart +++ b/lib/pages/msg_feed_top/like_me/controller.dart @@ -78,17 +78,11 @@ class LikeMeController extends CommonDataController onSetNotice( - int? id, int index, bool isNotice, bool isLatest) async { + Future 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> 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 { diff --git a/lib/pages/msg_feed_top/like_me/view.dart b/lib/pages/msg_feed_top/like_me/view.dart index bbdd54674..d67483b77 100644 --- a/lib/pages/msg_feed_top/like_me/view.dart +++ b/lib/pages/msg_feed_top/like_me/view.dart @@ -99,10 +99,6 @@ class _LikeMePageState extends State { (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 { (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 { ThemeData theme, MsgLikeItem item, ValueChanged onRemove, - Function(bool isNotice, int? id) onSetNotice, ) { return ListTile( onTap: () { @@ -223,10 +214,11 @@ class _LikeMePageState extends State { 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, diff --git a/lib/pages/pgc_review/child/controller.dart b/lib/pages/pgc_review/child/controller.dart index 8416c1a6f..67e9f8bf4 100644 --- a/lib/pages/pgc_review/child/controller.dart +++ b/lib/pages/pgc_review/child/controller.dart @@ -58,13 +58,12 @@ class PgcReviewController sort: sortType.value.sort, ); - Future onLike(int index, bool isLike, reviewId) async { + Future 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 onDislike(int index, bool isDislike, reviewId) async { + Future 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) { diff --git a/lib/pages/pgc_review/child/view.dart b/lib/pages/pgc_review/child/view.dart index bb5f16ddd..fe8864684 100644 --- a/lib/pages/pgc_review/child/view.dart +++ b/lib/pages/pgc_review/child/view.dart @@ -312,7 +312,7 @@ class _PgcReviewChildPageState extends State 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 onPressed: isLongReview ? null : () => _controller.onLike( - index, isLike, item.reviewId), + item, isLike, item.reviewId), child: Row( spacing: 4, children: [ diff --git a/lib/pages/setting/widgets/select_dialog.dart b/lib/pages/setting/widgets/select_dialog.dart index a4c419f47..ffb52afaf 100644 --- a/lib/pages/setting/widgets/select_dialog.dart +++ b/lib/pages/setting/widgets/select_dialog.dart @@ -35,17 +35,20 @@ class SelectDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: List.generate( values.length, - (index) => RadioListTile( - 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( + 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 { } 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 { if (message.isEmpty) { message = '测速失败'; } - _cdnResList[index].value = message; + item.value = message; } @override @@ -184,17 +188,20 @@ class _CdnSelectDialogState extends State { 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, ); } diff --git a/lib/pages/share/view.dart b/lib/pages/share/view.dart index bd197371a..ead688242 100644 --- a/lib/pages/share/view.dart +++ b/lib/pages/share/view.dart @@ -112,6 +112,7 @@ class _SharePanelState extends State { itemCount: _userList.length, controller: _scrollController, childBuilder: (index) { + final item = _userList[index]; return GestureDetector( onTap: () { _selectedIndex = index; @@ -131,13 +132,13 @@ class _SharePanelState extends State { 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), diff --git a/lib/pages/video/ai_conclusion/view.dart b/lib/pages/video/ai_conclusion/view.dart index 7950f6150..5af73570a 100644 --- a/lib/pages/video/ai_conclusion/view.dart +++ b/lib/pages/video/ai_conclusion/view.dart @@ -88,12 +88,13 @@ class _AiDetailState extends CommonCollapseSlidePageState { 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 { ), ), 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( diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index be8c5f7a8..4ff52614a 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -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']}', diff --git a/lib/pages/video/introduction/ugc/view.dart b/lib/pages/video/introduction/ugc/view.dart index a1a36650d..18ec92c34 100644 --- a/lib/pages/video/introduction/ugc/view.dart +++ b/lib/pages/video/introduction/ugc/view.dart @@ -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 { 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 { 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 { 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 { ], 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 { ); } - 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 { }); 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 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(), + ), + ), + ); + } } diff --git a/lib/pages/video/introduction/ugc/widgets/page.dart b/lib/pages/video/introduction/ugc/widgets/page.dart index dbaaacebb..088cd40e6 100644 --- a/lib/pages/video/introduction/ugc/widgets/page.dart +++ b/lib/pages/video/introduction/ugc/widgets/page.dart @@ -139,6 +139,7 @@ class _PagesPanelState extends State { 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 { 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 { } }, child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, horizontal: 8), + padding: const EdgeInsets.all(8), child: Row( children: [ if (isCurrentIndex) ...[ @@ -182,17 +182,18 @@ class _PagesPanelState extends State { 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, - )) + ), ], ), ), diff --git a/lib/pages/video/medialist/view.dart b/lib/pages/video/medialist/view.dart index dd54315e3..eed3b4311 100644 --- a/lib/pages/video/medialist/view.dart +++ b/lib/pages/video/medialist/view.dart @@ -42,7 +42,7 @@ class MediaListPanel extends CommonCollapseSlidePage { final bool desc; final VoidCallback onReverse; final RefreshCallback? loadPrevious; - final ValueChanged? onDelete; + final Function(dynamic item, int index)? onDelete; @override State 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( diff --git a/lib/pages/video/post_panel/view.dart b/lib/pages/video/post_panel/view.dart index f00403c0c..96942d63e 100644 --- a/lib/pages/video/post_panel/view.dart +++ b/lib/pages/video/post_panel/view.dart @@ -106,302 +106,9 @@ class _PostPanelState extends CommonCollapseSlidePageState { 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( - initialValue: - list![index].category, - onSelected: (item) { - list![index].category = item; - List - 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 { 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 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 { onPressed: () { updateSegment( isFirst: isFirst, - index: index, + item: item, value: currentPos, ); (context as Element).markNeedsBuild(); @@ -498,7 +205,7 @@ class _PostPanelState extends CommonCollapseSlidePageState { onPressed: () { updateSegment( isFirst: isFirst, - index: index, + item: item, value: isFirst ? 0 : videoDuration, ); (context as Element).markNeedsBuild(); @@ -553,7 +260,7 @@ class _PostPanelState extends CommonCollapseSlidePageState { if (duration <= videoDuration) { updateSegment( isFirst: isFirst, - index: index, + item: item, value: duration, ); (context as Element).markNeedsBuild(); @@ -629,4 +336,241 @@ class _PostPanelState extends CommonCollapseSlidePageState { }, ); } + + 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( + initialValue: item.category, + onSelected: (e) { + item.category = e; + List 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( + 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( + 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( + 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(), + ), + ); + } + }, + ), + ), + ], + ); + }, + ); + } } diff --git a/lib/pages/video/reply/view.dart b/lib/pages/video/reply/view.dart index 54529ae57..824677555 100644 --- a/lib/pages/video/reply/view.dart +++ b/lib/pages/video/reply/view.dart @@ -220,10 +220,9 @@ class _VideoReplyPanelState extends State 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 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, ), ); } diff --git a/lib/pages/video/reply/widgets/reply_item_grpc.dart b/lib/pages/video/reply/widgets/reply_item_grpc.dart index c8269a60c..80a61f282 100644 --- a/lib/pages/video/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/reply/widgets/reply_item_grpc.dart @@ -58,7 +58,7 @@ class ReplyItemGrpc extends StatelessWidget { final Function(ReplyInfo replyItem, int? rpid)? replyReply; final bool needDivider; final ValueChanged? onReply; - final ValueChanged? 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? onDismissed; final Function(List, int)? callback; final ValueChanged? onCheckReply; - final Function(bool isUpTop, int rpid)? onToggleTop; + final ValueChanged? 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, diff --git a/lib/pages/video/reply_reply/view.dart b/lib/pages/video/reply_reply/view.dart index 80056a5ac..1a8aa0729 100644 --- a/lib/pages/video/reply_reply/view.dart +++ b/lib/pages/video/reply_reply/view.dart @@ -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, - ), ); } diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index cf9d9a4d1..afbdeadc8 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -593,15 +593,16 @@ class HeaderControlState extends State { ), ), ), - 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 { ); }, // 可能包含会员解锁画质 - 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, ), - ), - ] + ); + }), ], ), ), diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index a28e207a5..e3506b6a5 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -106,12 +106,13 @@ class _WhisperPageState extends State { 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 { }; } - 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); + }, + ); + }), ), - ); + ), + ); + } } diff --git a/lib/pages/whisper_secondary/view.dart b/lib/pages/whisper_secondary/view.dart index 1f5e67144..97c3da1ac 100644 --- a/lib/pages/whisper_secondary/view.dart +++ b/lib/pages/whisper_secondary/view.dart @@ -97,12 +97,13 @@ class _WhisperSecPageState extends State { 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), ); }, diff --git a/lib/utils/request_utils.dart b/lib/utils/request_utils.dart index 70d24505f..cfb2453f0 100644 --- a/lib/utils/request_utils.dart +++ b/lib/utils/request_utils.dart @@ -379,11 +379,12 @@ class RequestUtils { child: Builder( builder: (context) => Column( children: List.generate(list.length, (index) { + final item = list[index]; return RadioWidget( padding: const EdgeInsets.only(left: 14), - title: list[index].title, + title: item.title, groupValue: checkedId, - value: list[index].id, + value: item.id, onChanged: (value) { checkedId = value; if (context.mounted) {