mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-15 06:50:09 +08:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a581945c9e | ||
|
|
331fd0d619 | ||
|
|
c6e229d571 | ||
|
|
b2c3b1ff95 | ||
|
|
3fc12fcc09 | ||
|
|
e098631553 | ||
|
|
0fcd55755e | ||
|
|
65e7c0c4f4 |
@@ -47,6 +47,9 @@
|
||||
|
||||
## feat
|
||||
|
||||
- [x] 互动视频
|
||||
- [x] 发评反诈
|
||||
- [x] 高能进度条
|
||||
- [x] 滑动跳转预览视频缩略图
|
||||
- [x] Live Photo
|
||||
- [x] 复制/移动收藏夹/稍后再看视频
|
||||
|
||||
@@ -12,6 +12,7 @@ class PBadge extends StatelessWidget {
|
||||
final double? fs;
|
||||
final String? semanticsLabel;
|
||||
final bool bold;
|
||||
final double? textScaleFactor;
|
||||
|
||||
const PBadge({
|
||||
super.key,
|
||||
@@ -26,6 +27,7 @@ class PBadge extends StatelessWidget {
|
||||
this.fs = 11,
|
||||
this.semanticsLabel,
|
||||
this.bold = true,
|
||||
this.textScaleFactor,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -71,6 +73,9 @@ class PBadge extends StatelessWidget {
|
||||
),
|
||||
child: Text(
|
||||
text ?? "",
|
||||
textScaler: textScaleFactor != null
|
||||
? TextScaler.linear(textScaleFactor!)
|
||||
: null,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: fs ?? fontSize,
|
||||
|
||||
@@ -977,7 +977,6 @@ class VideoHttp {
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
dynamic data = res.data['data'];
|
||||
List subtitlesJson = data['subtitle']['subtitles'];
|
||||
/*
|
||||
[
|
||||
{
|
||||
@@ -995,10 +994,11 @@ class VideoHttp {
|
||||
*/
|
||||
return {
|
||||
'status': true,
|
||||
'data': subtitlesJson,
|
||||
'data': data['subtitle']['subtitles'],
|
||||
'view_points': data['view_points'],
|
||||
// 'last_play_time': data['last_play_time'],
|
||||
'last_play_cid': data['last_play_cid'],
|
||||
'interaction': data['interaction'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
|
||||
@@ -265,7 +265,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: showIntroDetail,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: SizedBox(
|
||||
height: isLandscape ? 115 : 115 / 0.75,
|
||||
child: Column(
|
||||
|
||||
@@ -82,48 +82,48 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Obx(
|
||||
() => Visibility(
|
||||
visible: _bangumiController.isLogin.value,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Obx(
|
||||
() => Text(
|
||||
'最近${widget.tabType == TabType.bangumi ? '追番' : '追剧'}${_bangumiController.followCount.value == -1 ? '' : ' ${_bangumiController.followCount.value}'}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
() => _bangumiController.isLogin.value
|
||||
? Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Obx(
|
||||
() => Text(
|
||||
'最近${widget.tabType == TabType.bangumi ? '追番' : '追剧'}${_bangumiController.followCount.value == -1 ? '' : ' ${_bangumiController.followCount.value}'}',
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '刷新',
|
||||
onPressed: () {
|
||||
_bangumiController
|
||||
..followPage = 1
|
||||
..followEnd = false
|
||||
..queryBangumiFollow();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '刷新',
|
||||
onPressed: () {
|
||||
_bangumiController
|
||||
..followPage = 1
|
||||
..followEnd = false
|
||||
..queryBangumiFollow();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Grid.smallCardWidth / 2 / 0.75 +
|
||||
MediaQuery.textScalerOf(context).scale(50),
|
||||
child: Obx(
|
||||
() => _buildFollowBody(
|
||||
_bangumiController.followState.value),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Grid.smallCardWidth / 2 / 0.75 +
|
||||
MediaQuery.textScalerOf(context).scale(50),
|
||||
child: Obx(
|
||||
() => _buildFollowBody(
|
||||
_bangumiController.followState.value),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
|
||||
@@ -305,11 +305,16 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
|
||||
case 'DYNAMIC_TYPE_COMMON_SQUARE':
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed('/webview', parameters: {
|
||||
'url': item.modules.moduleDynamic.major.common['jump_url'],
|
||||
'type': 'url',
|
||||
'pageTitle': item.modules.moduleDynamic.major.common['title']
|
||||
});
|
||||
try {
|
||||
String url = item.modules.moduleDynamic.major.common['jump_url'];
|
||||
if (url.contains('bangumi/play') && Utils.viewPgcFromUri(url)) {
|
||||
return;
|
||||
}
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
parameters: {'url': url},
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
|
||||
@@ -55,7 +55,8 @@ class _EditProfilePageState extends State<EditProfilePage> {
|
||||
_getInfo() async {
|
||||
Map<String, String> data = {
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||
'',
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
@@ -329,7 +330,8 @@ class _EditProfilePageState extends State<EditProfilePage> {
|
||||
}) async {
|
||||
Map<String, String> data = {
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||
'',
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
|
||||
@@ -79,11 +79,11 @@ class UserInfoCard extends StatelessWidget {
|
||||
: images.nightImgurl?.http2https)
|
||||
: images.imgUrl?.http2https;
|
||||
return Hero(
|
||||
tag: imgUrl ?? 'bgTag',
|
||||
tag: imgUrl ?? '',
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.imageView(
|
||||
imgList: [SourceModel(url: imgUrl ?? 'bgTag')],
|
||||
imgList: [SourceModel(url: imgUrl ?? '')],
|
||||
);
|
||||
},
|
||||
child: CachedNetworkImage(
|
||||
@@ -447,22 +447,22 @@ class UserInfoCard extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
|
||||
_buildAvatar(BuildContext context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 2.5,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Hero(
|
||||
tag: card.face ?? 'avatarTag',
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.imageView(
|
||||
imgList: [SourceModel(url: card.face ?? 'avatarTag')],
|
||||
);
|
||||
},
|
||||
_buildAvatar(BuildContext context) => Hero(
|
||||
tag: card.face ?? '',
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.imageView(
|
||||
imgList: [SourceModel(url: card.face ?? '')],
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 2.5,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: NetworkImgLayer(
|
||||
src: card.face,
|
||||
type: 'avatar',
|
||||
|
||||
@@ -127,7 +127,7 @@ class _MinePageState extends State<MinePage> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _mineController.onLogin,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@@ -403,24 +403,18 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Container(
|
||||
height:
|
||||
MediaQuery.textScalerOf(context).scale(13),
|
||||
width: MediaQuery.textScalerOf(context).scale(13),
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _blockColor[index],
|
||||
),
|
||||
height: 10,
|
||||
width: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _blockColor[index],
|
||||
),
|
||||
),
|
||||
style: TextStyle(fontSize: 13),
|
||||
style: TextStyle(fontSize: 13, height: 1),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ${_blockSettings[index].first.title}',
|
||||
style: TextStyle(fontSize: 13),
|
||||
style: TextStyle(fontSize: 13, height: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -451,23 +445,18 @@ class _SponsorBlockPageState extends State<SponsorBlockPage> {
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Container(
|
||||
height: MediaQuery.textScalerOf(context).scale(15),
|
||||
height: 10,
|
||||
width: 10,
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _blockColor[index],
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _blockColor[index],
|
||||
),
|
||||
),
|
||||
style: TextStyle(fontSize: 14),
|
||||
style: TextStyle(fontSize: 14, height: 1),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ${_blockSettings[index].first.title}',
|
||||
style: TextStyle(fontSize: 14),
|
||||
style: TextStyle(fontSize: 14, height: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -425,24 +425,18 @@ class VideoDetailController extends GetxController
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Container(
|
||||
height:
|
||||
MediaQuery.textScalerOf(context).scale(14),
|
||||
height: 10,
|
||||
width: 10,
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _getColor(item),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _getColor(item),
|
||||
),
|
||||
),
|
||||
style: TextStyle(fontSize: 14),
|
||||
style: TextStyle(fontSize: 14, height: 1),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ${item.title}',
|
||||
style: TextStyle(fontSize: 14),
|
||||
style: TextStyle(fontSize: 14, height: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -528,24 +522,18 @@ class VideoDetailController extends GetxController
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Container(
|
||||
height:
|
||||
MediaQuery.textScalerOf(context).scale(14),
|
||||
height: 10,
|
||||
width: 10,
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _getColor(item.segmentType),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _getColor(item.segmentType),
|
||||
),
|
||||
),
|
||||
style: TextStyle(fontSize: 14),
|
||||
style: TextStyle(fontSize: 14, height: 1),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ${item.segmentType.title}',
|
||||
style: TextStyle(fontSize: 14),
|
||||
style: TextStyle(fontSize: 14, height: 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1858,6 +1846,31 @@ class VideoDetailController extends GetxController
|
||||
});
|
||||
}
|
||||
|
||||
// interactive video
|
||||
dynamic graphVersion;
|
||||
Map? steinEdgeInfo;
|
||||
late final RxBool showSteinEdgeInfo = false.obs;
|
||||
void getSteinEdgeInfo([edgeId]) async {
|
||||
steinEdgeInfo = null;
|
||||
try {
|
||||
dynamic res = await Request().get(
|
||||
'https://api.bilibili.com/x/stein/edgeinfo_v2',
|
||||
queryParameters: {
|
||||
'bvid': bvid,
|
||||
'graph_version': graphVersion,
|
||||
if (edgeId != null) 'edge_id': edgeId,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
steinEdgeInfo = res.data['data'];
|
||||
} else {
|
||||
debugPrint('getSteinEdgeInfo error: ${res.data['message']}');
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('getSteinEdgeInfo: $e');
|
||||
}
|
||||
}
|
||||
|
||||
late bool continuePlayingPart = GStorage.continuePlayingPart;
|
||||
|
||||
Future _querySubtitles() async {
|
||||
@@ -1866,6 +1879,19 @@ class VideoDetailController extends GetxController
|
||||
// SmartDialog.showToast('查询字幕错误,${res["msg"]}');
|
||||
// }
|
||||
if (res['status']) {
|
||||
// interactive video
|
||||
if (graphVersion == null) {
|
||||
try {
|
||||
final introCtr = Get.find<VideoIntroController>(tag: heroTag);
|
||||
if (introCtr.videoDetail.value.rights?['is_stein_gate'] == 1) {
|
||||
graphVersion = res['interaction']?['graph_version'];
|
||||
getSteinEdgeInfo();
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('handle stein: $e');
|
||||
}
|
||||
}
|
||||
|
||||
if (continuePlayingPart) {
|
||||
continuePlayingPart = false;
|
||||
try {
|
||||
@@ -1957,7 +1983,7 @@ class VideoDetailController extends GetxController
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
onReset() {
|
||||
onReset([isStein]) {
|
||||
playedTime = null;
|
||||
videoUrl = null;
|
||||
audioUrl = null;
|
||||
@@ -1974,10 +2000,19 @@ class VideoDetailController extends GetxController
|
||||
viewPointList.clear();
|
||||
|
||||
// sponsor block
|
||||
positionSubscription?.cancel();
|
||||
videoLabel.value = '';
|
||||
segmentList.clear();
|
||||
_segmentProgressList = null;
|
||||
if (enableSponsorBlock) {
|
||||
positionSubscription?.cancel();
|
||||
videoLabel.value = '';
|
||||
segmentList.clear();
|
||||
_segmentProgressList = null;
|
||||
}
|
||||
|
||||
// interactive video
|
||||
if (isStein != true) {
|
||||
graphVersion = null;
|
||||
}
|
||||
steinEdgeInfo = null;
|
||||
showSteinEdgeInfo.value = false;
|
||||
}
|
||||
|
||||
late final showDmChart = GStorage.showDmChart;
|
||||
|
||||
@@ -578,13 +578,13 @@ class VideoIntroController extends GetxController
|
||||
}
|
||||
|
||||
// 修改分P或番剧分集
|
||||
Future changeSeasonOrbangu(epid, bvid, cid, aid, cover) async {
|
||||
Future changeSeasonOrbangu(epid, bvid, cid, aid, cover, [isStein]) async {
|
||||
// 重新获取视频资源
|
||||
final videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag)
|
||||
..plPlayerController.pause()
|
||||
..makeHeartBeat()
|
||||
..updateMediaListHistory(aid)
|
||||
..onReset()
|
||||
..onReset(isStein)
|
||||
..bvid = bvid
|
||||
..oid.value = aid ?? IdUtils.bv2av(bvid)
|
||||
..cid.value = cid
|
||||
|
||||
@@ -308,223 +308,192 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
constraints.viewportMainAxisExtent * 1.25;
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
top: 10),
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
top: 10,
|
||||
),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: showIntroDetail,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: videoItem['staff'] == null
|
||||
? GestureDetector(
|
||||
onTap: onPushMember,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
src: widget.loadingStatus
|
||||
? videoItem['owner']?.face ?? ""
|
||||
: videoDetail.owner!.face,
|
||||
width: 35,
|
||||
height: 35,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.loadingStatus
|
||||
? videoItem['owner']?.name ?? ""
|
||||
: videoDetail.owner!.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
// color: t.colorScheme.primary,
|
||||
),
|
||||
// semanticsLabel: "UP主:${owner.name}",
|
||||
),
|
||||
const SizedBox(height: 0),
|
||||
Obx(
|
||||
() => Text(
|
||||
'${Utils.numFormat(videoIntroController.userStat.value['follower'])}粉丝',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
followButton(context, t),
|
||||
],
|
||||
),
|
||||
)
|
||||
: SelfSizedHorizontalList(
|
||||
gapSize: 25,
|
||||
itemCount: videoItem['staff'].length,
|
||||
childBuilder: (index) => GestureDetector(
|
||||
onTap: () {
|
||||
int? ownerMid = !widget.loadingStatus
|
||||
? 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}',
|
||||
// arguments: {
|
||||
// 'face':
|
||||
// videoItem['staff'][index].face,
|
||||
// 'heroTag': Utils.makeHeroTag(
|
||||
// videoItem['staff'][index].mid),
|
||||
// },
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
src: videoItem['staff'][index].face,
|
||||
width: 35,
|
||||
height: 35,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
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.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isHorizontal) ...[
|
||||
const SizedBox(width: 10),
|
||||
Expanded(child: actionGrid(context, videoIntroController)),
|
||||
]
|
||||
],
|
||||
),
|
||||
if (videoIntroController.videoDetail.value.argueMsg?.isNotEmpty ==
|
||||
true &&
|
||||
videoIntroController.showArgueMsg) ...[
|
||||
const SizedBox(height: 8),
|
||||
Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
child: Text.rich(
|
||||
TextSpan(children: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Icon(
|
||||
size: 17,
|
||||
Icons.warning_rounded,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text:
|
||||
' ${videoIntroController.videoDetail.value.argueMsg}')
|
||||
]),
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: showIntroDetail,
|
||||
child: ExpandablePanel(
|
||||
controller: videoIntroController.expandableCtr,
|
||||
collapsed: GestureDetector(
|
||||
onLongPress: () {
|
||||
feedBack();
|
||||
Utils.copyText(
|
||||
'${videoDetail.title ?? videoItem['title'] ?? ''}');
|
||||
},
|
||||
child: _buildVideoTitle(),
|
||||
),
|
||||
expanded: GestureDetector(
|
||||
onLongPress: () {
|
||||
feedBack();
|
||||
Utils.copyText(
|
||||
'${videoDetail.title ?? videoItem['title'] ?? ''}');
|
||||
},
|
||||
child: _buildVideoTitle(true),
|
||||
),
|
||||
theme: const ExpandableThemeData(
|
||||
animationDuration: Duration(milliseconds: 300),
|
||||
scrollAnimationDuration: Duration(milliseconds: 300),
|
||||
crossFadePoint: 0,
|
||||
fadeCurve: Curves.ease,
|
||||
sizeCurve: Curves.linear,
|
||||
),
|
||||
),
|
||||
),
|
||||
Stack(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: showIntroDetail,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Row(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: videoItem['staff'] == null
|
||||
? GestureDetector(
|
||||
onTap: onPushMember,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
src: widget.loadingStatus
|
||||
? videoItem['owner']?.face ?? ""
|
||||
: videoDetail.owner!.face,
|
||||
width: 35,
|
||||
height: 35,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.loadingStatus
|
||||
? videoItem['owner']?.name ??
|
||||
""
|
||||
: videoDetail.owner!.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
// color: t.colorScheme.primary,
|
||||
),
|
||||
// semanticsLabel: "UP主:${owner.name}",
|
||||
),
|
||||
const SizedBox(height: 0),
|
||||
Obx(
|
||||
() => Text(
|
||||
'${Utils.numFormat(videoIntroController.userStat.value['follower'])}粉丝',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
followButton(context, t),
|
||||
],
|
||||
),
|
||||
)
|
||||
: SelfSizedHorizontalList(
|
||||
gapSize: 25,
|
||||
itemCount: videoItem['staff'].length,
|
||||
childBuilder: (index) => GestureDetector(
|
||||
onTap: () {
|
||||
int? ownerMid = !widget.loadingStatus
|
||||
? 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}',
|
||||
// arguments: {
|
||||
// 'face':
|
||||
// videoItem['staff'][index].face,
|
||||
// 'heroTag': Utils.makeHeroTag(
|
||||
// videoItem['staff'][index].mid),
|
||||
// },
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
src: videoItem['staff'][index].face,
|
||||
width: 35,
|
||||
height: 35,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
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.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isHorizontal) ...[
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: actionGrid(context, videoIntroController)),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ExpandablePanel(
|
||||
controller: videoIntroController.expandableCtr,
|
||||
collapsed: GestureDetector(
|
||||
onLongPress: () {
|
||||
feedBack();
|
||||
Utils.copyText(
|
||||
'${videoDetail.title ?? videoItem['title'] ?? ''}');
|
||||
},
|
||||
child: _buildVideoTitle(),
|
||||
),
|
||||
expanded: GestureDetector(
|
||||
onLongPress: () {
|
||||
feedBack();
|
||||
Utils.copyText(
|
||||
'${videoDetail.title ?? videoItem['title'] ?? ''}');
|
||||
},
|
||||
child: _buildVideoTitle(true),
|
||||
),
|
||||
theme: const ExpandableThemeData(
|
||||
animationDuration: Duration(milliseconds: 300),
|
||||
scrollAnimationDuration: Duration(milliseconds: 300),
|
||||
crossFadePoint: 0,
|
||||
fadeCurve: Curves.ease,
|
||||
sizeCurve: Curves.linear,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Stack(
|
||||
children: [
|
||||
Row(
|
||||
children: <Widget>[
|
||||
statView(
|
||||
context: context,
|
||||
@@ -577,169 +546,195 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (videoIntroController.enableAi)
|
||||
Positioned(
|
||||
right: 10,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Center(
|
||||
child: Semantics(
|
||||
label: 'AI总结',
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final res =
|
||||
await videoIntroController.aiConclusion();
|
||||
if (res['status']) {
|
||||
widget.showAiBottomSheet();
|
||||
}
|
||||
},
|
||||
child:
|
||||
Image.asset('assets/images/ai.png', height: 22),
|
||||
if (videoIntroController.enableAi)
|
||||
Positioned(
|
||||
right: 10,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Center(
|
||||
child: Semantics(
|
||||
label: 'AI总结',
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final res =
|
||||
await videoIntroController.aiConclusion();
|
||||
if (res['status']) {
|
||||
widget.showAiBottomSheet();
|
||||
}
|
||||
},
|
||||
child: Image.asset('assets/images/ai.png',
|
||||
height: 22),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: showIntroDetail,
|
||||
child: ExpandablePanel(
|
||||
controller: videoIntroController.expandableCtr,
|
||||
collapsed: const SizedBox.shrink(),
|
||||
expanded: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Utils.copyText(
|
||||
'${videoIntroController.videoDetail.value.bvid}');
|
||||
},
|
||||
child: Text(
|
||||
videoIntroController.videoDetail.value.bvid ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (videoIntroController
|
||||
.videoDetail.value.descV2.isNullOrEmpty.not) ...[
|
||||
const SizedBox(height: 8),
|
||||
SelectableText.rich(
|
||||
style: const TextStyle(
|
||||
height: 1.4,
|
||||
// fontSize: 13,
|
||||
),
|
||||
TextSpan(
|
||||
children: [
|
||||
buildContent(context,
|
||||
videoIntroController.videoDetail.value),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
if (videoIntroController.videoTags is List &&
|
||||
videoIntroController.videoTags.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: (videoIntroController.videoTags as List)
|
||||
.map(
|
||||
(item) => SearchText(
|
||||
fontSize: 13,
|
||||
text: item['tag_name'],
|
||||
onTap: (_) => Get.toNamed('/searchResult',
|
||||
parameters: {
|
||||
'keyword': item['tag_name']
|
||||
}),
|
||||
onLongPress: (_) =>
|
||||
Utils.copyText(item['tag_name']),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
theme: const ExpandableThemeData(
|
||||
animationDuration: Duration(milliseconds: 300),
|
||||
scrollAnimationDuration: Duration(milliseconds: 300),
|
||||
crossFadePoint: 0,
|
||||
fadeCurve: Curves.ease,
|
||||
sizeCurve: Curves.linear,
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => videoIntroController.queryVideoIntroData.value["status"]
|
||||
? const SizedBox.shrink()
|
||||
: Center(
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () {
|
||||
videoIntroController
|
||||
.queryVideoIntroData.value["status"] = true;
|
||||
videoIntroController.queryVideoIntro();
|
||||
if (videoDetailCtr.videoUrl.isNullOrEmpty &&
|
||||
videoDetailCtr.isQuerying.not) {
|
||||
videoDetailCtr.queryVideoUrl();
|
||||
}
|
||||
},
|
||||
label: const Text("点此重新加载"),
|
||||
),
|
||||
if (videoIntroController
|
||||
.videoDetail.value.argueMsg?.isNotEmpty ==
|
||||
true &&
|
||||
videoIntroController.showArgueMsg) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Icon(
|
||||
size: 13,
|
||||
Icons.error_outline,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
WidgetSpan(child: SizedBox(width: 2)),
|
||||
TextSpan(
|
||||
text:
|
||||
'${videoIntroController.videoDetail.value.argueMsg}',
|
||||
)
|
||||
],
|
||||
),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
ExpandablePanel(
|
||||
controller: videoIntroController.expandableCtr,
|
||||
collapsed: const SizedBox.shrink(),
|
||||
expanded: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Utils.copyText(
|
||||
'${videoIntroController.videoDetail.value.bvid}');
|
||||
},
|
||||
child: Text(
|
||||
videoIntroController.videoDetail.value.bvid ?? '',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (videoIntroController
|
||||
.videoDetail.value.descV2.isNullOrEmpty.not) ...[
|
||||
const SizedBox(height: 8),
|
||||
SelectableText.rich(
|
||||
style: const TextStyle(
|
||||
height: 1.4,
|
||||
// fontSize: 13,
|
||||
),
|
||||
TextSpan(
|
||||
children: [
|
||||
buildContent(context,
|
||||
videoIntroController.videoDetail.value),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
if (videoIntroController.videoTags is List &&
|
||||
videoIntroController.videoTags.isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: (videoIntroController.videoTags as List)
|
||||
.map(
|
||||
(item) => SearchText(
|
||||
fontSize: 13,
|
||||
text: item['tag_name'],
|
||||
onTap: (_) => Get.toNamed('/searchResult',
|
||||
parameters: {
|
||||
'keyword': item['tag_name']
|
||||
}),
|
||||
onLongPress: (_) =>
|
||||
Utils.copyText(item['tag_name']),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
theme: const ExpandableThemeData(
|
||||
animationDuration: Duration(milliseconds: 300),
|
||||
scrollAnimationDuration: Duration(milliseconds: 300),
|
||||
crossFadePoint: 0,
|
||||
fadeCurve: Curves.ease,
|
||||
sizeCurve: Curves.linear,
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() =>
|
||||
videoIntroController.queryVideoIntroData.value["status"]
|
||||
? const SizedBox.shrink()
|
||||
: Center(
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: () {
|
||||
videoIntroController.queryVideoIntroData
|
||||
.value["status"] = true;
|
||||
videoIntroController.queryVideoIntro();
|
||||
if (videoDetailCtr.videoUrl.isNullOrEmpty &&
|
||||
videoDetailCtr.isQuerying.not) {
|
||||
videoDetailCtr.queryVideoUrl();
|
||||
}
|
||||
},
|
||||
label: const Text("点此重新加载"),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 点赞收藏转发 布局样式1
|
||||
// SingleChildScrollView(
|
||||
// padding: const EdgeInsets.only(top: 7, bottom: 7),
|
||||
// scrollDirection: Axis.horizontal,
|
||||
// child: actionRow(
|
||||
// context,
|
||||
// videoIntroController,
|
||||
// videoDetailCtr,
|
||||
// ),
|
||||
// ),
|
||||
// 点赞收藏转发 布局样式2
|
||||
if (!isHorizontal) ...[
|
||||
const SizedBox(height: 8),
|
||||
actionGrid(context, videoIntroController),
|
||||
],
|
||||
// 合集
|
||||
if (!widget.loadingStatus &&
|
||||
videoDetail.ugcSeason != null &&
|
||||
(context.orientation != Orientation.landscape ||
|
||||
(context.orientation == Orientation.landscape &&
|
||||
videoDetailCtr.horizontalSeasonPanel.not)))
|
||||
SeasonPanel(
|
||||
heroTag: widget.heroTag,
|
||||
ugcSeason: videoDetail.ugcSeason!,
|
||||
changeFuc: videoIntroController.changeSeasonOrbangu,
|
||||
showEpisodes: widget.showEpisodes,
|
||||
pages: videoDetail.pages,
|
||||
videoIntroController: videoIntroController,
|
||||
),
|
||||
if (!widget.loadingStatus &&
|
||||
videoDetail.pages != null &&
|
||||
videoDetail.pages!.length > 1 &&
|
||||
(context.orientation != Orientation.landscape ||
|
||||
(context.orientation == Orientation.landscape &&
|
||||
videoDetailCtr.horizontalSeasonPanel.not))) ...[
|
||||
PagesPanel(
|
||||
heroTag: widget.heroTag,
|
||||
videoIntroController: videoIntroController,
|
||||
bvid: videoIntroController.bvid,
|
||||
changeFuc: videoIntroController.changeSeasonOrbangu,
|
||||
showEpisodes: widget.showEpisodes,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
// 点赞收藏转发 布局样式1
|
||||
// SingleChildScrollView(
|
||||
// padding: const EdgeInsets.only(top: 7, bottom: 7),
|
||||
// scrollDirection: Axis.horizontal,
|
||||
// child: actionRow(
|
||||
// context,
|
||||
// videoIntroController,
|
||||
// videoDetailCtr,
|
||||
// ),
|
||||
// ),
|
||||
// 点赞收藏转发 布局样式2
|
||||
if (!isHorizontal) ...[
|
||||
const SizedBox(height: 8),
|
||||
actionGrid(context, videoIntroController),
|
||||
],
|
||||
// 合集
|
||||
if (!widget.loadingStatus &&
|
||||
videoDetail.ugcSeason != null &&
|
||||
(context.orientation != Orientation.landscape ||
|
||||
(context.orientation == Orientation.landscape &&
|
||||
videoDetailCtr.horizontalSeasonPanel.not)))
|
||||
SeasonPanel(
|
||||
heroTag: widget.heroTag,
|
||||
ugcSeason: videoDetail.ugcSeason!,
|
||||
changeFuc: videoIntroController.changeSeasonOrbangu,
|
||||
showEpisodes: widget.showEpisodes,
|
||||
pages: videoDetail.pages,
|
||||
videoIntroController: videoIntroController,
|
||||
),
|
||||
if (!widget.loadingStatus &&
|
||||
videoDetail.pages != null &&
|
||||
videoDetail.pages!.length > 1 &&
|
||||
(context.orientation != Orientation.landscape ||
|
||||
(context.orientation == Orientation.landscape &&
|
||||
videoDetailCtr.horizontalSeasonPanel.not))) ...[
|
||||
PagesPanel(
|
||||
heroTag: widget.heroTag,
|
||||
videoIntroController: videoIntroController,
|
||||
bvid: videoIntroController.bvid,
|
||||
changeFuc: videoIntroController.changeSeasonOrbangu,
|
||||
showEpisodes: widget.showEpisodes,
|
||||
),
|
||||
],
|
||||
],
|
||||
)),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -360,6 +360,7 @@ class ReplyItem extends StatelessWidget {
|
||||
type: 'line',
|
||||
fs: 9,
|
||||
semanticsLabel: '置顶',
|
||||
textScaleFactor: 1,
|
||||
),
|
||||
),
|
||||
const TextSpan(text: ' '),
|
||||
@@ -550,12 +551,13 @@ class ReplyItem extends StatelessWidget {
|
||||
if (replies![i].isUp!) ...[
|
||||
const TextSpan(text: ' '),
|
||||
const WidgetSpan(
|
||||
alignment: PlaceholderAlignment.top,
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: PBadge(
|
||||
text: 'UP',
|
||||
size: 'small',
|
||||
stack: 'normal',
|
||||
fs: 9,
|
||||
textScaleFactor: 1,
|
||||
),
|
||||
),
|
||||
const TextSpan(text: ' '),
|
||||
|
||||
@@ -375,6 +375,7 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
type: 'line',
|
||||
fs: 9,
|
||||
semanticsLabel: '置顶',
|
||||
textScaleFactor: 1,
|
||||
),
|
||||
),
|
||||
const TextSpan(text: ' '),
|
||||
@@ -584,12 +585,13 @@ class ReplyItemGrpc extends StatelessWidget {
|
||||
if (replyItem.replies[i].mid == upMid) ...[
|
||||
const TextSpan(text: ' '),
|
||||
const WidgetSpan(
|
||||
alignment: PlaceholderAlignment.top,
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: PBadge(
|
||||
text: 'UP',
|
||||
size: 'small',
|
||||
stack: 'normal',
|
||||
fs: 9,
|
||||
textScaleFactor: 1,
|
||||
),
|
||||
),
|
||||
const TextSpan(text: ' '),
|
||||
|
||||
@@ -240,6 +240,15 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
// 播放器状态监听
|
||||
void playerListener(PlayerStatus? status) async {
|
||||
if (status == PlayerStatus.completed) {
|
||||
try {
|
||||
if ((videoDetailController.steinEdgeInfo?['edges']['questions'][0]
|
||||
['choices'] as List?)
|
||||
?.isNotEmpty ==
|
||||
true) {
|
||||
videoDetailController.showSteinEdgeInfo.value = true;
|
||||
return;
|
||||
}
|
||||
} catch (_) {}
|
||||
shutdownTimerService.handleWaitingFinished();
|
||||
bool notExitFlag = false;
|
||||
|
||||
@@ -417,6 +426,12 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
return;
|
||||
}
|
||||
|
||||
if (videoDetailController.plPlayerController.playerStatus.status.value ==
|
||||
PlayerStatus.playing &&
|
||||
videoDetailController.playerStatus != PlayerStatus.playing) {
|
||||
videoDetailController.plPlayerController.pause();
|
||||
}
|
||||
|
||||
isShowing = true;
|
||||
PlPlayerController.setPlayCallBack(playCallBack);
|
||||
videoIntroController.startTimer();
|
||||
@@ -1415,6 +1430,69 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
// child: Text('index'),
|
||||
// ),
|
||||
// ),
|
||||
|
||||
Obx(
|
||||
() {
|
||||
if (videoDetailController.showSteinEdgeInfo.value) {
|
||||
try {
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: plPlayerController?.showControls.value == true
|
||||
? 75
|
||||
: 16,
|
||||
),
|
||||
child: Wrap(
|
||||
spacing: 25,
|
||||
runSpacing: 10,
|
||||
children: (videoDetailController.steinEdgeInfo!['edges']
|
||||
['questions'][0]['choices'] as List)
|
||||
.map((item) {
|
||||
return FilledButton.tonal(
|
||||
style: FilledButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondaryContainer
|
||||
.withOpacity(0.8),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 15,
|
||||
vertical: 10,
|
||||
),
|
||||
visualDensity:
|
||||
VisualDensity(horizontal: -2, vertical: -2),
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () {
|
||||
videoIntroController.changeSeasonOrbangu(
|
||||
null,
|
||||
videoDetailController.bvid,
|
||||
item['cid'],
|
||||
IdUtils.bv2av(videoDetailController.bvid),
|
||||
null,
|
||||
true,
|
||||
);
|
||||
videoDetailController
|
||||
.getSteinEdgeInfo(item['id']);
|
||||
},
|
||||
child: Text(item['option']),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('build stein edges: $e');
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -397,33 +397,7 @@ class _SendDanmakuPanelState extends CommonPublishPageState<SendDanmakuPanel> {
|
||||
builder: (context) => AlertDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
||||
title: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: 'Color Picker ',
|
||||
style: TextStyle(fontSize: 15),
|
||||
),
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Container(
|
||||
height: MediaQuery.textScalerOf(context).scale(13),
|
||||
width: MediaQuery.textScalerOf(context).scale(13),
|
||||
alignment: Alignment.center,
|
||||
child: Container(
|
||||
height: 10,
|
||||
width: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: _color.value,
|
||||
),
|
||||
),
|
||||
),
|
||||
style: TextStyle(fontSize: 13),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
title: Text('Color Picker'),
|
||||
content: SlideColorPicker(
|
||||
showResetBtn: false,
|
||||
color: _color.value,
|
||||
|
||||
@@ -285,7 +285,7 @@ class PlPlayerController {
|
||||
// 播放顺序相关
|
||||
PlayRepeat playRepeat = PlayRepeat.pause;
|
||||
|
||||
final GlobalKey<VideoState> key = GlobalKey<VideoState>();
|
||||
GlobalKey<VideoState> key = GlobalKey<VideoState>();
|
||||
|
||||
TextStyle get subTitleStyle => TextStyle(
|
||||
height: 1.5,
|
||||
@@ -1496,7 +1496,9 @@ class PlPlayerController {
|
||||
if (type == 'single' && playerCount.value > 1) {
|
||||
_playerCount.value -= 1;
|
||||
_heartDuration = 0;
|
||||
pause();
|
||||
if (!Get.previousRoute.startsWith('/video')) {
|
||||
pause();
|
||||
}
|
||||
return;
|
||||
}
|
||||
_playerCount.value = 0;
|
||||
|
||||
@@ -125,8 +125,13 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
}
|
||||
|
||||
// 双击播放、暂停
|
||||
void onDoubleTapCenter() {
|
||||
plPlayerController.videoPlayerController!.playOrPause();
|
||||
void onDoubleTapCenter() async {
|
||||
if (plPlayerController.videoPlayerController!.state.completed) {
|
||||
await plPlayerController.videoPlayerController!.seek(Duration.zero);
|
||||
plPlayerController.videoPlayerController!.play();
|
||||
} else {
|
||||
plPlayerController.videoPlayerController!.playOrPause();
|
||||
}
|
||||
}
|
||||
|
||||
void doubleTapFuc(String type) {
|
||||
@@ -154,6 +159,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
plPlayerController.key = GlobalKey<VideoState>();
|
||||
animationController = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 100));
|
||||
videoController = plPlayerController.videoController!;
|
||||
@@ -282,7 +288,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
|
||||
/// 播放暂停
|
||||
BottomControlType.playOrPause: PlayOrPauseButton(
|
||||
controller: plPlayerController,
|
||||
plPlayerController: plPlayerController,
|
||||
),
|
||||
|
||||
/// 下一集
|
||||
|
||||
@@ -7,13 +7,13 @@ import 'package:PiliPlus/plugin/pl_player/index.dart';
|
||||
class PlayOrPauseButton extends StatefulWidget {
|
||||
final double? iconSize;
|
||||
final Color? iconColor;
|
||||
final PlPlayerController? controller;
|
||||
final PlPlayerController plPlayerController;
|
||||
|
||||
const PlayOrPauseButton({
|
||||
super.key,
|
||||
this.iconSize,
|
||||
this.iconColor,
|
||||
this.controller,
|
||||
required this.plPlayerController,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -28,10 +28,12 @@ class PlayOrPauseButtonState extends State<PlayOrPauseButton>
|
||||
late Player player;
|
||||
bool isOpacity = false;
|
||||
|
||||
PlPlayerController get plPlayerController => widget.plPlayerController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
player = widget.controller!.videoPlayerController!;
|
||||
player = plPlayerController.videoPlayerController!;
|
||||
animation = AnimationController(
|
||||
vsync: this,
|
||||
value: player.state.playing ? 1 : 0,
|
||||
@@ -67,13 +69,20 @@ class PlayOrPauseButtonState extends State<PlayOrPauseButton>
|
||||
width: 42,
|
||||
height: 34,
|
||||
child: InkWell(
|
||||
onTap: player.playOrPause,
|
||||
onTap: () async {
|
||||
if (player.state.completed) {
|
||||
await player.seek(Duration.zero);
|
||||
player.play();
|
||||
} else {
|
||||
player.playOrPause();
|
||||
}
|
||||
},
|
||||
// iconSize: widget.iconSize ?? _theme(context).buttonBarButtonSize,
|
||||
// color: widget.iconColor ?? _theme(context).buttonBarButtonColor,
|
||||
child: Center(
|
||||
child: AnimatedIcon(
|
||||
semanticLabel:
|
||||
widget.controller!.videoPlayerController!.state.playing
|
||||
plPlayerController.videoPlayerController!.state.playing
|
||||
? '暂停'
|
||||
: '播放',
|
||||
progress: animation,
|
||||
|
||||
@@ -6,8 +6,10 @@ import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/dynamics_type.dart';
|
||||
import 'package:PiliPlus/models/common/tab_type.dart' hide tabsConfig;
|
||||
import 'package:PiliPlus/models/user/info.dart';
|
||||
import 'package:PiliPlus/models/user/stat.dart';
|
||||
import 'package:PiliPlus/pages/bangumi/controller.dart';
|
||||
import 'package:PiliPlus/pages/dynamics/tab/controller.dart';
|
||||
import 'package:PiliPlus/pages/live/controller.dart';
|
||||
import 'package:PiliPlus/pages/main/controller.dart';
|
||||
@@ -113,6 +115,18 @@ class LoginUtils {
|
||||
..isLogin.value = true
|
||||
..fetchLiveFollowing();
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
Get.find<BangumiController>(tag: TabType.bangumi.name)
|
||||
..isLogin.value = true
|
||||
..queryBangumiFollow();
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
Get.find<BangumiController>(tag: TabType.cinema.name)
|
||||
..isLogin.value = true
|
||||
..queryBangumiFollow();
|
||||
} catch (_) {}
|
||||
} else {
|
||||
// 获取用户信息失败
|
||||
SmartDialog.showNotify(
|
||||
@@ -170,6 +184,18 @@ class LoginUtils {
|
||||
Get.find<DynamicsTabController>(tag: tabsConfig[i]['tag']).onRefresh();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
try {
|
||||
Get.find<BangumiController>(tag: TabType.bangumi.name)
|
||||
..isLogin.value = false
|
||||
..followState.value = LoadingState.loading();
|
||||
} catch (_) {}
|
||||
|
||||
try {
|
||||
Get.find<BangumiController>(tag: TabType.cinema.name)
|
||||
..isLogin.value = false
|
||||
..followState.value = LoadingState.loading();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
static String buvid() {
|
||||
|
||||
@@ -373,7 +373,15 @@ class Utils {
|
||||
bool inApp = false,
|
||||
}) {
|
||||
if (inApp.not && GStorage.openInBrowser) {
|
||||
launchURL(url);
|
||||
if (RegExp(r'^(https?://)?((www|m).)?(bilibili|bv23).com')
|
||||
.hasMatch(url)) {
|
||||
toDupNamed(
|
||||
'/webview',
|
||||
parameters: {'url': url},
|
||||
);
|
||||
} else {
|
||||
launchURL(url);
|
||||
}
|
||||
} else {
|
||||
if (off) {
|
||||
Get.offNamed(
|
||||
|
||||
Reference in New Issue
Block a user