refa: video bottom sheet

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-02-15 21:01:48 +08:00
parent 84ed34f3a7
commit 2ff84857e7
7 changed files with 2194 additions and 2263 deletions

View File

@@ -275,145 +275,144 @@ class _ListSheetContentState extends State<ListSheetContent>
@override
Widget build(BuildContext context) {
return Material(
return ColoredBox(
color: Theme.of(context).colorScheme.surface,
child: SizedBox(
height: Utils.getSheetHeight(context),
child: Column(
children: [
Container(
height: 45,
padding: EdgeInsets.symmetric(
horizontal: widget.showTitle != false ? 14 : 6),
child: Row(
children: [
if (widget.showTitle != false)
Text(
'合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
style: Theme.of(context).textTheme.titleMedium,
),
StreamBuilder(
stream: _favStream?.stream,
builder: (context, snapshot) => snapshot.hasData
? mediumButton(
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
icon: _seasonFav == 1
? Icons.notifications_off_outlined
: Icons.notifications_active_outlined,
onPressed: () async {
dynamic result = await VideoHttp.seasonFav(
isFav: _seasonFav == 1,
seasonId: widget.season.id,
);
if (result['status']) {
SmartDialog.showToast(
'${_seasonFav == 1 ? '取消' : ''}订阅成功');
_seasonFav = _seasonFav == 1 ? 0 : 1;
_favStream?.add(_seasonFav);
} else {
SmartDialog.showToast(result['msg']);
}
},
)
: const SizedBox.shrink(),
child: Column(
children: [
Container(
height: 45,
padding: EdgeInsets.symmetric(
horizontal: widget.showTitle != false ? 14 : 6),
child: Row(
children: [
if (widget.showTitle != false)
Text(
'合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
style: Theme.of(context).textTheme.titleMedium,
),
mediumButton(
tooltip: '跳至顶部',
icon: Icons.vertical_align_top,
onPressed: () {
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0]
? 0
: _isList
? widget.season.sections[_ctr?.index].episodes
.length -
1
: episodes.length - 1,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
mediumButton(
tooltip: '跳至底部',
icon: Icons.vertical_align_bottom,
onPressed: () {
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0]
? _isList
? widget.season.sections[_ctr?.index].episodes
.length -
1
: episodes.length - 1
: 0,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
mediumButton(
tooltip: '跳至当前',
icon: Icons.my_location,
onPressed: () async {
if (_ctr != null && _ctr?.index != (_index)) {
_ctr?.animateTo(_index);
await Future.delayed(const Duration(milliseconds: 225));
}
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: currentIndex,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
if (widget.isSupportReverse == true)
if (!_isList)
_reverseButton
else
StreamBuilder(
stream: _indexStream?.stream,
initialData: _index,
builder: (context, snapshot) {
return snapshot.data == _index
? _reverseButton
: const SizedBox.shrink();
},
),
const Spacer(),
StreamBuilder(
stream: _indexStream?.stream,
initialData: _index,
builder: (context, snapshot) => mediumButton(
tooltip: reverse[snapshot.data] ? '顺序' : '倒序',
icon: !reverse[snapshot.data]
? MdiIcons.sortNumericAscending
: MdiIcons.sortNumericDescending,
onPressed: () {
setState(() {
reverse[_ctr?.index ?? 0] =
!reverse[_ctr?.index ?? 0];
});
StreamBuilder(
stream: _favStream?.stream,
builder: (context, snapshot) => snapshot.hasData
? mediumButton(
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
icon: _seasonFav == 1
? Icons.notifications_off_outlined
: Icons.notifications_active_outlined,
onPressed: () async {
dynamic result = await VideoHttp.seasonFav(
isFav: _seasonFav == 1,
seasonId: widget.season.id,
);
if (result['status']) {
SmartDialog.showToast(
'${_seasonFav == 1 ? '取消' : ''}订阅成功');
_seasonFav = _seasonFav == 1 ? 0 : 1;
_favStream?.add(_seasonFav);
} else {
SmartDialog.showToast(result['msg']);
}
},
)
: const SizedBox.shrink(),
),
mediumButton(
tooltip: '跳至顶部',
icon: Icons.vertical_align_top,
onPressed: () {
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0]
? 0
: _isList
? widget.season.sections[_ctr?.index].episodes
.length -
1
: episodes.length - 1,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
mediumButton(
tooltip: '跳至底部',
icon: Icons.vertical_align_bottom,
onPressed: () {
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: !reverse[_ctr?.index ?? 0]
? _isList
? widget.season.sections[_ctr?.index].episodes
.length -
1
: episodes.length - 1
: 0,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
mediumButton(
tooltip: '跳至当前',
icon: Icons.my_location,
onPressed: () async {
if (_ctr != null && _ctr?.index != (_index)) {
_ctr?.animateTo(_index);
await Future.delayed(const Duration(milliseconds: 225));
}
try {
itemScrollController[_ctr?.index ?? 0].scrollTo(
index: currentIndex,
duration: const Duration(milliseconds: 200),
);
} catch (_) {}
},
),
if (widget.isSupportReverse == true)
if (!_isList)
_reverseButton
else
StreamBuilder(
stream: _indexStream?.stream,
initialData: _index,
builder: (context, snapshot) {
return snapshot.data == _index
? _reverseButton
: const SizedBox.shrink();
},
),
const Spacer(),
StreamBuilder(
stream: _indexStream?.stream,
initialData: _index,
builder: (context, snapshot) => mediumButton(
tooltip: reverse[snapshot.data] ? '顺序' : '倒序',
icon: !reverse[snapshot.data]
? MdiIcons.sortNumericAscending
: MdiIcons.sortNumericDescending,
onPressed: () {
setState(() {
reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0];
});
},
),
if (widget.onClose != null)
mediumButton(
tooltip: '关闭',
icon: Icons.close,
onPressed: widget.onClose,
),
],
),
),
if (widget.onClose != null)
mediumButton(
tooltip: '关闭',
icon: Icons.close,
onPressed: widget.onClose,
),
],
),
Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
if (_isList)
TabBar(
),
Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
if (_isList)
Material(
color: Theme.of(context).colorScheme.surface,
child: TabBar(
controller: _ctr,
padding: const EdgeInsets.only(right: 60),
isScrollable: true,
@@ -423,20 +422,26 @@ class _ListSheetContentState extends State<ListSheetContent>
dividerHeight: 1,
dividerColor: Theme.of(context).dividerColor.withOpacity(0.1),
),
Expanded(
child: _isList
? TabBarView(
),
Expanded(
child: _isList
? Material(
color: Theme.of(context).colorScheme.surface,
child: TabBarView(
controller: _ctr,
children: List.generate(
widget.season.sections.length,
(index) => _buildBody(
index, widget.season.sections[index].episodes),
),
)
: _buildBody(null, episodes),
),
],
),
),
)
: Material(
color: Theme.of(context).colorScheme.surface,
child: _buildBody(null, episodes),
),
),
],
),
);
}

View File

@@ -3,6 +3,7 @@ import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/models/user/fav_detail.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
@@ -35,23 +36,24 @@ class FavDetailController extends MultiSelectController {
@override
bool customHandleResponse(Success response) {
FavDetailData data = response.response;
if (currentPage == 1) {
item.value = response.response.info;
isOwner.value = response.response.info.mid == mid;
item.value = data.info ?? FavFolderItemData();
isOwner.value = data.info?.mid == mid;
}
if (response.response.medias.isEmpty) {
if (data.medias.isNullOrEmpty) {
isEnd = true;
}
if (currentPage != 1 && loadingState.value is Success) {
response.response.medias?.insertAll(
data.medias?.insertAll(
0,
List<FavDetailItemData>.from((loadingState.value as Success).response),
);
}
if (response.response.medias.length >= response.response.info.mediaCount) {
if ((data.medias?.length ?? 0) >= (data.info?.mediaCount ?? 0)) {
isEnd = true;
}
loadingState.value = LoadingState.success(response.response.medias);
loadingState.value = LoadingState.success(data.medias);
return true;
}

View File

@@ -126,8 +126,6 @@ class VideoDetailController extends GetxController
PlayerStatus? playerStatus;
StreamSubscription<Duration>? positionSubscription;
PersistentBottomSheetController? bsController;
bool imageStatus = false;
void onViewImage() {
@@ -1229,9 +1227,9 @@ class VideoDetailController extends GetxController
);
}
if (plPlayerController.isFullScreen.value) {
bsController = scaffoldKey.currentState?.showBottomSheet(
enableDrag: false,
(context) => _postPanel(false),
Utils.showFSSheet(
child: _postPanel(),
isFullScreen: plPlayerController.isFullScreen.value,
);
} else {
childKey.currentState?.showBottomSheet(
@@ -1241,7 +1239,7 @@ class VideoDetailController extends GetxController
}
}
Widget _postPanel([bool isChild = true]) => StatefulBuilder(
Widget _postPanel() => StatefulBuilder(
builder: (context, setState) {
void updateSegment({
required bool isFirst,
@@ -1361,378 +1359,358 @@ class VideoDetailController extends GetxController
];
}
return SizedBox(
height: isChild ? null : Utils.getSheetHeight(context),
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
automaticallyImplyLeading: false,
titleSpacing: 16,
title: const Text('提交片段'),
actions: [
iconButton(
context: context,
tooltip: '添加片段',
onPressed: () {
setState(() {
list?.insert(
0,
PostSegmentModel(
segment: Pair(
first: 0,
second: plPlayerController.positionSeconds.value,
),
category: SegmentType.sponsor,
actionType: ActionType.skip,
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
automaticallyImplyLeading: false,
titleSpacing: 16,
title: const Text('提交片段'),
actions: [
iconButton(
context: context,
tooltip: '添加片段',
onPressed: () {
setState(() {
list?.insert(
0,
PostSegmentModel(
segment: Pair(
first: 0,
second: plPlayerController.positionSeconds.value,
),
);
});
},
icon: Icons.add,
),
const SizedBox(width: 10),
iconButton(
context: context,
tooltip: '关闭',
onPressed: () {
if (bsController != null) {
bsController!.close();
bsController = null;
} else {
Get.back();
}
},
icon: Icons.close,
),
const SizedBox(width: 16),
],
),
body: list?.isNotEmpty == true
? Stack(
children: [
SingleChildScrollView(
child: Column(
children: [
...List.generate(
list!.length,
(index) => Stack(
children: [
Container(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 5,
),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (list![index].actionType !=
ActionType.full) ...[
Row(
children: [
...segmentWidget(
isFirst: true,
index: index,
),
if (list![index].category !=
SegmentType
.poi_highlight) ...[
const SizedBox(width: 16),
...segmentWidget(
isFirst: false,
index: index,
),
],
],
),
const SizedBox(height: 8),
],
category: SegmentType.sponsor,
actionType: ActionType.skip,
),
);
});
},
icon: Icons.add,
),
const SizedBox(width: 10),
iconButton(
context: context,
tooltip: '关闭',
onPressed: Get.back,
icon: Icons.close,
),
const SizedBox(width: 16),
],
),
body: list?.isNotEmpty == true
? Stack(
children: [
SingleChildScrollView(
child: Column(
children: [
...List.generate(
list!.length,
(index) => Stack(
children: [
Container(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 5,
),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (list![index].actionType !=
ActionType.full) ...[
Row(
children: [
const Text('分类: '),
PopupMenuButton(
initialValue:
list![index].category,
onSelected: (item) async {
list![index].category = item;
List<ActionType>
constraintList =
_segmentType2ActionType(
item);
if (constraintList
.contains(list![index]
.actionType)
.not) {
list![index].actionType =
constraintList.first;
}
switch (item) {
case SegmentType
.poi_highlight:
updateSegment(
isFirst: false,
index: index,
value: list![index]
.segment
.first,
);
break;
case SegmentType
.exclusive_access:
updateSegment(
isFirst: true,
index: index,
value: 0,
);
break;
case _:
}
setState(() {});
},
itemBuilder: (context) =>
SegmentType.values
.map((item) =>
PopupMenuItem<
SegmentType>(
value: item,
child: Text(
item.title),
))
.toList(),
child: Row(
mainAxisSize:
MainAxisSize.min,
children: [
Text(
list![index]
.category
.title,
style: TextStyle(
height: 1,
fontSize: 14,
color: Theme.of(context)
.colorScheme
.secondary,
),
strutStyle: StrutStyle(
height: 1,
leading: 0,
),
),
Icon(
MdiIcons
.unfoldMoreHorizontal,
size: MediaQuery
.textScalerOf(
context)
.scale(14),
color: Theme.of(context)
.colorScheme
.secondary,
),
],
),
...segmentWidget(
isFirst: true,
index: index,
),
const SizedBox(width: 16),
const Text('行为类别: '),
PopupMenuButton(
initialValue:
list![index].actionType,
onSelected: (item) async {
if (list![index].category !=
SegmentType
.poi_highlight) ...[
const SizedBox(width: 16),
...segmentWidget(
isFirst: false,
index: index,
),
],
],
),
const SizedBox(height: 8),
],
Row(
children: [
const Text('分类: '),
PopupMenuButton(
initialValue:
list![index].category,
onSelected: (item) async {
list![index].category = item;
List<ActionType>
constraintList =
_segmentType2ActionType(
item);
if (constraintList
.contains(
list![index].actionType)
.not) {
list![index].actionType =
item;
if (item == ActionType.full) {
constraintList.first;
}
switch (item) {
case SegmentType
.poi_highlight:
updateSegment(
isFirst: false,
index: index,
value: list![index]
.segment
.first,
);
break;
case SegmentType
.exclusive_access:
updateSegment(
isFirst: true,
index: index,
value: 0,
);
}
setState(() {});
},
itemBuilder: (context) =>
ActionType.values
.map(
(item) =>
PopupMenuItem<
ActionType>(
enabled: _segmentType2ActionType(
list![index]
.category)
.contains(item),
break;
case _:
}
setState(() {});
},
itemBuilder: (context) =>
SegmentType.values
.map((item) =>
PopupMenuItem<
SegmentType>(
value: item,
child: Text(
item.title),
),
)
.toList(),
child: Row(
mainAxisSize:
MainAxisSize.min,
children: [
Text(
list![index]
.actionType
.title,
style: TextStyle(
height: 1,
fontSize: 14,
color: Theme.of(context)
.colorScheme
.secondary,
),
strutStyle: StrutStyle(
height: 1,
leading: 0,
),
),
Icon(
MdiIcons
.unfoldMoreHorizontal,
size: MediaQuery
.textScalerOf(
context)
.scale(14),
))
.toList(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
list![index].category.title,
style: TextStyle(
height: 1,
fontSize: 14,
color: Theme.of(context)
.colorScheme
.secondary,
),
],
),
strutStyle: StrutStyle(
height: 1,
leading: 0,
),
),
Icon(
MdiIcons
.unfoldMoreHorizontal,
size:
MediaQuery.textScalerOf(
context)
.scale(14),
color: Theme.of(context)
.colorScheme
.secondary,
),
],
),
],
),
],
),
),
Positioned(
top: 10,
right: 21,
child: iconButton(
context: context,
size: 26,
tooltip: '移除',
icon: Icons.clear,
onPressed: () {
setState(() {
list!.removeAt(index);
});
},
),
),
],
),
),
SizedBox(
height:
88 + MediaQuery.paddingOf(context).bottom,
),
],
),
),
Positioned(
right: 16,
bottom: 16 + MediaQuery.paddingOf(context).bottom,
child: FloatingActionButton(
tooltip: '提交',
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确定无误再提交'),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.outline,
),
const SizedBox(width: 16),
const Text('行为类别: '),
PopupMenuButton(
initialValue:
list![index].actionType,
onSelected: (item) async {
list![index].actionType = item;
if (item == ActionType.full) {
updateSegment(
isFirst: true,
index: index,
value: 0,
);
}
setState(() {});
},
itemBuilder: (context) =>
ActionType.values
.map(
(item) => PopupMenuItem<
ActionType>(
enabled: _segmentType2ActionType(
list![index]
.category)
.contains(item),
value: item,
child:
Text(item.title),
),
)
.toList(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
list![index]
.actionType
.title,
style: TextStyle(
height: 1,
fontSize: 14,
color: Theme.of(context)
.colorScheme
.secondary,
),
strutStyle: StrutStyle(
height: 1,
leading: 0,
),
),
Icon(
MdiIcons
.unfoldMoreHorizontal,
size:
MediaQuery.textScalerOf(
context)
.scale(14),
color: Theme.of(context)
.colorScheme
.secondary,
),
],
),
),
],
),
],
),
),
Positioned(
top: 10,
right: 21,
child: iconButton(
context: context,
size: 26,
tooltip: '移除',
icon: Icons.clear,
onPressed: () {
setState(() {
list!.removeAt(index);
});
},
),
),
],
),
),
SizedBox(
height: 88 + MediaQuery.paddingOf(context).bottom,
),
],
),
),
Positioned(
right: 16,
bottom: 16 + MediaQuery.paddingOf(context).bottom,
child: FloatingActionButton(
tooltip: '提交',
onPressed: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确定无误再提交'),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.outline,
),
),
TextButton(
onPressed: () {
Get.back();
Request()
.post(
'${GStorage.blockServer}/api/skipSegments',
queryParameters: {
'videoID': bvid,
'cid': cid.value,
'userID': GStorage.blockUserID,
'userAgent': Constants.userAgent,
'videoDuration': plPlayerController
.durationSeconds
.value
.inSeconds,
},
data: {
'segments': list!
.map(
(item) => {
'segment': [
item.segment.first,
item.segment.second,
],
'category':
item.category.name,
'actionType':
item.actionType.name,
},
)
.toList(),
},
options: _options,
)
.then(
(res) {
if (res.statusCode == 200) {
Get.back();
SmartDialog.showToast('提交成功');
list?.clear();
_handleSBData(res);
plPlayerController
.segmentList.value =
_segmentProgressList ??
<Segment>[];
if (positionSubscription ==
null) {
_initSkip();
}
} else {
SmartDialog.showToast(
'提交失败: ${{
400: '参数错误',
403: '被自动审核机制拒绝',
429: '重复提交太快',
409: '重复提交'
}[res.statusCode]}',
);
),
TextButton(
onPressed: () {
Get.back();
Request()
.post(
'${GStorage.blockServer}/api/skipSegments',
queryParameters: {
'videoID': bvid,
'cid': cid.value,
'userID': GStorage.blockUserID,
'userAgent': Constants.userAgent,
'videoDuration': plPlayerController
.durationSeconds.value.inSeconds,
},
data: {
'segments': list!
.map(
(item) => {
'segment': [
item.segment.first,
item.segment.second,
],
'category':
item.category.name,
'actionType':
item.actionType.name,
},
)
.toList(),
},
options: _options,
)
.then(
(res) {
if (res.statusCode == 200) {
Get.back();
SmartDialog.showToast('提交成功');
list?.clear();
_handleSBData(res);
plPlayerController
.segmentList.value =
_segmentProgressList ??
<Segment>[];
if (positionSubscription == null) {
_initSkip();
}
},
);
},
child: const Text('确定提交'),
),
],
),
);
},
child: Icon(Icons.check),
),
)
],
)
: errorWidget(),
),
} else {
SmartDialog.showToast(
'提交失败: ${{
400: '参数错误',
403: '被自动审核机制拒绝',
429: '重复提交太快',
409: '重复提交'
}[res.statusCode]}',
);
}
},
);
},
child: const Text('确定提交'),
),
],
),
);
},
child: Icon(Icons.check),
),
)
],
)
: errorWidget(),
);
},
);

View File

@@ -1803,14 +1803,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
videoDetailController.videoType == SearchType.media_bangumi
? bangumiIntroController.changeSeasonOrbangu
: videoIntroController.changeSeasonOrbangu,
onClose: () {
if (videoDetailController.bsController != null) {
videoDetailController.bsController!.close();
videoDetailController.bsController = null;
} else {
Get.back();
}
},
onClose: Get.back,
onReverse: () {
Get.back();
onReversePlay(
@@ -1821,10 +1814,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
},
);
if (isFullScreen) {
videoDetailController.bsController =
videoDetailController.scaffoldKey.currentState?.showBottomSheet(
(context) => listSheetContent(),
);
Utils.showFSSheet(child: listSheetContent(), isFullScreen: isFullScreen);
} else {
videoDetailController.childKey.currentState?.showBottomSheet(
(context) => listSheetContent(),
@@ -1903,157 +1893,138 @@ class _VideoDetailPageState extends State<VideoDetailPage>
}
void showViewPoints() {
Widget listSheetContent(context, [bool isFS = false]) {
Widget listSheetContent() {
int currentIndex = -1;
return StatefulBuilder(
builder: (context, setState) => SizedBox(
height: isFS ? Utils.getSheetHeight(context) : null,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
titleSpacing: 16,
title: const Text('分段信息'),
actions: [
Text(
'分段进度条',
style: TextStyle(fontSize: 16),
),
Obx(
() => Transform.scale(
alignment: Alignment.centerLeft,
scale: 0.8,
child: Switch(
thumbIcon:
WidgetStateProperty.resolveWith<Icon?>((states) {
if (states.isNotEmpty &&
states.first == WidgetState.selected) {
return const Icon(Icons.done);
}
return null;
}),
value:
videoDetailController.plPlayerController.showVP.value,
onChanged: (value) {
videoDetailController.plPlayerController.showVP.value =
value;
},
),
builder: (context, setState) => Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
titleSpacing: 16,
title: const Text('分段信息'),
actions: [
Text(
'分段进度条',
style: TextStyle(fontSize: 16),
),
Obx(
() => Transform.scale(
alignment: Alignment.centerLeft,
scale: 0.8,
child: Switch(
thumbIcon: WidgetStateProperty.resolveWith<Icon?>((states) {
if (states.isNotEmpty &&
states.first == WidgetState.selected) {
return const Icon(Icons.done);
}
return null;
}),
value:
videoDetailController.plPlayerController.showVP.value,
onChanged: (value) {
videoDetailController.plPlayerController.showVP.value =
value;
},
),
),
iconButton(
context: context,
size: 30,
icon: Icons.clear,
tooltip: '关闭',
onPressed: () {
if (videoDetailController.bsController != null) {
videoDetailController.bsController!.close();
videoDetailController.bsController = null;
} else {
Get.back();
}
},
),
const SizedBox(width: 16),
],
),
body: SingleChildScrollView(
child: Column(
children: [
...List.generate(
videoDetailController.viewPointList.length * 2 - 1,
(rawIndex) {
if (rawIndex % 2 == 1) {
return Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
);
}
int index = rawIndex ~/ 2;
Segment segment =
videoDetailController.viewPointList[index];
if (currentIndex == -1 &&
segment.from != null &&
segment.to != null) {
if (videoDetailController
.plPlayerController.positionSeconds.value >=
segment.from! &&
videoDetailController
.plPlayerController.positionSeconds.value <
segment.to!) {
currentIndex = index;
}
}
return ListTile(
dense: true,
onTap: segment.from != null
? () {
currentIndex = index;
plPlayerController?.danmakuController?.clear();
plPlayerController?.videoPlayerController
?.seek(Duration(seconds: segment.from!));
if (videoDetailController.bsController != null) {
videoDetailController.bsController!.close();
videoDetailController.bsController = null;
} else {
Get.back();
// setState(() {});
}
}
: null,
leading: segment.url?.isNotEmpty == true
? Container(
margin: const EdgeInsets.symmetric(vertical: 6),
decoration: currentIndex == index
? BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(
width: 1.8,
strokeAlign:
BorderSide.strokeAlignOutside,
color: Theme.of(context)
.colorScheme
.primary,
),
)
: null,
child: LayoutBuilder(
builder: (context, constraints) =>
NetworkImgLayer(
radius: 6,
src: segment.url,
width: constraints.maxHeight *
StyleString.aspectRatio,
height: constraints.maxHeight,
),
),
)
: null,
title: Text(
segment.title ?? '',
style: TextStyle(
fontSize: 14,
fontWeight:
currentIndex == index ? FontWeight.bold : null,
color: currentIndex == index
? Theme.of(context).colorScheme.primary
: null,
),
),
subtitle: Text(
'${segment.from != null ? Utils.timeFormat(segment.from) : ''} - ${segment.to != null ? Utils.timeFormat(segment.to) : ''}',
style: TextStyle(
fontSize: 13,
color: currentIndex == index
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
),
),
);
}),
SizedBox(height: 25 + MediaQuery.paddingOf(context).bottom),
],
),
iconButton(
context: context,
size: 30,
icon: Icons.clear,
tooltip: '关闭',
onPressed: Get.back,
),
const SizedBox(width: 16),
],
),
body: SingleChildScrollView(
child: Column(
children: [
...List.generate(
videoDetailController.viewPointList.length * 2 - 1,
(rawIndex) {
if (rawIndex % 2 == 1) {
return Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
);
}
int index = rawIndex ~/ 2;
Segment segment = videoDetailController.viewPointList[index];
if (currentIndex == -1 &&
segment.from != null &&
segment.to != null) {
if (videoDetailController
.plPlayerController.positionSeconds.value >=
segment.from! &&
videoDetailController
.plPlayerController.positionSeconds.value <
segment.to!) {
currentIndex = index;
}
}
return ListTile(
dense: true,
onTap: segment.from != null
? () {
currentIndex = index;
plPlayerController?.danmakuController?.clear();
plPlayerController?.videoPlayerController
?.seek(Duration(seconds: segment.from!));
Get.back();
}
: null,
leading: segment.url?.isNotEmpty == true
? Container(
margin: const EdgeInsets.symmetric(vertical: 6),
decoration: currentIndex == index
? BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(
width: 1.8,
strokeAlign:
BorderSide.strokeAlignOutside,
color:
Theme.of(context).colorScheme.primary,
),
)
: null,
child: LayoutBuilder(
builder: (context, constraints) =>
NetworkImgLayer(
radius: 6,
src: segment.url,
width: constraints.maxHeight *
StyleString.aspectRatio,
height: constraints.maxHeight,
),
),
)
: null,
title: Text(
segment.title ?? '',
style: TextStyle(
fontSize: 14,
fontWeight:
currentIndex == index ? FontWeight.bold : null,
color: currentIndex == index
? Theme.of(context).colorScheme.primary
: null,
),
),
subtitle: Text(
'${segment.from != null ? Utils.timeFormat(segment.from) : ''} - ${segment.to != null ? Utils.timeFormat(segment.to) : ''}',
style: TextStyle(
fontSize: 13,
color: currentIndex == index
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
),
),
);
}),
SizedBox(height: 25 + MediaQuery.paddingOf(context).bottom),
],
),
),
),
@@ -2061,23 +2032,15 @@ class _VideoDetailPageState extends State<VideoDetailPage>
}
if (isFullScreen) {
videoDetailController.bsController =
videoDetailController.scaffoldKey.currentState?.showBottomSheet(
(context) => listSheetContent(context, true),
);
Utils.showFSSheet(child: listSheetContent(), isFullScreen: isFullScreen);
} else {
videoDetailController.childKey.currentState?.showBottomSheet(
(context) => listSheetContent(context),
(context) => listSheetContent(),
);
}
}
void _onPopInvokedWithResult(didPop, result) {
if (videoDetailController.bsController != null) {
videoDetailController.bsController!.close();
videoDetailController.bsController = null;
return;
}
if (plPlayerController?.controlsLock.value == true) {
plPlayerController?.onLockControl(false);
return;

File diff suppressed because it is too large Load Diff

View File

@@ -1750,7 +1750,9 @@ Widget buildViewPointWidget(
return item.start >= seg;
}).reduce((a, b) => a.start < b.start ? a : b);
if (item.from != null) {
plPlayerController.seekTo(Duration(seconds: item.from!));
plPlayerController.danmakuController?.clear();
plPlayerController.videoPlayerController
?.seek(Duration(seconds: item.from!));
}
// debugPrint('${item.title},,${item.from}');
} catch (e) {

View File

@@ -33,6 +33,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:get/get_navigation/src/dialog/dialog_route.dart';
import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:html/dom.dart' as dom;
@@ -44,6 +45,52 @@ class Utils {
static const channel = MethodChannel("PiliPlus");
static void showFSSheet({required Widget child, required bool isFullScreen}) {
Navigator.of(Get.context!).push(
GetDialogRoute(
pageBuilder: (buildContext, animation, secondaryAnimation) {
return MediaQuery.orientationOf(Get.context!) == Orientation.portrait
? isFullScreen
? Column(
children: [
const Spacer(),
Expanded(child: child),
],
)
: Column(
children: [
const Spacer(),
ConstrainedBox(
constraints:
BoxConstraints(maxHeight: Get.height * 0.7),
child: child,
),
],
)
: Row(
children: [
const Spacer(),
Expanded(child: child),
],
);
},
transitionDuration: const Duration(milliseconds: 400),
transitionBuilder: (context, animation, secondaryAnimation, child) {
Offset begin =
MediaQuery.orientationOf(Get.context!) == Orientation.portrait
? Offset(0.0, 1.0)
: Offset(1.0, 0.0);
var tween = Tween(begin: begin, end: Offset.zero)
.chain(CurveTween(curve: Curves.linear));
return SlideTransition(
position: animation.drive(tween),
child: child,
);
},
),
);
}
static darkenTheme(ThemeData themeData) {
// return themeData;
Color color = themeData.colorScheme.surfaceContainerHighest.darken(0.7);