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

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