Compare commits
23 Commits
fix-favori
...
feature-li
| Author | SHA1 | Date | |
|---|---|---|---|
| 33d28f51d1 | |||
| a37f3b8b5b | |||
| a57f5e8b2f | |||
| beb640ac83 | |||
| 1b54f07bc3 | |||
| fca0588377 | |||
| 4865948609 | |||
| 09eb180fc7 | |||
| 3fab47780b | |||
| 453cbd7b1c | |||
| 0bf2326c73 | |||
| 2985c624ab | |||
| 32cbc2759e | |||
| 13c77957fe | |||
| f382c8f377 | |||
| 491bc87251 | |||
| 5e8d9b524b | |||
| 357133fa97 | |||
| da2bbeedff | |||
| bc9ea43cd2 | |||
| 882957e2f8 | |||
| 98aaca286b | |||
| 6fdfcb888d |
@ -31,14 +31,14 @@ class MsgHttp {
|
||||
} catch (err) {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'data': [],
|
||||
'msg': err.toString(),
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
|
||||
43
lib/models/live/quality.dart
Normal file
43
lib/models/live/quality.dart
Normal file
@ -0,0 +1,43 @@
|
||||
enum LiveQuality {
|
||||
dolby,
|
||||
super4K,
|
||||
origin,
|
||||
bluRay,
|
||||
superHD,
|
||||
smooth,
|
||||
flunt,
|
||||
}
|
||||
|
||||
extension LiveQualityCode on LiveQuality {
|
||||
static final List<int> _codeList = [
|
||||
30000,
|
||||
20000,
|
||||
10000,
|
||||
400,
|
||||
250,
|
||||
150,
|
||||
80,
|
||||
];
|
||||
int get code => _codeList[index];
|
||||
|
||||
static LiveQuality? fromCode(int code) {
|
||||
final index = _codeList.indexOf(code);
|
||||
if (index != -1) {
|
||||
return LiveQuality.values[index];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
extension VideoQualityDesc on LiveQuality {
|
||||
static final List<String> _descList = [
|
||||
'杜比',
|
||||
'4K',
|
||||
'原画',
|
||||
'蓝光',
|
||||
'超清',
|
||||
'高清',
|
||||
'流畅',
|
||||
];
|
||||
get description => _descList[index];
|
||||
}
|
||||
@ -25,13 +25,6 @@ class BangumiIntroController extends GetxController {
|
||||
? int.tryParse(Get.parameters['epId']!)
|
||||
: null;
|
||||
|
||||
// 是否预渲染 骨架屏
|
||||
bool preRender = false;
|
||||
|
||||
// 视频详情 上个页面传入
|
||||
Map? videoItem = {};
|
||||
BangumiInfoModel? bangumiItem;
|
||||
|
||||
// 请求状态
|
||||
RxBool isLoading = false.obs;
|
||||
|
||||
@ -63,27 +56,6 @@ class BangumiIntroController extends GetxController {
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (Get.arguments.isNotEmpty as bool) {
|
||||
if (Get.arguments.containsKey('bangumiItem') as bool) {
|
||||
preRender = true;
|
||||
bangumiItem = Get.arguments['bangumiItem'];
|
||||
// bangumiItem!['pic'] = args.pic;
|
||||
// if (args.title is String) {
|
||||
// videoItem!['title'] = args.title;
|
||||
// } else {
|
||||
// String str = '';
|
||||
// for (Map map in args.title) {
|
||||
// str += map['text'];
|
||||
// }
|
||||
// videoItem!['title'] = str;
|
||||
// }
|
||||
// if (args.stat != null) {
|
||||
// videoItem!['stat'] = args.stat;
|
||||
// }
|
||||
// videoItem!['pubdate'] = args.pubdate;
|
||||
// videoItem!['owner'] = args.owner;
|
||||
}
|
||||
}
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
userLogin = userInfo != null;
|
||||
}
|
||||
@ -183,20 +155,21 @@ class BangumiIntroController extends GetxController {
|
||||
actions: [
|
||||
TextButton(onPressed: () => Get.back(), child: const Text('取消')),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
var res = await VideoHttp.coinVideo(
|
||||
bvid: bvid, multiply: _tempThemeValue);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('投币成功 👏');
|
||||
hasCoin.value = true;
|
||||
bangumiDetail.value.stat!['coins'] =
|
||||
bangumiDetail.value.stat!['coins'] + _tempThemeValue;
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确定'))
|
||||
onPressed: () async {
|
||||
var res = await VideoHttp.coinVideo(
|
||||
bvid: bvid, multiply: _tempThemeValue);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('投币成功 👏');
|
||||
hasCoin.value = true;
|
||||
bangumiDetail.value.stat!['coins'] =
|
||||
bangumiDetail.value.stat!['coins'] + _tempThemeValue;
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确定'),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
@ -12,11 +12,10 @@ import 'package:pilipala/models/bangumi/info.dart';
|
||||
import 'package:pilipala/pages/bangumi/widgets/bangumi_panel.dart';
|
||||
import 'package:pilipala/pages/video/detail/index.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/widgets/action_item.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/widgets/action_row_item.dart';
|
||||
import 'package:pilipala/pages/video/detail/introduction/widgets/fav_panel.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
import '../../../common/widgets/http_error.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/intro_detail.dart';
|
||||
|
||||
@ -51,9 +50,6 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
cid = widget.cid!;
|
||||
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
|
||||
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||
bangumiIntroController.bangumiDetail.listen((BangumiInfoModel value) {
|
||||
bangumiDetail = value;
|
||||
});
|
||||
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
|
||||
videoDetailCtr.cid.listen((int p0) {
|
||||
cid = p0;
|
||||
@ -68,27 +64,32 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
future: _futureBuilderFuture,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SliverToBoxAdapter(child: SizedBox());
|
||||
}
|
||||
if (snapshot.data['status']) {
|
||||
// 请求成功
|
||||
|
||||
return BangumiInfo(
|
||||
loadingStatus: false,
|
||||
bangumiDetail: bangumiDetail,
|
||||
cid: cid,
|
||||
return Obx(
|
||||
() => BangumiInfo(
|
||||
bangumiDetail: bangumiIntroController.bangumiDetail.value,
|
||||
cid: cid,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
// 请求错误
|
||||
// return HttpError(
|
||||
// errMsg: snapshot.data['msg'],
|
||||
// fn: () => Get.back(),
|
||||
// );
|
||||
return const SizedBox();
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
fn: () => Get.back(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return BangumiInfo(
|
||||
loadingStatus: true,
|
||||
bangumiDetail: bangumiDetail,
|
||||
cid: cid,
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 100,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -99,12 +100,10 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||
class BangumiInfo extends StatefulWidget {
|
||||
const BangumiInfo({
|
||||
super.key,
|
||||
this.loadingStatus = false,
|
||||
this.bangumiDetail,
|
||||
this.cid,
|
||||
});
|
||||
|
||||
final bool loadingStatus;
|
||||
final BangumiInfoModel? bangumiDetail;
|
||||
final int? cid;
|
||||
|
||||
@ -117,7 +116,6 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
late final BangumiIntroController bangumiIntroController;
|
||||
late final VideoDetailController videoDetailCtr;
|
||||
Box localCache = GStrorage.localCache;
|
||||
late final BangumiInfoModel? bangumiItem;
|
||||
late double sheetHeight;
|
||||
int? cid;
|
||||
bool isProcessing = false;
|
||||
@ -136,13 +134,10 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
super.initState();
|
||||
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
|
||||
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||
bangumiItem = bangumiIntroController.bangumiItem;
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
cid = widget.cid!;
|
||||
print('cid: $cid');
|
||||
videoDetailCtr.cid.listen((p0) {
|
||||
cid = p0;
|
||||
print('cid: $cid');
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
@ -182,207 +177,155 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 20),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: !widget.loadingStatus || bangumiItem != null
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
width: 105,
|
||||
height: 160,
|
||||
src: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.cover!
|
||||
: bangumiItem!.cover!,
|
||||
),
|
||||
if (bangumiItem != null &&
|
||||
bangumiItem!.rating != null)
|
||||
PBadge(
|
||||
text:
|
||||
'评分 ${!widget.loadingStatus ? widget.bangumiDetail!.rating!['score']! : bangumiItem!.rating!['score']!}',
|
||||
top: null,
|
||||
right: 6,
|
||||
bottom: 6,
|
||||
left: null,
|
||||
NetworkImgLayer(
|
||||
width: 105,
|
||||
height: 160,
|
||||
src: widget.bangumiDetail!.cover!,
|
||||
),
|
||||
PBadge(
|
||||
text: '评分 ${widget.bangumiDetail!.rating!['score']!}',
|
||||
top: null,
|
||||
right: 6,
|
||||
bottom: 6,
|
||||
left: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => showIntroDetail(),
|
||||
child: SizedBox(
|
||||
height: 158,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.bangumiDetail!.title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: InkWell(
|
||||
onTap: () => showIntroDetail(),
|
||||
child: SizedBox(
|
||||
height: 158,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
!widget.loadingStatus
|
||||
? widget.bangumiDetail!.title!
|
||||
: bangumiItem!.title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(
|
||||
EdgeInsets.zero),
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith(
|
||||
(Set<MaterialState> states) {
|
||||
return t
|
||||
.colorScheme.primaryContainer
|
||||
.withOpacity(0.7);
|
||||
}),
|
||||
),
|
||||
onPressed: () =>
|
||||
bangumiIntroController.bangumiAdd(),
|
||||
icon: Icon(
|
||||
Icons.favorite_border_rounded,
|
||||
color: t.colorScheme.primary,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 20),
|
||||
SizedBox(
|
||||
width: 34,
|
||||
height: 34,
|
||||
child: IconButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(
|
||||
EdgeInsets.zero),
|
||||
backgroundColor:
|
||||
MaterialStateProperty.resolveWith(
|
||||
(Set<MaterialState> states) {
|
||||
return t.colorScheme.primaryContainer
|
||||
.withOpacity(0.7);
|
||||
}),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['views']
|
||||
: bangumiItem!.stat!['views'],
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: !widget.loadingStatus
|
||||
? widget
|
||||
.bangumiDetail!.stat!['danmakus']
|
||||
: bangumiItem!.stat!['danmakus'],
|
||||
size: 'medium',
|
||||
),
|
||||
],
|
||||
onPressed: () =>
|
||||
bangumiIntroController.bangumiAdd(),
|
||||
icon: Icon(
|
||||
Icons.favorite_border_rounded,
|
||||
color: t.colorScheme.primary,
|
||||
size: 22,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? (widget.bangumiDetail!.areas!
|
||||
.isNotEmpty
|
||||
? widget.bangumiDetail!.areas!
|
||||
.first['name']
|
||||
: '')
|
||||
: (bangumiItem!.areas!.isNotEmpty
|
||||
? bangumiItem!
|
||||
.areas!.first['name']
|
||||
: ''),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? widget.bangumiDetail!
|
||||
.publish!['pub_time_show']
|
||||
: bangumiItem!
|
||||
.publish!['pub_time_show'],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// const SizedBox(height: 4),
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? widget.bangumiDetail!.newEp!['desc']
|
||||
: bangumiItem!.newEp!['desc'],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
// const SizedBox(height: 10),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'简介:${!widget.loadingStatus ? widget.bangumiDetail!.evaluate! : bangumiItem!.evaluate!}',
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: widget.bangumiDetail!.stat!['views'],
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: widget.bangumiDetail!.stat!['danmakus'],
|
||||
size: 'medium',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
(widget.bangumiDetail!.areas!.isNotEmpty
|
||||
? widget.bangumiDetail!.areas!.first['name']
|
||||
: ''),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
widget.bangumiDetail!.publish!['pub_time_show'],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
widget.bangumiDetail!.newEp!['desc'],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
Text(
|
||||
'简介:${widget.bangumiDetail!.evaluate!}',
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
// 点赞收藏转发 布局样式1
|
||||
// SingleChildScrollView(
|
||||
// padding: const EdgeInsets.only(top: 7, bottom: 7),
|
||||
// scrollDirection: Axis.horizontal,
|
||||
// child: actionRow(
|
||||
// context,
|
||||
// bangumiIntroController,
|
||||
// videoDetailCtr,
|
||||
// ),
|
||||
// ),
|
||||
// 点赞收藏转发 布局样式2
|
||||
actionGrid(context, bangumiIntroController),
|
||||
// 番剧分p
|
||||
if ((!widget.loadingStatus &&
|
||||
widget.bangumiDetail!.episodes!.isNotEmpty) ||
|
||||
bangumiItem != null &&
|
||||
bangumiItem!.episodes!.isNotEmpty) ...[
|
||||
BangumiPanel(
|
||||
pages: bangumiItem != null
|
||||
? bangumiItem!.episodes!
|
||||
: widget.bangumiDetail!.episodes!,
|
||||
cid: cid ??
|
||||
(bangumiItem != null
|
||||
? bangumiItem!.episodes!.first.cid
|
||||
: widget.bangumiDetail!.episodes!.first.cid),
|
||||
sheetHeight: sheetHeight,
|
||||
changeFuc: (bvid, cid, aid) => bangumiIntroController
|
||||
.changeSeasonOrbangu(bvid, cid, aid),
|
||||
)
|
||||
],
|
||||
],
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 100,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
|
||||
/// 点赞收藏转发
|
||||
actionGrid(context, bangumiIntroController),
|
||||
// 番剧分p
|
||||
if (widget.bangumiDetail!.episodes!.isNotEmpty) ...[
|
||||
BangumiPanel(
|
||||
pages: widget.bangumiDetail!.episodes!,
|
||||
cid: cid ?? widget.bangumiDetail!.episodes!.first.cid,
|
||||
sheetHeight: sheetHeight,
|
||||
changeFuc: (bvid, cid, aid) =>
|
||||
bangumiIntroController.changeSeasonOrbangu(bvid, cid, aid),
|
||||
bangumiDetail: bangumiIntroController.bangumiDetail.value,
|
||||
)
|
||||
],
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -402,57 +345,44 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
children: <Widget>[
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap:
|
||||
handleState(bangumiIntroController.actionLikeVideo),
|
||||
selectStatus: bangumiIntroController.hasLike.value,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['likes']!.toString()
|
||||
: bangumiItem!.stat!['likes']!.toString()),
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap: handleState(bangumiIntroController.actionLikeVideo),
|
||||
selectStatus: bangumiIntroController.hasLike.value,
|
||||
text: widget.bangumiDetail!.stat!['likes']!.toString(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap:
|
||||
handleState(bangumiIntroController.actionCoinVideo),
|
||||
selectStatus: bangumiIntroController.hasCoin.value,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['coins']!.toString()
|
||||
: bangumiItem!.stat!['coins']!.toString()),
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(bangumiIntroController.actionCoinVideo),
|
||||
selectStatus: bangumiIntroController.hasCoin.value,
|
||||
text: widget.bangumiDetail!.stat!['coins']!.toString(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: bangumiIntroController.hasFav.value,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['favorite']!.toString()
|
||||
: bangumiItem!.stat!['favorite']!.toString()),
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: bangumiIntroController.hasFav.value,
|
||||
text: widget.bangumiDetail!.stat!['favorite']!.toString(),
|
||||
),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
selectIcon: const Icon(FontAwesomeIcons.reply),
|
||||
onTap: () => videoDetailCtr.tabCtr.animateTo(1),
|
||||
selectStatus: false,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['reply']!.toString()
|
||||
: bangumiItem!.stat!['reply']!.toString(),
|
||||
text: widget.bangumiDetail!.stat!['reply']!.toString(),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => bangumiIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: false,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['share']!.toString()
|
||||
: bangumiItem!.stat!['share']!.toString()),
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => bangumiIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
text: widget.bangumiDetail!.stat!['share']!.toString(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -460,63 +390,4 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
|
||||
return Row(children: [
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
onTap: handleState(videoIntroController.actionLikeVideo),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['likes']!.toString()
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['coins']!.toString()
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.heart),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['favorite']!.toString()
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
onTap: () {
|
||||
videoDetailCtr.tabCtr.animateTo(1);
|
||||
},
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.bangumiDetail!.stat!['reply']!.toString()
|
||||
: '-',
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.share),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: '转发'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,12 +14,14 @@ class BangumiPanel extends StatefulWidget {
|
||||
this.cid,
|
||||
this.sheetHeight,
|
||||
this.changeFuc,
|
||||
this.bangumiDetail,
|
||||
});
|
||||
|
||||
final List<EpisodeItem> pages;
|
||||
final int? cid;
|
||||
final double? sheetHeight;
|
||||
final Function? changeFuc;
|
||||
final BangumiInfoModel? bangumiDetail;
|
||||
|
||||
@override
|
||||
State<BangumiPanel> createState() => _BangumiPanelState();
|
||||
@ -87,7 +89,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
)
|
||||
: null,
|
||||
title: Text(
|
||||
'第${index + 1}话 ${page.longTitle!}',
|
||||
'第${page.title}话 ${page.longTitle!}',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: isCurrentIndex
|
||||
@ -96,9 +98,11 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
),
|
||||
),
|
||||
trailing: page.badge != null
|
||||
? Image.asset(
|
||||
'assets/images/big-vip.png',
|
||||
height: 20,
|
||||
? Text(
|
||||
page.badge!,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
)
|
||||
: const SizedBox(),
|
||||
);
|
||||
@ -201,11 +205,11 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
return Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10, bottom: 6),
|
||||
padding: const EdgeInsets.only(top: 10, bottom: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('合集 '),
|
||||
const Text('选集 '),
|
||||
Expanded(
|
||||
child: Text(
|
||||
' 正在播放:${widget.pages[currentIndex].longTitle}',
|
||||
@ -225,7 +229,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
),
|
||||
onPressed: () => showBangumiPanel(),
|
||||
child: Text(
|
||||
'全${widget.pages.length}话',
|
||||
'${widget.bangumiDetail!.newEp!['desc']}',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
@ -278,23 +282,15 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
if (widget.pages[i].badge != null) ...[
|
||||
if (widget.pages[i].badge == '会员') ...[
|
||||
Image.asset(
|
||||
'assets/images/big-vip.png',
|
||||
height: 16,
|
||||
const Spacer(),
|
||||
Text(
|
||||
widget.pages[i].badge!,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
],
|
||||
if (widget.pages[i].badge != '会员') ...[
|
||||
const Spacer(),
|
||||
Text(
|
||||
widget.pages[i].badge!,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
|
||||
@ -14,6 +14,7 @@ import 'package:pilipala/pages/main/index.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
import '../mine/controller.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/dynamic_panel.dart';
|
||||
import 'widgets/up_panel.dart';
|
||||
@ -28,6 +29,7 @@ class DynamicsPage extends StatefulWidget {
|
||||
class _DynamicsPageState extends State<DynamicsPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final DynamicsController _dynamicsController = Get.put(DynamicsController());
|
||||
final MineController mineController = Get.put(MineController());
|
||||
late Future _futureBuilderFuture;
|
||||
late Future _futureBuilderFutureUp;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
@ -256,6 +258,14 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
}
|
||||
},
|
||||
);
|
||||
} else if (data['msg'] == "账号未登录") {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
btnText: "去登录",
|
||||
fn: () {
|
||||
mineController.onLogin();
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/constants.dart';
|
||||
import 'package:pilipala/http/live.dart';
|
||||
import 'package:pilipala/models/live/quality.dart';
|
||||
import 'package:pilipala/models/live/room_info.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
import '../../models/live/room_info_h5.dart';
|
||||
@ -19,10 +21,16 @@ class LiveRoomController extends GetxController {
|
||||
PlPlayerController.getInstance(videoType: 'live');
|
||||
Rx<RoomInfoH5Model> roomInfoH5 = RoomInfoH5Model().obs;
|
||||
late bool enableCDN;
|
||||
late int currentQn;
|
||||
int? tempCurrentQn;
|
||||
late List<Map<String, dynamic>> acceptQnList;
|
||||
RxString currentQnDesc = ''.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
currentQn = setting.get(SettingBoxKey.defaultLiveQa,
|
||||
defaultValue: LiveQuality.values.last.code);
|
||||
roomId = int.parse(Get.parameters['roomid']!);
|
||||
if (Get.arguments != null) {
|
||||
liveItem = Get.arguments['liveItem'];
|
||||
@ -57,11 +65,28 @@ class LiveRoomController extends GetxController {
|
||||
}
|
||||
|
||||
Future queryLiveInfo() async {
|
||||
var res = await LiveHttp.liveRoomInfo(roomId: roomId, qn: 10000);
|
||||
var res = await LiveHttp.liveRoomInfo(roomId: roomId, qn: currentQn);
|
||||
if (res['status']) {
|
||||
List<CodecItem> codec =
|
||||
res['data'].playurlInfo.playurl.stream.first.format.first.codec;
|
||||
CodecItem item = codec.first;
|
||||
// 以服务端返回的码率为准
|
||||
currentQn = item.currentQn!;
|
||||
if (tempCurrentQn != null && tempCurrentQn == currentQn) {
|
||||
SmartDialog.showToast('画质切换失败,请检查登录状态');
|
||||
}
|
||||
List acceptQn = item.acceptQn!;
|
||||
acceptQnList = acceptQn.map((e) {
|
||||
return {
|
||||
'code': e,
|
||||
'desc': LiveQuality.values
|
||||
.firstWhere((element) => element.code == e)
|
||||
.description,
|
||||
};
|
||||
}).toList();
|
||||
currentQnDesc.value = LiveQuality.values
|
||||
.firstWhere((element) => element.code == currentQn)
|
||||
.description;
|
||||
String videoUrl = enableCDN
|
||||
? VideoUtils.getCdnUrl(item)
|
||||
: (item.urlInfo?.first.host)! +
|
||||
@ -90,4 +115,17 @@ class LiveRoomController extends GetxController {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 修改画质
|
||||
void changeQn(int qn) async {
|
||||
tempCurrentQn = currentQn;
|
||||
if (currentQn == qn) {
|
||||
return;
|
||||
}
|
||||
currentQn = qn;
|
||||
currentQnDesc.value = LiveQuality.values
|
||||
.firstWhere((element) => element.code == currentQn)
|
||||
.description;
|
||||
await queryLiveInfo();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:floating/floating.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/models/video/play/url.dart';
|
||||
import 'package:pilipala/pages/live_room/index.dart';
|
||||
@ -83,6 +84,30 @@ class _BottomControlState extends State<BottomControl> {
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 4),
|
||||
SizedBox(
|
||||
width: 30,
|
||||
child: PopupMenuButton<int>(
|
||||
padding: EdgeInsets.zero,
|
||||
onSelected: (value) {
|
||||
widget.liveRoomCtr!.changeQn(value);
|
||||
},
|
||||
child: Obx(
|
||||
() => Text(
|
||||
widget.liveRoomCtr!.currentQnDesc.value,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
itemBuilder: (BuildContext context) {
|
||||
return widget.liveRoomCtr!.acceptQnList.map((e) {
|
||||
return PopupMenuItem<int>(
|
||||
value: e['code'],
|
||||
child: Text(e['desc']),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
if (Platform.isAndroid) ...[
|
||||
SizedBox(
|
||||
width: 34,
|
||||
@ -110,7 +135,7 @@ class _BottomControlState extends State<BottomControl> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
ComBtn(
|
||||
icon: const Icon(
|
||||
|
||||
@ -10,6 +10,7 @@ import 'package:pilipala/services/service_locator.dart';
|
||||
import 'package:pilipala/utils/global_data.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
import '../../models/live/quality.dart';
|
||||
import 'widgets/switch_item.dart';
|
||||
|
||||
class PlaySetting extends StatefulWidget {
|
||||
@ -22,6 +23,7 @@ class PlaySetting extends StatefulWidget {
|
||||
class _PlaySettingState extends State<PlaySetting> {
|
||||
Box setting = GStrorage.setting;
|
||||
late dynamic defaultVideoQa;
|
||||
late dynamic defaultLiveQa;
|
||||
late dynamic defaultAudioQa;
|
||||
late dynamic defaultDecode;
|
||||
late int defaultFullScreenMode;
|
||||
@ -32,6 +34,8 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
super.initState();
|
||||
defaultVideoQa = setting.get(SettingBoxKey.defaultVideoQa,
|
||||
defaultValue: VideoQuality.values.last.code);
|
||||
defaultLiveQa = setting.get(SettingBoxKey.defaultLiveQa,
|
||||
defaultValue: LiveQuality.values.last.code);
|
||||
defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
||||
defaultValue: AudioQuality.values.last.code);
|
||||
defaultDecode = setting.get(SettingBoxKey.defaultDecode,
|
||||
@ -157,9 +161,9 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
}),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('默认画质', style: titleStyle),
|
||||
title: Text('默认视频画质', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前画质${VideoQualityCode.fromCode(defaultVideoQa)!.description!}',
|
||||
'当前默认画质${VideoQualityCode.fromCode(defaultVideoQa)!.description!}',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
onTap: () async {
|
||||
@ -167,7 +171,7 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<int>(
|
||||
title: '默认画质',
|
||||
title: '默认视频画质',
|
||||
value: defaultVideoQa,
|
||||
values: VideoQuality.values.reversed.map((e) {
|
||||
return {'title': e.description, 'value': e.code};
|
||||
@ -181,6 +185,32 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('默认直播画质', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前默认画质${LiveQualityCode.fromCode(defaultLiveQa)!.description!}',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
onTap: () async {
|
||||
int? result = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<int>(
|
||||
title: '默认直播画质',
|
||||
value: defaultLiveQa,
|
||||
values: LiveQuality.values.reversed.map((e) {
|
||||
return {'title': e.description, 'value': e.code};
|
||||
}).toList());
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
defaultLiveQa = result;
|
||||
setting.put(SettingBoxKey.defaultLiveQa, result);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('默认音质', style: titleStyle),
|
||||
|
||||
@ -25,13 +25,6 @@ class VideoIntroController extends GetxController {
|
||||
VideoIntroController({required this.bvid});
|
||||
// 视频bvid
|
||||
String bvid;
|
||||
|
||||
// 是否预渲染 骨架屏
|
||||
bool preRender = false;
|
||||
|
||||
// 视频详情 上个页面传入
|
||||
Map? videoItem = {};
|
||||
|
||||
// 请求状态
|
||||
RxBool isLoading = false.obs;
|
||||
|
||||
@ -74,26 +67,6 @@ class VideoIntroController extends GetxController {
|
||||
try {
|
||||
heroTag = Get.arguments['heroTag'];
|
||||
} catch (_) {}
|
||||
if (Get.arguments.isNotEmpty) {
|
||||
if (Get.arguments.containsKey('videoItem')) {
|
||||
preRender = true;
|
||||
var args = Get.arguments['videoItem'];
|
||||
var keys = Get.arguments.keys.toList();
|
||||
videoItem!['pic'] = args.pic;
|
||||
if (args.title is String) {
|
||||
videoItem!['title'] = args.title;
|
||||
} else {
|
||||
String str = '';
|
||||
for (Map map in args.title) {
|
||||
str += map['text'];
|
||||
}
|
||||
videoItem!['title'] = str;
|
||||
}
|
||||
videoItem!['stat'] = keys.contains('stat') && args.stat;
|
||||
videoItem!['pubdate'] = keys.contains('pubdate') && args.pubdate;
|
||||
videoItem!['owner'] = keys.contains('owner') && args.owner;
|
||||
}
|
||||
}
|
||||
userLogin = userInfo != null;
|
||||
lastPlayCid.value = int.parse(Get.parameters['cid']!);
|
||||
isShowOnlineTotal =
|
||||
|
||||
@ -15,9 +15,7 @@ import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
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';
|
||||
@ -78,7 +76,6 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
// 请求成功
|
||||
return Obx(
|
||||
() => VideoInfo(
|
||||
loadingStatus: false,
|
||||
videoDetail: videoIntroController.videoDetail.value,
|
||||
heroTag: heroTag,
|
||||
bvid: widget.bvid,
|
||||
@ -96,11 +93,13 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return VideoInfo(
|
||||
loadingStatus: true,
|
||||
videoDetail: videoDetail,
|
||||
heroTag: heroTag,
|
||||
bvid: widget.bvid,
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 100,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -109,14 +108,12 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
}
|
||||
|
||||
class VideoInfo extends StatefulWidget {
|
||||
final bool loadingStatus;
|
||||
final VideoDetailData? videoDetail;
|
||||
final String? heroTag;
|
||||
final String bvid;
|
||||
|
||||
const VideoInfo({
|
||||
Key? key,
|
||||
this.loadingStatus = false,
|
||||
this.videoDetail,
|
||||
this.heroTag,
|
||||
required this.bvid,
|
||||
@ -127,18 +124,12 @@ class VideoInfo extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
// final String heroTag = Get.arguments['heroTag'];
|
||||
late String heroTag;
|
||||
late final VideoIntroController videoIntroController;
|
||||
late final VideoDetailController videoDetailCtr;
|
||||
late final Map<dynamic, dynamic> videoItem;
|
||||
|
||||
final Box<dynamic> localCache = GStrorage.localCache;
|
||||
final Box<dynamic> setting = GStrorage.setting;
|
||||
late double sheetHeight;
|
||||
|
||||
late final bool loadingStatus; // 加载状态
|
||||
|
||||
late final dynamic owner;
|
||||
late final dynamic follower;
|
||||
late final dynamic followStatus;
|
||||
@ -163,14 +154,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
videoIntroController =
|
||||
Get.put(VideoIntroController(bvid: widget.bvid), 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']);
|
||||
owner = widget.videoDetail!.owner;
|
||||
follower = Utils.numFormat(videoIntroController.userStat['follower']);
|
||||
followStatus = videoIntroController.followStatus;
|
||||
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
||||
}
|
||||
@ -224,9 +211,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
|
||||
// 视频介绍
|
||||
showIntroDetail() {
|
||||
if (loadingStatus) {
|
||||
return;
|
||||
}
|
||||
feedBack();
|
||||
showBottomSheet(
|
||||
context: context,
|
||||
@ -240,13 +224,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
// 用户主页
|
||||
onPushMember() {
|
||||
feedBack();
|
||||
mid = !loadingStatus
|
||||
? widget.videoDetail!.owner!.mid
|
||||
: videoItem['owner'].mid;
|
||||
mid = widget.videoDetail!.owner!.mid!;
|
||||
memberHeroTag = Utils.makeHeroTag(mid);
|
||||
String face = !loadingStatus
|
||||
? widget.videoDetail!.owner!.face
|
||||
: videoItem['owner'].face;
|
||||
String face = widget.videoDetail!.owner!.face!;
|
||||
Get.toNamed('/member?mid=$mid',
|
||||
arguments: {'face': face, 'heroTag': memberHeroTag});
|
||||
}
|
||||
@ -273,221 +253,181 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
top: 16,
|
||||
),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: !loadingStatus
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => showIntroDetail(),
|
||||
child: Text(
|
||||
!loadingStatus
|
||||
? widget.videoDetail!.title
|
||||
: videoItem['title'],
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Stack(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => showIntroDetail(),
|
||||
child: Text(
|
||||
widget.videoDetail!.title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Stack(
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => showIntroDetail(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 7, bottom: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => showIntroDetail(),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 7, bottom: 6),
|
||||
child: Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: !loadingStatus
|
||||
? widget.videoDetail!.stat!.view
|
||||
: videoItem['stat'].view,
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: !loadingStatus
|
||||
? widget.videoDetail!.stat!.danmaku
|
||||
: videoItem['stat'].danmaku,
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
Utils.dateFormat(
|
||||
!loadingStatus
|
||||
? widget.videoDetail!.pubdate
|
||||
: videoItem['pubdate'],
|
||||
formatType: 'detail'),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
if (videoIntroController.isShowOnlineTotal)
|
||||
Obx(
|
||||
() => Text(
|
||||
'${videoIntroController.total.value}人在看',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: widget.videoDetail!.stat!.view,
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: widget.videoDetail!.stat!.danmaku,
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
Utils.dateFormat(widget.videoDetail!.pubdate,
|
||||
formatType: 'detail'),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
if (enableAi)
|
||||
Positioned(
|
||||
right: 10,
|
||||
top: 6,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final res =
|
||||
await videoIntroController.aiConclusion();
|
||||
if (res['status']) {
|
||||
showAiBottomSheet();
|
||||
}
|
||||
},
|
||||
child:
|
||||
Image.asset('assets/images/ai.png', height: 22),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
// 点赞收藏转发 布局样式1
|
||||
// SingleChildScrollView(
|
||||
// padding: const EdgeInsets.only(top: 7, bottom: 7),
|
||||
// scrollDirection: Axis.horizontal,
|
||||
// child: actionRow(
|
||||
// context,
|
||||
// videoIntroController,
|
||||
// videoDetailCtr,
|
||||
// ),
|
||||
// ),
|
||||
// 点赞收藏转发 布局样式2
|
||||
actionGrid(context, videoIntroController),
|
||||
// 合集
|
||||
if (!loadingStatus &&
|
||||
widget.videoDetail!.ugcSeason != null) ...[
|
||||
Obx(
|
||||
() => SeasonPanel(
|
||||
ugcSeason: widget.videoDetail!.ugcSeason!,
|
||||
cid: videoIntroController.lastPlayCid.value != 0
|
||||
? videoIntroController.lastPlayCid.value
|
||||
: widget.videoDetail!.pages!.first.cid,
|
||||
sheetHeight: sheetHeight,
|
||||
changeFuc: (bvid, cid, aid) => videoIntroController
|
||||
.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: onPushMember,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 12, horizontal: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
src: loadingStatus
|
||||
? owner.face
|
||||
: widget.videoDetail!.owner!.face,
|
||||
width: 34,
|
||||
height: 34,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(owner.name,
|
||||
style: const TextStyle(fontSize: 13)),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
follower,
|
||||
const SizedBox(width: 10),
|
||||
if (videoIntroController.isShowOnlineTotal)
|
||||
Obx(
|
||||
() => Text(
|
||||
'${videoIntroController.total.value}人在看',
|
||||
style: TextStyle(
|
||||
fontSize: t.textTheme.labelSmall!.fontSize,
|
||||
color: outline,
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Obx(() => AnimatedOpacity(
|
||||
opacity: loadingStatus ||
|
||||
videoIntroController
|
||||
.followStatus.isEmpty
|
||||
? 0
|
||||
: 1,
|
||||
duration: const Duration(milliseconds: 50),
|
||||
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:
|
||||
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('关注'),
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 100,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (enableAi)
|
||||
Positioned(
|
||||
right: 10,
|
||||
top: 6,
|
||||
child: GestureDetector(
|
||||
onTap: () async {
|
||||
final res = await videoIntroController.aiConclusion();
|
||||
if (res['status']) {
|
||||
showAiBottomSheet();
|
||||
}
|
||||
},
|
||||
child: Image.asset('assets/images/ai.png', height: 22),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
||||
/// 点赞收藏转发
|
||||
actionGrid(context, videoIntroController),
|
||||
// 合集
|
||||
if (widget.videoDetail!.ugcSeason != null) ...[
|
||||
Obx(
|
||||
() => SeasonPanel(
|
||||
ugcSeason: widget.videoDetail!.ugcSeason!,
|
||||
cid: videoIntroController.lastPlayCid.value != 0
|
||||
? videoIntroController.lastPlayCid.value
|
||||
: widget.videoDetail!.pages!.first.cid,
|
||||
sheetHeight: sheetHeight,
|
||||
changeFuc: (bvid, cid, aid) =>
|
||||
videoIntroController.changeSeasonOrbangu(bvid, cid, aid),
|
||||
),
|
||||
)
|
||||
],
|
||||
if (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: onPushMember,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
src: widget.videoDetail!.owner!.face,
|
||||
width: 34,
|
||||
height: 34,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(owner.name, style: const TextStyle(fontSize: 13)),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
follower,
|
||||
style: TextStyle(
|
||||
fontSize: t.textTheme.labelSmall!.fontSize,
|
||||
color: outline,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Obx(() => AnimatedOpacity(
|
||||
opacity:
|
||||
videoIntroController.followStatus.isEmpty ? 0 : 1,
|
||||
duration: const Duration(milliseconds: 50),
|
||||
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:
|
||||
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('关注'),
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
@ -509,10 +449,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||
onTap: handleState(videoIntroController.actionLikeVideo),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.like!.toString()
|
||||
: '-'),
|
||||
text: widget.videoDetail!.stat!.like!.toString()),
|
||||
),
|
||||
// ActionItem(
|
||||
// icon: const Icon(FontAwesomeIcons.clock),
|
||||
@ -522,104 +459,38 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
// text: '稍后再看'),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.coin!.toString()
|
||||
: '-'),
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
text: widget.videoDetail!.stat!.coin!.toString(),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.favorite!.toString()
|
||||
: '-'),
|
||||
icon: const Icon(FontAwesomeIcons.star),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
text: widget.videoDetail!.stat!.favorite!.toString(),
|
||||
),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
onTap: () => videoDetailCtr.tabCtr.animateTo(1),
|
||||
selectStatus: false,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.reply!.toString()
|
||||
: '评论'),
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
onTap: () => videoDetailCtr.tabCtr.animateTo(1),
|
||||
selectStatus: false,
|
||||
text: widget.videoDetail!.stat!.reply!.toString(),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: loadingStatus,
|
||||
text: '分享'),
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
text: '分享',
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
|
||||
return Row(children: <Widget>[
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
onTap: handleState(videoIntroController.actionLikeVideo),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text:
|
||||
!loadingStatus ? widget.videoDetail!.stat!.like!.toString() : '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text:
|
||||
!loadingStatus ? widget.videoDetail!.stat!.coin!.toString() : '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Obx(
|
||||
() => ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.heart),
|
||||
onTap: () => showFavBottomSheet(),
|
||||
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
? widget.videoDetail!.stat!.favorite!.toString()
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
onTap: () {
|
||||
videoDetailCtr.tabCtr.animateTo(1);
|
||||
},
|
||||
selectStatus: false,
|
||||
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: loadingStatus,
|
||||
// text: !loadingStatus
|
||||
// ? widget.videoDetail!.stat!.share!.toString()
|
||||
// : '-',
|
||||
text: '转发'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ class ActionItem extends StatelessWidget {
|
||||
final Icon? selectIcon;
|
||||
final Function? onTap;
|
||||
final Function? onLongPress;
|
||||
final bool? loadingStatus;
|
||||
final String? text;
|
||||
final bool selectStatus;
|
||||
|
||||
@ -17,7 +16,6 @@ class ActionItem extends StatelessWidget {
|
||||
this.selectIcon,
|
||||
this.onTap,
|
||||
this.onLongPress,
|
||||
this.loadingStatus,
|
||||
this.text,
|
||||
this.selectStatus = false,
|
||||
}) : super(key: key);
|
||||
@ -43,25 +41,15 @@ class ActionItem extends StatelessWidget {
|
||||
: Icon(icon!.icon!,
|
||||
size: 18, color: Theme.of(context).colorScheme.outline),
|
||||
const SizedBox(height: 6),
|
||||
AnimatedOpacity(
|
||||
opacity: loadingStatus! ? 0 : 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
transitionBuilder: (Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(scale: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
text ?? '',
|
||||
key: ValueKey<String>(text ?? ''),
|
||||
style: TextStyle(
|
||||
color: selectStatus
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize),
|
||||
),
|
||||
Text(
|
||||
text ?? '',
|
||||
style: TextStyle(
|
||||
color: selectStatus
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@ -194,6 +194,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
floating.toggleAutoPip(autoEnter: false);
|
||||
floating.dispose();
|
||||
}
|
||||
appbarStream.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@ -180,7 +180,8 @@ class _WhisperPageState extends State<WhisperPage> {
|
||||
sessionList[i]
|
||||
.lastMsg
|
||||
.content[
|
||||
'reply_content'])
|
||||
'reply_content'] ??
|
||||
'不支持的消息类型')
|
||||
: '不支持的消息类型',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
||||
10
lib/plugin/pl_player/models/bottom_control_type.dart
Normal file
10
lib/plugin/pl_player/models/bottom_control_type.dart
Normal file
@ -0,0 +1,10 @@
|
||||
enum BottomControlType {
|
||||
pre,
|
||||
playOrPause,
|
||||
next,
|
||||
time,
|
||||
space,
|
||||
fit,
|
||||
speed,
|
||||
fullscreen,
|
||||
}
|
||||
@ -8,7 +8,6 @@ import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
import 'package:nil/nil.dart';
|
||||
import 'package:pilipala/models/common/gesture_mode.dart';
|
||||
import 'package:pilipala/plugin/pl_player/controller.dart';
|
||||
import 'package:pilipala/plugin/pl_player/models/duration.dart';
|
||||
@ -19,12 +18,14 @@ import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:screen_brightness/screen_brightness.dart';
|
||||
|
||||
import '../../utils/global_data.dart';
|
||||
import 'models/bottom_control_type.dart';
|
||||
import 'models/bottom_progress_behavior.dart';
|
||||
import 'widgets/app_bar_ani.dart';
|
||||
import 'widgets/backward_seek.dart';
|
||||
import 'widgets/bottom_control.dart';
|
||||
import 'widgets/common_btn.dart';
|
||||
import 'widgets/forward_seek.dart';
|
||||
import 'widgets/play_pause_btn.dart';
|
||||
|
||||
class PLVideoPlayer extends StatefulWidget {
|
||||
const PLVideoPlayer({
|
||||
@ -32,6 +33,7 @@ class PLVideoPlayer extends StatefulWidget {
|
||||
this.headerControl,
|
||||
this.bottomControl,
|
||||
this.danmuWidget,
|
||||
this.bottomList,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@ -39,6 +41,7 @@ class PLVideoPlayer extends StatefulWidget {
|
||||
final PreferredSizeWidget? headerControl;
|
||||
final PreferredSizeWidget? bottomControl;
|
||||
final Widget? danmuWidget;
|
||||
final List<BottomControlType>? bottomList;
|
||||
|
||||
@override
|
||||
State<PLVideoPlayer> createState() => _PLVideoPlayerState();
|
||||
@ -48,26 +51,22 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController animationController;
|
||||
late VideoController videoController;
|
||||
final PLVideoPlayerController _ctr = Get.put(PLVideoPlayerController());
|
||||
|
||||
// bool _mountSeekBackwardButton = false;
|
||||
// bool _mountSeekForwardButton = false;
|
||||
// bool _hideSeekBackwardButton = false;
|
||||
// bool _hideSeekForwardButton = false;
|
||||
final RxBool _mountSeekBackwardButton = false.obs;
|
||||
final RxBool _mountSeekForwardButton = false.obs;
|
||||
final RxBool _hideSeekBackwardButton = false.obs;
|
||||
final RxBool _hideSeekForwardButton = false.obs;
|
||||
|
||||
// double _brightnessValue = 0.0;
|
||||
// bool _brightnessIndicator = false;
|
||||
final RxDouble _brightnessValue = 0.0.obs;
|
||||
final RxBool _brightnessIndicator = false.obs;
|
||||
Timer? _brightnessTimer;
|
||||
|
||||
// double _volumeValue = 0.0;
|
||||
// bool _volumeIndicator = false;
|
||||
final RxDouble _volumeValue = 0.0.obs;
|
||||
final RxBool _volumeIndicator = false.obs;
|
||||
Timer? _volumeTimer;
|
||||
|
||||
double _distance = 0.0;
|
||||
// 初始手指落下位置
|
||||
// double _initTapPositoin = 0.0;
|
||||
|
||||
// bool _volumeInterceptEventStream = false;
|
||||
final RxDouble _distance = 0.0.obs;
|
||||
final RxBool _volumeInterceptEventStream = false.obs;
|
||||
|
||||
Box setting = GStrorage.setting;
|
||||
late FullScreenMode mode;
|
||||
@ -82,11 +81,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
DateTime? lastFullScreenToggleTime;
|
||||
|
||||
void onDoubleTapSeekBackward() {
|
||||
_ctr.onDoubleTapSeekBackward();
|
||||
_mountSeekBackwardButton.value = true;
|
||||
}
|
||||
|
||||
void onDoubleTapSeekForward() {
|
||||
_ctr.onDoubleTapSeekForward();
|
||||
_mountSeekForwardButton.value = true;
|
||||
}
|
||||
|
||||
// 双击播放、暂停
|
||||
@ -138,10 +137,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
Future.microtask(() async {
|
||||
try {
|
||||
FlutterVolumeController.updateShowSystemUI(true);
|
||||
_ctr.volumeValue.value = (await FlutterVolumeController.getVolume())!;
|
||||
_volumeValue.value = (await FlutterVolumeController.getVolume())!;
|
||||
FlutterVolumeController.addListener((double value) {
|
||||
if (mounted && !_ctr.volumeInterceptEventStream.value) {
|
||||
_ctr.volumeValue.value = value;
|
||||
if (mounted && !_volumeInterceptEventStream.value) {
|
||||
_volumeValue.value = value;
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
@ -149,10 +148,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
|
||||
Future.microtask(() async {
|
||||
try {
|
||||
_ctr.brightnessValue.value = await ScreenBrightness().current;
|
||||
_brightnessValue.value = await ScreenBrightness().current;
|
||||
ScreenBrightness().onCurrentBrightnessChanged.listen((double value) {
|
||||
if (mounted) {
|
||||
_ctr.brightnessValue.value = value;
|
||||
_brightnessValue.value = value;
|
||||
}
|
||||
});
|
||||
} catch (_) {}
|
||||
@ -164,14 +163,14 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
FlutterVolumeController.updateShowSystemUI(false);
|
||||
await FlutterVolumeController.setVolume(value);
|
||||
} catch (_) {}
|
||||
_ctr.volumeValue.value = value;
|
||||
_ctr.volumeIndicator.value = true;
|
||||
_ctr.volumeInterceptEventStream.value = true;
|
||||
_volumeValue.value = value;
|
||||
_volumeIndicator.value = true;
|
||||
_volumeInterceptEventStream.value = true;
|
||||
_volumeTimer?.cancel();
|
||||
_volumeTimer = Timer(const Duration(milliseconds: 200), () {
|
||||
if (mounted) {
|
||||
_ctr.volumeIndicator.value = false;
|
||||
_ctr.volumeInterceptEventStream.value = false;
|
||||
_volumeIndicator.value = false;
|
||||
_volumeInterceptEventStream.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -180,11 +179,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
try {
|
||||
await ScreenBrightness().setScreenBrightness(value);
|
||||
} catch (_) {}
|
||||
_ctr.brightnessIndicator.value = true;
|
||||
_brightnessIndicator.value = true;
|
||||
_brightnessTimer?.cancel();
|
||||
_brightnessTimer = Timer(const Duration(milliseconds: 200), () {
|
||||
if (mounted) {
|
||||
_ctr.brightnessIndicator.value = false;
|
||||
_brightnessIndicator.value = false;
|
||||
}
|
||||
});
|
||||
widget.controller.brightness.value = value;
|
||||
@ -197,6 +196,134 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// 动态构建底部控制条
|
||||
List<Widget> buildBottomControl() {
|
||||
const TextStyle textStyle = TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
);
|
||||
final PlPlayerController _ = widget.controller;
|
||||
Map<BottomControlType, Widget> videoProgressWidgets = {
|
||||
/// 上一集
|
||||
BottomControlType.pre: ComBtn(
|
||||
icon: const Icon(
|
||||
Icons.skip_previous_outlined,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
fuc: () {},
|
||||
),
|
||||
|
||||
/// 播放暂停
|
||||
BottomControlType.playOrPause: PlayOrPauseButton(
|
||||
controller: _,
|
||||
),
|
||||
|
||||
/// 下一集
|
||||
BottomControlType.next: ComBtn(
|
||||
icon: const Icon(
|
||||
Icons.last_page_outlined,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
fuc: () {},
|
||||
),
|
||||
|
||||
/// 时间进度
|
||||
BottomControlType.time: Row(
|
||||
children: [
|
||||
Obx(() {
|
||||
return Text(
|
||||
_.durationSeconds.value >= 3600
|
||||
? printDurationWithHours(
|
||||
Duration(seconds: _.positionSeconds.value))
|
||||
: printDuration(Duration(seconds: _.positionSeconds.value)),
|
||||
style: textStyle,
|
||||
);
|
||||
}),
|
||||
const SizedBox(width: 2),
|
||||
const Text('/', style: textStyle),
|
||||
const SizedBox(width: 2),
|
||||
Obx(
|
||||
() => Text(
|
||||
_.durationSeconds.value >= 3600
|
||||
? printDurationWithHours(
|
||||
Duration(seconds: _.durationSeconds.value))
|
||||
: printDuration(Duration(seconds: _.durationSeconds.value)),
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
/// 空白占位
|
||||
BottomControlType.space: const Spacer(),
|
||||
|
||||
/// 画面比例
|
||||
BottomControlType.fit: SizedBox(
|
||||
height: 30,
|
||||
child: TextButton(
|
||||
onPressed: () => _.toggleVideoFit(),
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
child: Obx(
|
||||
() => Text(
|
||||
_.videoFitDEsc.value,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// 播放速度
|
||||
BottomControlType.speed: SizedBox(
|
||||
width: 45,
|
||||
height: 34,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {},
|
||||
child: Obx(
|
||||
() => Text(
|
||||
'${_.playbackSpeed.toString()}X',
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// 字幕
|
||||
/// 全屏
|
||||
BottomControlType.fullscreen: ComBtn(
|
||||
icon: Obx(
|
||||
() => Icon(
|
||||
_.isFullScreen.value
|
||||
? FontAwesomeIcons.compress
|
||||
: FontAwesomeIcons.expand,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
fuc: () => _.triggerFullScreen(),
|
||||
),
|
||||
};
|
||||
final List<Widget> list = [];
|
||||
var userSpecifyItem = widget.bottomList ??
|
||||
[
|
||||
BottomControlType.playOrPause,
|
||||
BottomControlType.time,
|
||||
BottomControlType.space,
|
||||
BottomControlType.fit,
|
||||
BottomControlType.fullscreen,
|
||||
];
|
||||
for (var i = 0; i < userSpecifyItem.length; i++) {
|
||||
list.add(videoProgressWidgets[userSpecifyItem[i]]!);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final PlPlayerController _ = widget.controller;
|
||||
@ -316,7 +443,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
() => Align(
|
||||
child: AnimatedOpacity(
|
||||
curve: Curves.easeInOut,
|
||||
opacity: _ctr.volumeIndicator.value ? 1.0 : 0.0,
|
||||
opacity: _volumeIndicator.value ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
@ -335,9 +462,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
width: 28.0,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Icon(
|
||||
_ctr.volumeValue.value == 0.0
|
||||
_volumeValue.value == 0.0
|
||||
? Icons.volume_off
|
||||
: _ctr.volumeValue.value < 0.5
|
||||
: _volumeValue.value < 0.5
|
||||
? Icons.volume_down
|
||||
: Icons.volume_up,
|
||||
color: const Color(0xFFFFFFFF),
|
||||
@ -346,7 +473,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${(_ctr.volumeValue.value * 100.0).round()}%',
|
||||
'${(_volumeValue.value * 100.0).round()}%',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 13.0,
|
||||
@ -367,7 +494,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
() => Align(
|
||||
child: AnimatedOpacity(
|
||||
curve: Curves.easeInOut,
|
||||
opacity: _ctr.brightnessIndicator.value ? 1.0 : 0.0,
|
||||
opacity: _brightnessIndicator.value ? 1.0 : 0.0,
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
@ -386,9 +513,9 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
width: 28.0,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Icon(
|
||||
_ctr.brightnessValue.value < 1.0 / 3.0
|
||||
_brightnessValue.value < 1.0 / 3.0
|
||||
? Icons.brightness_low
|
||||
: _ctr.brightnessValue.value < 2.0 / 3.0
|
||||
: _brightnessValue.value < 2.0 / 3.0
|
||||
? Icons.brightness_medium
|
||||
: Icons.brightness_high,
|
||||
color: const Color(0xFFFFFFFF),
|
||||
@ -398,7 +525,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
const SizedBox(width: 2.0),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'${(_ctr.brightnessValue.value * 100.0).round()}%',
|
||||
'${(_brightnessValue.value * 100.0).round()}%',
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 13.0,
|
||||
@ -489,7 +616,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
pos.clamp(Duration.zero, _.duration.value);
|
||||
_.onUpdatedSliderProgress(result);
|
||||
_.onChangedSliderStart();
|
||||
// _initTapPositoin = tapPosition;
|
||||
},
|
||||
onHorizontalDragEnd: (DragEndDetails details) {
|
||||
if (_.videoType.value == 'live' || _.controlsLock.value) {
|
||||
@ -521,7 +647,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
: screenWidth * 9 / 16) *
|
||||
3;
|
||||
final double brightness =
|
||||
_ctr.brightnessValue.value - delta / level;
|
||||
_brightnessValue.value - delta / level;
|
||||
final double result = brightness.clamp(0.0, 1.0);
|
||||
setBrightness(result);
|
||||
} else if (tapPosition < sectionWidth * 2) {
|
||||
@ -530,29 +656,29 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
const double threshold = 7.0; // 滑动阈值
|
||||
final bool flag =
|
||||
fullScreenGestureMode != FullScreenGestureMode.values.last;
|
||||
if (dy > _distance && dy > threshold) {
|
||||
if (dy > _distance.value && dy > threshold) {
|
||||
if (_.isFullScreen.value ^ flag) {
|
||||
lastFullScreenToggleTime = DateTime.now();
|
||||
// 下滑退出全屏
|
||||
await widget.controller.triggerFullScreen(status: flag);
|
||||
}
|
||||
_distance = 0.0;
|
||||
} else if (dy < _distance && dy < -threshold) {
|
||||
_distance.value = 0.0;
|
||||
} else if (dy < _distance.value && dy < -threshold) {
|
||||
if (!_.isFullScreen.value ^ flag) {
|
||||
lastFullScreenToggleTime = DateTime.now();
|
||||
// 上滑进入全屏
|
||||
await widget.controller.triggerFullScreen(status: !flag);
|
||||
}
|
||||
_distance = 0.0;
|
||||
_distance.value = 0.0;
|
||||
}
|
||||
_distance = dy;
|
||||
_distance.value = dy;
|
||||
} else {
|
||||
// 右边区域 👈
|
||||
final double level = (_.isFullScreen.value
|
||||
? Get.size.height
|
||||
: screenWidth * 9 / 16) *
|
||||
3;
|
||||
final double volume = _ctr.volumeValue.value - delta / level;
|
||||
final double volume = _volumeValue.value - delta / level;
|
||||
final double result = volume.clamp(0.0, 1.0);
|
||||
setVolume(result);
|
||||
}
|
||||
@ -585,9 +711,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
position: 'bottom',
|
||||
child: widget.bottomControl ??
|
||||
BottomControl(
|
||||
controller: widget.controller,
|
||||
triggerFullScreen:
|
||||
widget.controller.triggerFullScreen),
|
||||
controller: widget.controller,
|
||||
triggerFullScreen: _.triggerFullScreen,
|
||||
buildBottomControl: buildBottomControl(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -607,23 +734,23 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
}
|
||||
if (defaultBtmProgressBehavior ==
|
||||
BtmProgresBehavior.alwaysHide.code) {
|
||||
return nil;
|
||||
return const SizedBox();
|
||||
}
|
||||
if (defaultBtmProgressBehavior ==
|
||||
BtmProgresBehavior.onlyShowFullScreen.code &&
|
||||
!_.isFullScreen.value) {
|
||||
return nil;
|
||||
return const SizedBox();
|
||||
} else if (defaultBtmProgressBehavior ==
|
||||
BtmProgresBehavior.onlyHideFullScreen.code &&
|
||||
_.isFullScreen.value) {
|
||||
return nil;
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
if (_.videoType.value == 'live') {
|
||||
return const SizedBox();
|
||||
}
|
||||
if (value > max || max <= 0) {
|
||||
return nil;
|
||||
return const SizedBox();
|
||||
}
|
||||
return Positioned(
|
||||
bottom: -1.5,
|
||||
@ -720,18 +847,17 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
/// 点击 快进/快退
|
||||
Obx(
|
||||
() => Visibility(
|
||||
visible: _ctr.mountSeekBackwardButton.value ||
|
||||
_ctr.mountSeekForwardButton.value,
|
||||
visible:
|
||||
_mountSeekBackwardButton.value || _mountSeekForwardButton.value,
|
||||
child: Positioned.fill(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: _ctr.mountSeekBackwardButton.value
|
||||
child: _mountSeekBackwardButton.value
|
||||
? TweenAnimationBuilder<double>(
|
||||
tween: Tween<double>(
|
||||
begin: 0.0,
|
||||
end:
|
||||
_ctr.hideSeekBackwardButton.value ? 0.0 : 1.0,
|
||||
end: _hideSeekBackwardButton.value ? 0.0 : 1.0,
|
||||
),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
builder: (BuildContext context, double value,
|
||||
@ -741,17 +867,15 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
child: child,
|
||||
),
|
||||
onEnd: () {
|
||||
if (_ctr.hideSeekBackwardButton.value) {
|
||||
_ctr.hideSeekBackwardButton.value = false;
|
||||
_ctr.mountSeekBackwardButton.value = false;
|
||||
if (_hideSeekBackwardButton.value) {
|
||||
_hideSeekBackwardButton.value = false;
|
||||
_mountSeekBackwardButton.value = false;
|
||||
}
|
||||
},
|
||||
child: BackwardSeekIndicator(
|
||||
onChanged: (Duration value) {
|
||||
// _seekBarDeltaValueNotifier.value = -value;
|
||||
},
|
||||
onChanged: (Duration value) => {},
|
||||
onSubmitted: (Duration value) {
|
||||
_ctr.hideSeekBackwardButton.value = true;
|
||||
_hideSeekBackwardButton.value = true;
|
||||
final Player player =
|
||||
widget.controller.videoPlayerController!;
|
||||
Duration result = player.state.position - value;
|
||||
@ -764,7 +888,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
},
|
||||
),
|
||||
)
|
||||
: nil,
|
||||
: const SizedBox(),
|
||||
),
|
||||
Expanded(
|
||||
child: SizedBox(
|
||||
@ -772,11 +896,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _ctr.mountSeekForwardButton.value
|
||||
child: _mountSeekForwardButton.value
|
||||
? TweenAnimationBuilder<double>(
|
||||
tween: Tween<double>(
|
||||
begin: 0.0,
|
||||
end: _ctr.hideSeekForwardButton.value ? 0.0 : 1.0,
|
||||
end: _hideSeekForwardButton.value ? 0.0 : 1.0,
|
||||
),
|
||||
duration: const Duration(milliseconds: 500),
|
||||
builder: (BuildContext context, double value,
|
||||
@ -786,17 +910,15 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
child: child,
|
||||
),
|
||||
onEnd: () {
|
||||
if (_ctr.hideSeekForwardButton.value) {
|
||||
_ctr.hideSeekForwardButton.value = false;
|
||||
_ctr.mountSeekForwardButton.value = false;
|
||||
if (_hideSeekForwardButton.value) {
|
||||
_hideSeekForwardButton.value = false;
|
||||
_mountSeekForwardButton.value = false;
|
||||
}
|
||||
},
|
||||
child: ForwardSeekIndicator(
|
||||
onChanged: (Duration value) {
|
||||
// _seekBarDeltaValueNotifier.value = value;
|
||||
},
|
||||
onChanged: (Duration value) => {},
|
||||
onSubmitted: (Duration value) {
|
||||
_ctr.hideSeekForwardButton.value = true;
|
||||
_hideSeekForwardButton.value = true;
|
||||
final Player player =
|
||||
widget.controller.videoPlayerController!;
|
||||
Duration result = player.state.position + value;
|
||||
@ -809,7 +931,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
},
|
||||
),
|
||||
)
|
||||
: nil,
|
||||
: const SizedBox(),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -820,31 +942,3 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PLVideoPlayerController extends GetxController {
|
||||
RxBool mountSeekBackwardButton = false.obs;
|
||||
RxBool mountSeekForwardButton = false.obs;
|
||||
RxBool hideSeekBackwardButton = false.obs;
|
||||
RxBool hideSeekForwardButton = false.obs;
|
||||
|
||||
RxDouble brightnessValue = 0.0.obs;
|
||||
RxBool brightnessIndicator = false.obs;
|
||||
|
||||
RxDouble volumeValue = 0.0.obs;
|
||||
RxBool volumeIndicator = false.obs;
|
||||
|
||||
RxDouble distance = 0.0.obs;
|
||||
// 初始手指落下位置
|
||||
RxDouble initTapPositoin = 0.0.obs;
|
||||
|
||||
RxBool volumeInterceptEventStream = false.obs;
|
||||
|
||||
// 双击快进 展示样式
|
||||
void onDoubleTapSeekForward() {
|
||||
mountSeekForwardButton.value = true;
|
||||
}
|
||||
|
||||
void onDoubleTapSeekBackward() {
|
||||
mountSeekBackwardButton.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,20 @@
|
||||
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:nil/nil.dart';
|
||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||
import 'package:pilipala/plugin/pl_player/widgets/play_pause_btn.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
|
||||
class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
final PlPlayerController? controller;
|
||||
final Function? triggerFullScreen;
|
||||
const BottomControl({this.controller, this.triggerFullScreen, Key? key})
|
||||
: super(key: key);
|
||||
final List<Widget>? buildBottomControl;
|
||||
const BottomControl({
|
||||
this.controller,
|
||||
this.triggerFullScreen,
|
||||
this.buildBottomControl,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Size get preferredSize => const Size(double.infinity, kToolbarHeight);
|
||||
@ -20,11 +23,6 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
Widget build(BuildContext context) {
|
||||
Color colorTheme = Theme.of(context).colorScheme.primary;
|
||||
final _ = controller!;
|
||||
const textStyle = TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
);
|
||||
|
||||
return Container(
|
||||
color: Colors.transparent,
|
||||
height: 90,
|
||||
@ -71,86 +69,89 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||
},
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
PlayOrPauseButton(
|
||||
controller: _,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
// 播放时间
|
||||
Obx(() {
|
||||
return Text(
|
||||
_.durationSeconds.value >= 3600
|
||||
? printDurationWithHours(
|
||||
Duration(seconds: _.positionSeconds.value))
|
||||
: printDuration(
|
||||
Duration(seconds: _.positionSeconds.value)),
|
||||
style: textStyle,
|
||||
);
|
||||
}),
|
||||
const SizedBox(width: 2),
|
||||
const Text('/', style: textStyle),
|
||||
const SizedBox(width: 2),
|
||||
Obx(
|
||||
() => Text(
|
||||
_.durationSeconds.value >= 3600
|
||||
? printDurationWithHours(
|
||||
Duration(seconds: _.durationSeconds.value))
|
||||
: printDuration(
|
||||
Duration(seconds: _.durationSeconds.value)),
|
||||
style: textStyle,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
// 倍速
|
||||
// Obx(
|
||||
// () => SizedBox(
|
||||
// width: 45,
|
||||
// height: 34,
|
||||
// child: TextButton(
|
||||
// style: ButtonStyle(
|
||||
// padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// _.togglePlaybackSpeed();
|
||||
// },
|
||||
// child: Text(
|
||||
// '${_.playbackSpeed.toString()}X',
|
||||
// style: textStyle,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
SizedBox(
|
||||
height: 30,
|
||||
child: TextButton(
|
||||
onPressed: () => _.toggleVideoFit(),
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
child: Obx(
|
||||
() => Text(
|
||||
_.videoFitDEsc.value,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
// 全屏
|
||||
Obx(
|
||||
() => ComBtn(
|
||||
icon: Icon(
|
||||
_.isFullScreen.value
|
||||
? FontAwesomeIcons.compress
|
||||
: FontAwesomeIcons.expand,
|
||||
size: 15,
|
||||
color: Colors.white,
|
||||
),
|
||||
fuc: () => triggerFullScreen!(),
|
||||
),
|
||||
),
|
||||
],
|
||||
children: [...buildBottomControl!],
|
||||
),
|
||||
// Row(
|
||||
// children: [
|
||||
// PlayOrPauseButton(
|
||||
// controller: _,
|
||||
// ),
|
||||
// const SizedBox(width: 4),
|
||||
// // 播放时间
|
||||
// Obx(() {
|
||||
// return Text(
|
||||
// _.durationSeconds.value >= 3600
|
||||
// ? printDurationWithHours(
|
||||
// Duration(seconds: _.positionSeconds.value))
|
||||
// : printDuration(
|
||||
// Duration(seconds: _.positionSeconds.value)),
|
||||
// style: textStyle,
|
||||
// );
|
||||
// }),
|
||||
// const SizedBox(width: 2),
|
||||
// const Text('/', style: textStyle),
|
||||
// const SizedBox(width: 2),
|
||||
// Obx(
|
||||
// () => Text(
|
||||
// _.durationSeconds.value >= 3600
|
||||
// ? printDurationWithHours(
|
||||
// Duration(seconds: _.durationSeconds.value))
|
||||
// : printDuration(
|
||||
// Duration(seconds: _.durationSeconds.value)),
|
||||
// style: textStyle,
|
||||
// ),
|
||||
// ),
|
||||
// const Spacer(),
|
||||
// // 倍速
|
||||
// // Obx(
|
||||
// // () => SizedBox(
|
||||
// // width: 45,
|
||||
// // height: 34,
|
||||
// // child: TextButton(
|
||||
// // style: ButtonStyle(
|
||||
// // padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
// // ),
|
||||
// // onPressed: () {
|
||||
// // _.togglePlaybackSpeed();
|
||||
// // },
|
||||
// // child: Text(
|
||||
// // '${_.playbackSpeed.toString()}X',
|
||||
// // style: textStyle,
|
||||
// // ),
|
||||
// // ),
|
||||
// // ),
|
||||
// // ),
|
||||
// SizedBox(
|
||||
// height: 30,
|
||||
// child: TextButton(
|
||||
// onPressed: () => _.toggleVideoFit(),
|
||||
// style: ButtonStyle(
|
||||
// padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
// ),
|
||||
// child: Obx(
|
||||
// () => Text(
|
||||
// _.videoFitDEsc.value,
|
||||
// style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(width: 10),
|
||||
// // 全屏
|
||||
// Obx(
|
||||
// () => ComBtn(
|
||||
// icon: Icon(
|
||||
// _.isFullScreen.value
|
||||
// ? FontAwesomeIcons.compress
|
||||
// : FontAwesomeIcons.expand,
|
||||
// size: 15,
|
||||
// color: Colors.white,
|
||||
// ),
|
||||
// fuc: () => triggerFullScreen!(),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
const SizedBox(height: 12),
|
||||
],
|
||||
),
|
||||
|
||||
@ -84,6 +84,7 @@ class SettingBoxKey {
|
||||
autoUpgradeEnable = 'autoUpgradeEnable',
|
||||
feedBackEnable = 'feedBackEnable',
|
||||
defaultVideoQa = 'defaultVideoQa',
|
||||
defaultLiveQa = 'defaultLiveQa',
|
||||
defaultAudioQa = 'defaultAudioQa',
|
||||
autoPlayEnable = 'autoPlayEnable',
|
||||
fullScreenMode = 'fullScreenMode',
|
||||
|
||||
Reference in New Issue
Block a user