merge main
This commit is contained in:
@ -13,36 +13,47 @@ import 'package:pilipala/models/video/reply/item.dart';
|
||||
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class VideoDetailController extends GetxController
|
||||
with GetSingleTickerProviderStateMixin {
|
||||
int tabInitialIndex = 0;
|
||||
TabController? tabCtr;
|
||||
// tabs
|
||||
RxList<String> tabs = <String>['简介', '评论'].obs;
|
||||
|
||||
// 视频aid
|
||||
/// 路由传参
|
||||
String bvid = Get.parameters['bvid']!;
|
||||
int cid = int.parse(Get.parameters['cid']!);
|
||||
// 视频类型 默认投稿视频
|
||||
SearchType videoType = SearchType.video;
|
||||
|
||||
late PlayUrlModel data;
|
||||
// 当前画质
|
||||
late VideoQuality currentVideoQa;
|
||||
// 当前音质
|
||||
late AudioQuality currentAudioQa;
|
||||
|
||||
// 是否预渲染 骨架屏
|
||||
bool preRender = false;
|
||||
|
||||
// 视频详情 上个页面传入
|
||||
String heroTag = Get.arguments['heroTag'];
|
||||
// 视频详情
|
||||
Map videoItem = {};
|
||||
// 视频类型 默认投稿视频
|
||||
SearchType videoType = Get.arguments['videoType'] ?? SearchType.video;
|
||||
|
||||
/// tabs相关配置
|
||||
int tabInitialIndex = 0;
|
||||
late TabController tabCtr;
|
||||
RxList<String> tabs = <String>['简介', '评论'].obs;
|
||||
|
||||
// 请求返回的视频信息
|
||||
late PlayUrlModel data;
|
||||
// 请求状态
|
||||
RxBool isLoading = false.obs;
|
||||
|
||||
String heroTag = '';
|
||||
/// 播放器配置 画质 音质 解码格式
|
||||
late VideoQuality currentVideoQa;
|
||||
late AudioQuality currentAudioQa;
|
||||
late VideoDecodeFormats currentDecodeFormats;
|
||||
// PlPlayerController plPlayerController = PlPlayerController();
|
||||
// 是否开始自动播放 存在多p的情况下,第二p需要为true
|
||||
RxBool autoPlay = true.obs;
|
||||
// 视频资源是否有效
|
||||
RxBool isEffective = true.obs;
|
||||
// 封面图的展示
|
||||
RxBool isShowCover = true.obs;
|
||||
// 硬解
|
||||
RxBool enableHA = true.obs;
|
||||
|
||||
/// 本地存储
|
||||
Box user = GStrorage.user;
|
||||
Box localCache = GStrorage.localCache;
|
||||
Box setting = GStrorage.setting;
|
||||
|
||||
int oid = 0;
|
||||
// 评论id 请求楼中楼评论使用
|
||||
@ -52,15 +63,7 @@ class VideoDetailController extends GetxController
|
||||
final scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
Timer? timer;
|
||||
RxString bgCover = ''.obs;
|
||||
Box user = GStrorage.user;
|
||||
Box localCache = GStrorage.localCache;
|
||||
PlPlayerController plPlayerController = PlPlayerController.getInstance();
|
||||
// 是否开始自动播放 存在多p的情况下,第二p需要为true
|
||||
RxBool autoPlay = true.obs;
|
||||
// 视频资源是否有效
|
||||
RxBool isEffective = true.obs;
|
||||
// 封面图的展示
|
||||
RxBool isShowCover = true.obs;
|
||||
|
||||
late VideoItem firstVideo;
|
||||
late String videoUrl;
|
||||
@ -70,24 +73,23 @@ class VideoDetailController extends GetxController
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (Get.arguments.isNotEmpty) {
|
||||
if (Get.arguments.containsKey('videoItem')) {
|
||||
preRender = true;
|
||||
var args = Get.arguments['videoItem'];
|
||||
Map argMap = Get.arguments;
|
||||
var keys = argMap.keys.toList();
|
||||
if (keys.isNotEmpty) {
|
||||
if (keys.contains('videoItem')) {
|
||||
var args = argMap['videoItem'];
|
||||
if (args.pic != null && args.pic != '') {
|
||||
videoItem['pic'] = args.pic;
|
||||
bgCover.value = args.pic;
|
||||
}
|
||||
}
|
||||
if (Get.arguments.containsKey('pic')) {
|
||||
videoItem['pic'] = Get.arguments['pic'];
|
||||
bgCover.value = Get.arguments['pic'];
|
||||
if (keys.contains('pic')) {
|
||||
videoItem['pic'] = argMap['pic'];
|
||||
}
|
||||
heroTag = Get.arguments['heroTag'];
|
||||
videoType = Get.arguments['videoType'] ?? SearchType.video;
|
||||
}
|
||||
tabCtr = TabController(length: 2, vsync: this);
|
||||
// queryVideoUrl();
|
||||
autoPlay.value =
|
||||
setting.get(SettingBoxKey.autoPlayEnable, defaultValue: true);
|
||||
enableHA.value = setting.get(SettingBoxKey.enableHA, defaultValue: true);
|
||||
}
|
||||
|
||||
showReplyReplyPanel() {
|
||||
@ -120,8 +122,15 @@ class VideoDetailController extends GetxController
|
||||
/// 暂不匹配解码规则
|
||||
|
||||
/// 根据currentVideoQa 重新设置videoUrl
|
||||
firstVideo =
|
||||
data.dash!.video!.firstWhere((i) => i.id == currentVideoQa.code);
|
||||
// firstVideo =
|
||||
// data.dash!.video!.firstWhere((i) => i.id == currentVideoQa.code);
|
||||
// videoUrl = firstVideo.baseUrl!;
|
||||
|
||||
/// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl
|
||||
List<VideoItem> videoList =
|
||||
data.dash!.video!.where((i) => i.id == currentVideoQa.code).toList();
|
||||
firstVideo = videoList
|
||||
.firstWhere((i) => i.codecs!.startsWith(currentDecodeFormats.code));
|
||||
videoUrl = firstVideo.baseUrl!;
|
||||
|
||||
/// 根据currentAudioQa 重新设置audioUrl
|
||||
@ -133,6 +142,7 @@ class VideoDetailController extends GetxController
|
||||
}
|
||||
|
||||
Future playerInit({video, audio, seekToTime, duration}) async {
|
||||
print('data.timeLength:${data.timeLength}');
|
||||
await plPlayerController.setDataSource(
|
||||
DataSource(
|
||||
videoSource: video ?? videoUrl,
|
||||
@ -145,7 +155,7 @@ class VideoDetailController extends GetxController
|
||||
},
|
||||
),
|
||||
// 硬解
|
||||
enableHA: true,
|
||||
enableHA: enableHA.value,
|
||||
autoplay: autoPlay.value,
|
||||
seekTo: seekToTime ?? defaultST,
|
||||
duration: duration ?? Duration(milliseconds: data.timeLength ?? 0),
|
||||
@ -170,14 +180,73 @@ class VideoDetailController extends GetxController
|
||||
data = result['data'];
|
||||
|
||||
/// 优先顺序 省流模式 -> 设置中指定质量 -> 当前可选的最高质量
|
||||
firstVideo = data.dash!.video!.first;
|
||||
videoUrl = firstVideo.baseUrl!;
|
||||
//
|
||||
currentVideoQa = VideoQualityCode.fromCode(firstVideo.id!)!;
|
||||
// firstVideo = data.dash!.video!.first;
|
||||
// videoUrl = firstVideo.baseUrl!;
|
||||
// //
|
||||
// currentVideoQa = VideoQualityCode.fromCode(firstVideo.id!)!;
|
||||
|
||||
// /// 优先顺序 设置中指定质量 -> 当前可选的最高质量
|
||||
// AudioItem firstAudio =
|
||||
// data.dash!.audio!.isNotEmpty ? data.dash!.audio!.first : AudioItem();
|
||||
// audioUrl = firstAudio.baseUrl ?? '';
|
||||
|
||||
List<VideoItem> allVideosList = data.dash!.video!;
|
||||
|
||||
try {
|
||||
// 当前可播放的最高质量视频
|
||||
int currentHighVideoQa = allVideosList.first.quality!.code;
|
||||
//
|
||||
int cacheVideoQa = setting.get(SettingBoxKey.defaultVideoQa,
|
||||
defaultValue: currentHighVideoQa);
|
||||
int resVideoQa = currentHighVideoQa;
|
||||
if (cacheVideoQa <= currentHighVideoQa) {
|
||||
List<int> numbers = data.acceptQuality!
|
||||
.where((e) => e <= currentHighVideoQa)
|
||||
.toList();
|
||||
resVideoQa = Utils.findClosestNumber(cacheVideoQa, numbers);
|
||||
}
|
||||
currentVideoQa = VideoQualityCode.fromCode(resVideoQa)!;
|
||||
|
||||
/// 取出符合当前画质的videoList
|
||||
List<VideoItem> videosList =
|
||||
allVideosList.where((e) => e.quality!.code == resVideoQa).toList();
|
||||
|
||||
/// 优先顺序 设置中指定解码格式 -> 当前可选的首个解码格式
|
||||
List<FormatItem> supportFormats = data.supportFormats!;
|
||||
// 根据画质选编码格式
|
||||
List supportDecodeFormats =
|
||||
supportFormats.firstWhere((e) => e.quality == resVideoQa).codecs!;
|
||||
|
||||
try {
|
||||
currentDecodeFormats = VideoDecodeFormatsCode.fromString(setting.get(
|
||||
SettingBoxKey.defaultDecode,
|
||||
defaultValue: supportDecodeFormats.first))!;
|
||||
} catch (_) {}
|
||||
|
||||
/// 取出符合当前解码格式的videoItem
|
||||
firstVideo = videosList
|
||||
.firstWhere((e) => e.codecs!.startsWith(currentDecodeFormats.code));
|
||||
videoUrl = firstVideo.baseUrl!;
|
||||
} catch (_) {}
|
||||
|
||||
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
|
||||
AudioItem firstAudio =
|
||||
data.dash!.audio!.isNotEmpty ? data.dash!.audio!.first : AudioItem();
|
||||
late AudioItem firstAudio;
|
||||
List audiosList = data.dash!.audio!;
|
||||
try {
|
||||
if (audiosList.isNotEmpty) {
|
||||
firstAudio = audiosList.first;
|
||||
int resultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
||||
defaultValue: firstAudio.id);
|
||||
// 选择最接近的那个音轨
|
||||
firstAudio = audiosList.firstWhere(
|
||||
(e) => e.id == resultAudioQa,
|
||||
orElse: () => AudioItem(),
|
||||
);
|
||||
} else {
|
||||
firstAudio = AudioItem();
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
audioUrl = firstAudio.baseUrl ?? '';
|
||||
//
|
||||
if (firstAudio.id != null) {
|
||||
@ -185,6 +254,13 @@ class VideoDetailController extends GetxController
|
||||
}
|
||||
defaultST = Duration(milliseconds: data.lastPlayTime!);
|
||||
await playerInit();
|
||||
|
||||
// await playerInit(
|
||||
// firstVideo,
|
||||
// audioUrl,
|
||||
// defaultST: Duration(milliseconds: data.lastPlayTime!),
|
||||
// duration: data.timeLength ?? 0,
|
||||
// );
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg'].toString());
|
||||
}
|
||||
|
@ -51,6 +51,8 @@ class VideoIntroController extends GetxController {
|
||||
RxMap followStatus = {}.obs;
|
||||
int _tempThemeValue = -1;
|
||||
|
||||
RxInt lastPlayCid = 0.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
@ -76,6 +78,7 @@ class VideoIntroController extends GetxController {
|
||||
}
|
||||
}
|
||||
userLogin = user.get(UserBoxKey.userLogin) != null;
|
||||
lastPlayCid.value = int.parse(Get.parameters['cid']!);
|
||||
}
|
||||
|
||||
// 获取视频简介&分p
|
||||
@ -83,6 +86,9 @@ class VideoIntroController extends GetxController {
|
||||
var result = await VideoHttp.videoIntro(bvid: bvid);
|
||||
if (result['status']) {
|
||||
videoDetail.value = result['data']!;
|
||||
if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) {
|
||||
lastPlayCid.value = videoDetail.value.pages!.first.cid!;
|
||||
}
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
||||
.tabs
|
||||
.value = ['简介', '评论 ${result['data']!.stat!.reply}'];
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
@ -20,6 +19,7 @@ import 'widgets/action_item.dart';
|
||||
import 'widgets/action_row_item.dart';
|
||||
import 'widgets/fav_panel.dart';
|
||||
import 'widgets/intro_detail.dart';
|
||||
import 'widgets/page.dart';
|
||||
import 'widgets/season.dart';
|
||||
|
||||
class VideoIntroPanel extends StatefulWidget {
|
||||
@ -62,7 +62,6 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data['status']) {
|
||||
// 请求成功
|
||||
// return _buildView(context, false, videoDetail);
|
||||
return Obx(
|
||||
() => VideoInfo(
|
||||
loadingStatus: false,
|
||||
@ -95,22 +94,35 @@ class VideoInfo extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
Map videoItem = Get.put(VideoIntroController()).videoItem!;
|
||||
final VideoIntroController videoIntroController =
|
||||
Get.put(VideoIntroController(), tag: Get.arguments['heroTag']);
|
||||
bool isExpand = false;
|
||||
final String heroTag = Get.arguments['heroTag'];
|
||||
late final VideoIntroController videoIntroController;
|
||||
late final VideoDetailController videoDetailCtr;
|
||||
late final Map<dynamic, dynamic> videoItem;
|
||||
|
||||
late VideoDetailController? videoDetailCtr;
|
||||
Box localCache = GStrorage.localCache;
|
||||
late double sheetHeight;
|
||||
|
||||
late final bool loadingStatus; // 加载状态
|
||||
|
||||
late final dynamic owner;
|
||||
late final dynamic follower;
|
||||
late final dynamic followStatus;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
videoDetailCtr =
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||
videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
|
||||
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||
videoItem = videoIntroController.videoItem!;
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
|
||||
loadingStatus = widget.loadingStatus;
|
||||
owner = loadingStatus ? videoItem['owner'] : widget.videoDetail!.owner;
|
||||
follower = loadingStatus
|
||||
? '-'
|
||||
: Utils.numFormat(videoIntroController.userStat['follower']);
|
||||
followStatus = videoIntroController.followStatus;
|
||||
}
|
||||
|
||||
// 收藏
|
||||
@ -141,24 +153,39 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
|
||||
// 用户主页
|
||||
onPushMember() {
|
||||
feedBack();
|
||||
int mid = !loadingStatus
|
||||
? widget.videoDetail!.owner!.mid
|
||||
: videoItem['owner'].mid;
|
||||
String face = !loadingStatus
|
||||
? widget.videoDetail!.owner!.face
|
||||
: videoItem['owner'].face;
|
||||
Get.toNamed('/member?mid=$mid',
|
||||
arguments: {'face': face, 'heroTag': (mid + 99).toString()});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ThemeData t = Theme.of(context);
|
||||
Color outline = t.colorScheme.outline;
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 10),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: !widget.loadingStatus || videoItem.isNotEmpty
|
||||
child: !loadingStatus || videoItem.isNotEmpty
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
InkWell(
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => showIntroDetail(),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
!widget.loadingStatus
|
||||
!loadingStatus
|
||||
? widget.videoDetail!.title
|
||||
: videoItem['title'],
|
||||
style: const TextStyle(
|
||||
@ -182,14 +209,18 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
return t.highlightColor.withOpacity(0.2);
|
||||
}),
|
||||
),
|
||||
onPressed: () => showIntroDetail(),
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
onPressed: showIntroDetail,
|
||||
icon: Icon(
|
||||
Icons.more_horiz,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => showIntroDetail(),
|
||||
child: Row(
|
||||
children: [
|
||||
@ -237,7 +268,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
// 点赞收藏转发 布局样式2
|
||||
// actionGrid(context, videoIntroController),
|
||||
// 合集
|
||||
if (!widget.loadingStatus &&
|
||||
if (!loadingStatus &&
|
||||
widget.videoDetail!.ugcSeason != null) ...[
|
||||
SeasonPanel(
|
||||
ugcSeason: widget.videoDetail!.ugcSeason!,
|
||||
@ -247,97 +278,86 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
.changeSeasonOrbangu(bvid, cid, aid),
|
||||
)
|
||||
],
|
||||
if (!loadingStatus &&
|
||||
widget.videoDetail!.pages != null &&
|
||||
widget.videoDetail!.pages!.length > 1) ...[
|
||||
Obx(() => PagesPanel(
|
||||
pages: widget.videoDetail!.pages!,
|
||||
cid: videoIntroController.lastPlayCid.value,
|
||||
sheetHeight: sheetHeight,
|
||||
changeFuc: (cid) =>
|
||||
videoIntroController.changeSeasonOrbangu(
|
||||
videoIntroController.bvid, cid, null),
|
||||
))
|
||||
],
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
feedBack();
|
||||
int mid = !widget.loadingStatus
|
||||
? widget.videoDetail!.owner!.mid
|
||||
: videoItem['owner'].mid;
|
||||
String face = !widget.loadingStatus
|
||||
? widget.videoDetail!.owner!.face
|
||||
: videoItem['owner'].face;
|
||||
Get.toNamed('/member?mid=$mid', arguments: {
|
||||
'face': face,
|
||||
'heroTag': (mid + 99).toString()
|
||||
});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 12, bottom: 12, left: 4, right: 4),
|
||||
onTap: onPushMember,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12, horizontal: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
src: !widget.loadingStatus
|
||||
? widget.videoDetail!.owner!.face
|
||||
: videoItem['owner'].face,
|
||||
src: loadingStatus
|
||||
? owner.face
|
||||
: widget.videoDetail!.owner!.face,
|
||||
width: 34,
|
||||
height: 34,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? widget.videoDetail!.owner!.name
|
||||
: videoItem['owner'].name,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
Text(owner.name,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
widget.loadingStatus
|
||||
? '-'
|
||||
: Utils.numFormat(
|
||||
videoIntroController.userStat['follower']),
|
||||
follower,
|
||||
style: TextStyle(
|
||||
fontSize: t.textTheme.labelSmall!.fontSize,
|
||||
color: t.colorScheme.outline),
|
||||
fontSize: t.textTheme.labelSmall!.fontSize,
|
||||
color: outline,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
AnimatedOpacity(
|
||||
opacity: widget.loadingStatus ? 0 : 1,
|
||||
opacity: loadingStatus ? 0 : 1,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: SizedBox(
|
||||
height: 32,
|
||||
child: Obx(
|
||||
() => videoIntroController
|
||||
.followStatus.isNotEmpty
|
||||
? TextButton(
|
||||
onPressed: () => videoIntroController
|
||||
.actionRelationMod(),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8, right: 8),
|
||||
foregroundColor:
|
||||
videoIntroController.followStatus[
|
||||
'attribute'] !=
|
||||
0
|
||||
? t.colorScheme.outline
|
||||
: t.colorScheme.onPrimary,
|
||||
backgroundColor: videoIntroController
|
||||
.followStatus[
|
||||
'attribute'] !=
|
||||
0
|
||||
? t.colorScheme.onInverseSurface
|
||||
: t.colorScheme
|
||||
.primary, // 设置按钮背景色
|
||||
),
|
||||
child: Text(
|
||||
videoIntroController.followStatus[
|
||||
'attribute'] !=
|
||||
0
|
||||
? '已关注'
|
||||
: '关注',
|
||||
style: TextStyle(
|
||||
fontSize: t.textTheme.labelMedium!
|
||||
.fontSize),
|
||||
),
|
||||
)
|
||||
: ElevatedButton(
|
||||
onPressed: () => videoIntroController
|
||||
.actionRelationMod(),
|
||||
child: const Text('关注'),
|
||||
),
|
||||
() =>
|
||||
videoIntroController.followStatus.isNotEmpty
|
||||
? TextButton(
|
||||
onPressed: videoIntroController
|
||||
.actionRelationMod,
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8, right: 8),
|
||||
foregroundColor:
|
||||
followStatus['attribute'] != 0
|
||||
? outline
|
||||
: t.colorScheme.onPrimary,
|
||||
backgroundColor:
|
||||
followStatus['attribute'] != 0
|
||||
? t.colorScheme
|
||||
.onInverseSurface
|
||||
: t.colorScheme
|
||||
.primary, // 设置按钮背景色
|
||||
),
|
||||
child: Text(
|
||||
followStatus['attribute'] != 0
|
||||
? '已关注'
|
||||
: '关注',
|
||||
style: TextStyle(
|
||||
fontSize: t.textTheme
|
||||
.labelMedium!.fontSize),
|
||||
),
|
||||
)
|
||||
: ElevatedButton(
|
||||
onPressed: videoIntroController
|
||||
.actionRelationMod,
|
||||
child: const Text('关注'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -359,66 +379,64 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
|
||||
Widget actionGrid(BuildContext context, videoIntroController) {
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
return Padding(
|
||||
return Container(
|
||||
padding: const EdgeInsets.only(top: 6, bottom: 10),
|
||||
child: SizedBox(
|
||||
height: constraints.maxWidth / 5 * 0.8,
|
||||
child: GridView.count(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.all(0),
|
||||
crossAxisCount: 5,
|
||||
childAspectRatio: 1.25,
|
||||
children: <Widget>[
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap: () => videoIntroController.actionLikeVideo(),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.like!.toString()
|
||||
: '-'),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.clock),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: '稍后再看'),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: () => videoIntroController.actionCoinVideo(),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.coin!.toString()
|
||||
: '-'),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
// onTap: () => videoIntroController.actionFavVideo(),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.favorite!.toString()
|
||||
: '-'),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.share!.toString()
|
||||
height: constraints.maxWidth / 5 * 0.8,
|
||||
child: GridView.count(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.all(0),
|
||||
crossAxisCount: 5,
|
||||
childAspectRatio: 1.25,
|
||||
children: <Widget>[
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap: () => videoIntroController.actionLikeVideo(),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.like!.toString()
|
||||
: '-'),
|
||||
],
|
||||
),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.clock),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: loadingStatus,
|
||||
text: '稍后再看'),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: () => videoIntroController.actionCoinVideo(),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.coin!.toString()
|
||||
: '-'),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
// onTap: () => videoIntroController.actionFavVideo(),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.favorite!.toString()
|
||||
: '-'),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.share!.toString()
|
||||
: '-'),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
@ -431,10 +449,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
onTap: () => videoIntroController.actionLikeVideo(),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.like!.toString()
|
||||
: '-',
|
||||
loadingStatus: loadingStatus,
|
||||
text:
|
||||
!loadingStatus ? widget.videoDetail!.stat!.like!.toString() : '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@ -443,10 +460,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: () => videoIntroController.actionCoinVideo(),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.coin!.toString()
|
||||
: '-',
|
||||
loadingStatus: loadingStatus,
|
||||
text:
|
||||
!loadingStatus ? widget.videoDetail!.stat!.coin!.toString() : '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@ -455,8 +471,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
icon: const Icon(FontAwesomeIcons.heart),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.favorite!.toString()
|
||||
: '-',
|
||||
),
|
||||
@ -468,57 +484,20 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
videoDetailCtr.tabCtr.animateTo(1);
|
||||
},
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.reply!.toString()
|
||||
: '-',
|
||||
loadingStatus: loadingStatus,
|
||||
text:
|
||||
!loadingStatus ? widget.videoDetail!.stat!.reply!.toString() : '-',
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.share),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
// text: !widget.loadingStatus
|
||||
loadingStatus: loadingStatus,
|
||||
// text: !loadingStatus
|
||||
// ? widget.videoDetail!.stat!.share!.toString()
|
||||
// : '-',
|
||||
text: '转发'),
|
||||
]);
|
||||
}
|
||||
|
||||
InlineSpan buildContent(BuildContext context, content) {
|
||||
String desc = content.desc;
|
||||
List descV2 = content.descV2;
|
||||
// type
|
||||
// 1 普通文本
|
||||
// 2 @用户
|
||||
List<InlineSpan> spanChilds = [];
|
||||
if (descV2.isNotEmpty) {
|
||||
for (var i = 0; i < descV2.length; i++) {
|
||||
if (descV2[i].type == 1) {
|
||||
spanChilds.add(TextSpan(text: descV2[i].rawText));
|
||||
} else if (descV2[i].type == 2) {
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: '@${descV2[i].rawText}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
String heroTag = Utils.makeHeroTag(descV2[i].bizId);
|
||||
Get.toNamed(
|
||||
'/member?mid=${descV2[i].bizId}',
|
||||
arguments: {'face': '', 'heroTag': heroTag},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
spanChilds.add(TextSpan(text: desc));
|
||||
}
|
||||
return TextSpan(children: spanChilds);
|
||||
}
|
||||
}
|
||||
|
@ -33,24 +33,13 @@ class _FavPanelState extends State<FavPanel> {
|
||||
child: Column(
|
||||
children: [
|
||||
AppBar(
|
||||
toolbarHeight: 50,
|
||||
automaticallyImplyLeading: false,
|
||||
centerTitle: false,
|
||||
elevation: 1,
|
||||
title: Text(
|
||||
'选择文件夹',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
feedBack();
|
||||
await widget.ctr!.actionFavVideo();
|
||||
},
|
||||
child: const Text('完成'),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
],
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
onPressed: () => Get.back(),
|
||||
icon: const Icon(Icons.close_outlined)),
|
||||
title:
|
||||
Text('添加到收藏夹', style: Theme.of(context).textTheme.titleMedium),
|
||||
),
|
||||
Expanded(
|
||||
child: Material(
|
||||
@ -63,45 +52,33 @@ class _FavPanelState extends State<FavPanel> {
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
itemCount:
|
||||
widget.ctr!.favFolderData.value.list!.length + 1,
|
||||
widget.ctr!.favFolderData.value.list!.length,
|
||||
itemBuilder: (context, index) {
|
||||
if (index == 0) {
|
||||
return const SizedBox(height: 10);
|
||||
} else {
|
||||
return ListTile(
|
||||
onTap: () => widget.ctr!.onChoose(
|
||||
widget.ctr!.favFolderData.value
|
||||
.list![index - 1].favState !=
|
||||
1,
|
||||
index - 1),
|
||||
dense: true,
|
||||
leading:
|
||||
const Icon(Icons.folder_special_outlined),
|
||||
minLeadingWidth: 0,
|
||||
title: Text(widget.ctr!.favFolderData.value
|
||||
.list![index - 1].title!),
|
||||
subtitle: Text(
|
||||
'${widget.ctr!.favFolderData.value.list![index - 1].mediaCount}个内容',
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.fontSize),
|
||||
return ListTile(
|
||||
onTap: () => widget.ctr!.onChoose(
|
||||
widget.ctr!.favFolderData.value.list![index]
|
||||
.favState !=
|
||||
1,
|
||||
index),
|
||||
dense: true,
|
||||
leading: const Icon(Icons.folder_outlined),
|
||||
minLeadingWidth: 0,
|
||||
title: Text(widget.ctr!.favFolderData.value
|
||||
.list![index].title!),
|
||||
subtitle: Text(
|
||||
'${widget.ctr!.favFolderData.value.list![index].mediaCount}个内容',
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: Checkbox(
|
||||
value: widget.ctr!.favFolderData.value
|
||||
.list![index].favState ==
|
||||
1,
|
||||
onChanged: (bool? checkValue) =>
|
||||
widget.ctr!.onChoose(checkValue!, index),
|
||||
),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: Checkbox(
|
||||
value: widget.ctr!.favFolderData.value
|
||||
.list![index - 1].favState ==
|
||||
1,
|
||||
onChanged: (bool? checkValue) => widget.ctr!
|
||||
.onChoose(checkValue!, index - 1),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
@ -119,6 +96,46 @@ class _FavPanelState extends State<FavPanel> {
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).disabledColor.withOpacity(0.08),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 20,
|
||||
right: 20,
|
||||
top: 12,
|
||||
bottom: MediaQuery.of(context).padding.bottom),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => Get.back(),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 30, right: 30),
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface, // 设置按钮背景色
|
||||
),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
feedBack();
|
||||
await widget.ctr!.actionFavVideo();
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 30, right: 30),
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.primary, // 设置按钮背景色
|
||||
),
|
||||
child: const Text('完成'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -27,19 +27,20 @@ class IntroDetail extends StatelessWidget {
|
||||
height: sheetHeight,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
.withOpacity(0.5),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(3))),
|
||||
InkWell(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(3))),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -125,33 +126,29 @@ class IntroDetail extends StatelessWidget {
|
||||
// type
|
||||
// 1 普通文本
|
||||
// 2 @用户
|
||||
List<InlineSpan> spanChilds = [];
|
||||
if (descV2.isNotEmpty) {
|
||||
for (var i = 0; i < descV2.length; i++) {
|
||||
if (descV2[i].type == 1) {
|
||||
spanChilds.add(TextSpan(text: descV2[i].rawText));
|
||||
} else if (descV2[i].type == 2) {
|
||||
spanChilds.add(
|
||||
TextSpan(
|
||||
text: '@${descV2[i].rawText}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
String heroTag = Utils.makeHeroTag(descV2[i].bizId);
|
||||
Get.toNamed(
|
||||
'/member?mid=${descV2[i].bizId}',
|
||||
arguments: {'face': '', 'heroTag': heroTag},
|
||||
);
|
||||
},
|
||||
),
|
||||
List<TextSpan> spanChilds = List.generate(descV2.length, (index) {
|
||||
final currentDesc = descV2[index];
|
||||
switch (currentDesc.type) {
|
||||
case 1:
|
||||
return TextSpan(text: currentDesc.rawText);
|
||||
case 2:
|
||||
final colorSchemePrimary = Theme.of(context).colorScheme.primary;
|
||||
final heroTag = Utils.makeHeroTag(currentDesc.bizId);
|
||||
return TextSpan(
|
||||
text: '@${currentDesc.rawText}',
|
||||
style: TextStyle(color: colorSchemePrimary),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
Get.toNamed(
|
||||
'/member?mid=${currentDesc.bizId}',
|
||||
arguments: {'face': '', 'heroTag': heroTag},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
default:
|
||||
return TextSpan();
|
||||
}
|
||||
} else {
|
||||
spanChilds.add(TextSpan(text: desc));
|
||||
}
|
||||
});
|
||||
return TextSpan(children: spanChilds);
|
||||
}
|
||||
}
|
||||
|
203
lib/pages/video/detail/introduction/widgets/page.dart
Normal file
203
lib/pages/video/detail/introduction/widgets/page.dart
Normal file
@ -0,0 +1,203 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/models/video_detail_res.dart';
|
||||
|
||||
class PagesPanel extends StatefulWidget {
|
||||
final List<Part> pages;
|
||||
final int? cid;
|
||||
final double? sheetHeight;
|
||||
final Function? changeFuc;
|
||||
|
||||
const PagesPanel({
|
||||
super.key,
|
||||
required this.pages,
|
||||
this.cid,
|
||||
this.sheetHeight,
|
||||
this.changeFuc,
|
||||
});
|
||||
|
||||
@override
|
||||
State<PagesPanel> createState() => _PagesPanelState();
|
||||
}
|
||||
|
||||
class _PagesPanelState extends State<PagesPanel> {
|
||||
late List<Part> episodes;
|
||||
late int currentIndex;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
episodes = widget.pages;
|
||||
currentIndex = episodes.indexWhere((e) => e.cid == widget.cid);
|
||||
}
|
||||
|
||||
void changeFucCall(item, i) async {
|
||||
await widget.changeFuc!(
|
||||
item.cid,
|
||||
);
|
||||
currentIndex = i;
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10, bottom: 2),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('视频选集 '),
|
||||
Expanded(
|
||||
child: Text(
|
||||
' 正在播放:${widget.pages[currentIndex].pagePart}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
SizedBox(
|
||||
height: 34,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {
|
||||
showBottomSheet(
|
||||
context: context,
|
||||
builder: (_) => Container(
|
||||
height: widget.sheetHeight,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding:
|
||||
const EdgeInsets.only(left: 14, right: 14),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'合集(${episodes.length})',
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context)
|
||||
.dividerColor
|
||||
.withOpacity(0.1),
|
||||
),
|
||||
Expanded(
|
||||
child: Material(
|
||||
child: ListView.builder(
|
||||
itemCount: episodes.length,
|
||||
itemBuilder: (context, index) {
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
changeFucCall(episodes[index], index);
|
||||
Get.back();
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
left: 15,
|
||||
right: 15),
|
||||
child: Text(
|
||||
episodes[index].pagePart!,
|
||||
style: TextStyle(
|
||||
color: index == currentIndex
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurface),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
'共${widget.pages.length}集',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 35,
|
||||
margin: const EdgeInsets.only(bottom: 8),
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: widget.pages.length,
|
||||
itemExtent: 150,
|
||||
itemBuilder: ((context, i) {
|
||||
return Container(
|
||||
width: 150,
|
||||
margin: const EdgeInsets.only(right: 10),
|
||||
child: Material(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
onTap: () => changeFucCall(widget.pages[i], i),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
if (i == currentIndex) ...[
|
||||
Image.asset(
|
||||
'assets/images/live.gif',
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
height: 12,
|
||||
),
|
||||
const SizedBox(width: 6)
|
||||
],
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.pages[i].pagePart!,
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: i == currentIndex
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.onSurface),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -391,6 +391,7 @@ class ReplyItemRow extends StatelessWidget {
|
||||
),
|
||||
if (replies![i].isUp)
|
||||
const WidgetSpan(
|
||||
alignment: PlaceholderAlignment.top,
|
||||
child: UpTag(),
|
||||
),
|
||||
buildContent(
|
||||
|
@ -118,7 +118,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 400,
|
||||
height: 500,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(
|
||||
|
@ -1,11 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/common/widgets/sliver_header.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/pages/bangumi/introduction/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
|
||||
@ -163,8 +165,11 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
scrolledUnderElevation: 0,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
expandedHeight: videoHeight,
|
||||
// backgroundColor: Colors.transparent,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
backgroundColor:
|
||||
MediaQuery.of(Get.context!).platformBrightness ==
|
||||
Brightness.dark
|
||||
? Colors.black
|
||||
: Theme.of(context).colorScheme.background,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Padding(
|
||||
padding: EdgeInsets.only(top: statusBarHeight),
|
||||
@ -233,10 +238,17 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
backgroundColor:
|
||||
Colors.transparent,
|
||||
actions: [
|
||||
/// TODO
|
||||
IconButton(
|
||||
tooltip: '稍后再看',
|
||||
onPressed: () {},
|
||||
onPressed: () async {
|
||||
var res = await UserHttp
|
||||
.toViewLater(
|
||||
bvid:
|
||||
videoDetailController
|
||||
.bvid);
|
||||
SmartDialog.showToast(
|
||||
res['msg']);
|
||||
},
|
||||
icon: const Icon(Icons
|
||||
.history_outlined))
|
||||
],
|
||||
@ -291,39 +303,20 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: 0,
|
||||
child: Container(
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
height: 0,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context)
|
||||
.dividerColor
|
||||
.withOpacity(0.1),
|
||||
),
|
||||
child: Obx(
|
||||
() => TabBar(
|
||||
controller: videoDetailController.tabCtr,
|
||||
dividerColor: Colors.transparent,
|
||||
indicatorColor:
|
||||
Theme.of(context).colorScheme.background,
|
||||
tabs: videoDetailController.tabs
|
||||
.map((String name) => Tab(text: name))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Container(
|
||||
width: 280,
|
||||
margin: const EdgeInsets.only(left: 20),
|
||||
child: Obx(
|
||||
() => TabBar(
|
||||
controller: videoDetailController.tabCtr,
|
||||
dividerColor: Colors.transparent,
|
||||
indicatorColor:
|
||||
Theme.of(context).colorScheme.background,
|
||||
tabs: videoDetailController.tabs
|
||||
.map((String name) => Tab(text: name))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
|
@ -28,6 +28,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
late PlayUrlModel videoInfo;
|
||||
List<PlaySpeed> playSpeed = PlaySpeed.values;
|
||||
TextStyle subTitleStyle = const TextStyle(fontSize: 12);
|
||||
TextStyle titleStyle = const TextStyle(fontSize: 14);
|
||||
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||
|
||||
@override
|
||||
@ -81,7 +82,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
enabled: false,
|
||||
leading:
|
||||
const Icon(Icons.network_cell_outlined, size: 20),
|
||||
title: const Text('省流模式'),
|
||||
title: Text('省流模式', style: titleStyle),
|
||||
subtitle: Text('低画质 | 减少视频缓存', style: subTitleStyle),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.75,
|
||||
@ -99,22 +100,22 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => ListTile(
|
||||
onTap: () => {Get.back(), showSetSpeedSheet()},
|
||||
dense: true,
|
||||
leading: const Icon(Icons.speed_outlined, size: 20),
|
||||
title: const Text('播放速度'),
|
||||
subtitle: Text(
|
||||
'当前倍速 x${widget.controller!.playbackSpeed}',
|
||||
style: subTitleStyle),
|
||||
),
|
||||
),
|
||||
// Obx(
|
||||
// () => ListTile(
|
||||
// onTap: () => {Get.back(), showSetSpeedSheet()},
|
||||
// dense: true,
|
||||
// leading: const Icon(Icons.speed_outlined, size: 20),
|
||||
// title: Text('播放速度', style: titleStyle),
|
||||
// subtitle: Text(
|
||||
// '当前倍速 x${widget.controller!.playbackSpeed}',
|
||||
// style: subTitleStyle),
|
||||
// ),
|
||||
// ),
|
||||
ListTile(
|
||||
onTap: () => {Get.back(), showSetVideoQa()},
|
||||
dense: true,
|
||||
leading: const Icon(Icons.play_circle_outline, size: 20),
|
||||
title: const Text('选择画质'),
|
||||
title: Text('选择画质', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前画质 ${widget.videoDetailCtr!.currentVideoQa.description}',
|
||||
style: subTitleStyle),
|
||||
@ -123,24 +124,33 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
onTap: () => {Get.back(), showSetAudioQa()},
|
||||
dense: true,
|
||||
leading: const Icon(Icons.album_outlined, size: 20),
|
||||
title: const Text('选择音质'),
|
||||
title: Text('选择音质', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前音质 ${widget.videoDetailCtr!.currentAudioQa.description}',
|
||||
style: subTitleStyle),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () {},
|
||||
onTap: () => {Get.back(), showSetDecodeFormats()},
|
||||
dense: true,
|
||||
enabled: false,
|
||||
leading: const Icon(Icons.play_circle_outline, size: 20),
|
||||
title: const Text('播放设置'),
|
||||
leading: const Icon(Icons.av_timer_outlined, size: 20),
|
||||
title: Text('解码格式', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前解码格式 ${widget.videoDetailCtr!.currentDecodeFormats.description}',
|
||||
style: subTitleStyle),
|
||||
),
|
||||
// ListTile(
|
||||
// onTap: () {},
|
||||
// dense: true,
|
||||
// enabled: false,
|
||||
// leading: const Icon(Icons.play_circle_outline, size: 20),
|
||||
// title: Text('播放设置', style: titleStyle),
|
||||
// ),
|
||||
ListTile(
|
||||
onTap: () {},
|
||||
dense: true,
|
||||
enabled: false,
|
||||
leading: const Icon(Icons.subtitles_outlined, size: 20),
|
||||
title: const Text('弹幕设置'),
|
||||
title: Text('弹幕设置', style: titleStyle),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -250,7 +260,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('选择画质'),
|
||||
Text('选择画质', style: titleStyle),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.info_outline,
|
||||
@ -329,7 +339,9 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
margin: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 45, child: Center(child: Text('选择音质'))),
|
||||
SizedBox(
|
||||
height: 45,
|
||||
child: Center(child: Text('选择音质', style: titleStyle))),
|
||||
Expanded(
|
||||
child: Material(
|
||||
child: ListView(
|
||||
@ -370,6 +382,74 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
);
|
||||
}
|
||||
|
||||
// 选择解码格式
|
||||
void showSetDecodeFormats() {
|
||||
// 当前选中的解码格式
|
||||
VideoDecodeFormats currentDecodeFormats =
|
||||
widget.videoDetailCtr!.currentDecodeFormats;
|
||||
// 当前视频可用的解码格式
|
||||
List<FormatItem> videoFormat = videoInfo.supportFormats!;
|
||||
List list = videoFormat.first.codecs!;
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
builder: (BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
height: 250,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
margin: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 45,
|
||||
child: Center(child: Text('选择解码格式', style: titleStyle))),
|
||||
Expanded(
|
||||
child: Material(
|
||||
child: ListView(
|
||||
children: [
|
||||
for (var i in list) ...[
|
||||
ListTile(
|
||||
onTap: () {
|
||||
widget.videoDetailCtr!.currentDecodeFormats =
|
||||
VideoDecodeFormatsCode.fromString(i)!;
|
||||
widget.videoDetailCtr!.updatePlayer();
|
||||
Get.back();
|
||||
},
|
||||
dense: true,
|
||||
contentPadding:
|
||||
const EdgeInsets.only(left: 20, right: 20),
|
||||
title: Text(VideoDecodeFormatsCode.fromString(i)!
|
||||
.description!),
|
||||
subtitle: Text(
|
||||
i!,
|
||||
style: subTitleStyle,
|
||||
),
|
||||
trailing: i.startsWith(currentDecodeFormats.code)
|
||||
? Icon(
|
||||
Icons.done,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _ = widget.controller!;
|
||||
|
Reference in New Issue
Block a user