diff --git a/lib/common/widgets/list_sheet.dart b/lib/common/widgets/list_sheet.dart index 381473bcf..2657e339a 100644 --- a/lib/common/widgets/list_sheet.dart +++ b/lib/common/widgets/list_sheet.dart @@ -275,145 +275,144 @@ class _ListSheetContentState extends State @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 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), + ), + ), + ], ), ); } diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index 8fdbf2266..123f4c64e 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -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.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; } diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 059201e5d..9c0ad64b0 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -126,8 +126,6 @@ class VideoDetailController extends GetxController PlayerStatus? playerStatus; StreamSubscription? 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 - 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 + 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 ?? - []; - 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 ?? + []; + 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(), ); }, ); diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 523afd6be..171e285d3 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -1803,14 +1803,7 @@ class _VideoDetailPageState extends State 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 }, ); 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 } 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((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((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 } 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; diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index d0f6b49b9..f660ed46f 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -99,702 +99,676 @@ class _HeaderControlState extends State { /// 设置面板 void showSettingSheet() { - showModalBottomSheet( - elevation: 0, - context: context, - isScrollControlled: true, - clipBehavior: Clip.hardEdge, - backgroundColor: Colors.transparent, - builder: (context) { - return Container( - width: double.infinity, - height: 500, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: EdgeInsets.only( - left: 12, - top: 12, - right: 12, - bottom: 12 + MediaQuery.paddingOf(context).bottom, - ), - child: Column( - children: [ - SizedBox( - height: 35, - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .onSecondaryContainer - .withOpacity(0.5), - borderRadius: - const BorderRadius.all(Radius.circular(3))), - ), + Utils.showFSSheet( + isFullScreen: isFullScreen, + child: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + child: Column( + children: [ + SizedBox( + height: 35, + child: Center( + child: Container( + width: 32, + height: 3, + decoration: BoxDecoration( + color: Theme.of(context) + .colorScheme + .onSecondaryContainer + .withOpacity(0.5), + borderRadius: const BorderRadius.all(Radius.circular(3))), ), ), - Expanded( - child: Material( - child: MediaQuery.removePadding( - context: context, - removeBottom: true, - child: ListView( - children: [ - // ListTile( - // onTap: () {}, - // dense: true, - // enabled: false, - // leading: - // const Icon(Icons.network_cell_outlined, size: 20), - // title: Text('省流模式', style: titleStyle), - // subtitle: Text('低画质 | 减少视频缓存', style: subTitleStyle), - // trailing: Transform.scale( - // scale: 0.75, - // child: Switch( - // thumbIcon: WidgetStateProperty.resolveWith( - // (Set states) { - // if (states.isNotEmpty && - // states.first == WidgetState.selected) { - // return const Icon(Icons.done); - // } - // return null; // All other states will use the default thumbIcon. - // }), - // value: false, - // onChanged: (value) => {}, - // ), - // ), - // ), - // if (widget.videoDetailCtr.userInfo != null) - ListTile( - dense: true, - onTap: () async { - Get.back(); - final res = await UserHttp.toViewLater( - bvid: widget.videoDetailCtr.bvid); - SmartDialog.showToast(res['msg']); - }, - leading: - const Icon(Icons.watch_later_outlined, size: 20), - title: const Text('添加至「稍后再看」', style: titleStyle), - ), - ListTile( - dense: true, - onTap: () => {Get.back(), scheduleExit()}, - leading: const Icon(Icons.hourglass_top_outlined, - size: 20), - title: const Text('定时关闭', style: titleStyle), - ), - ListTile( - dense: true, - onTap: () => { - Get.back(), - widget.videoDetailCtr.queryVideoUrl() - }, - leading: const Icon(Icons.refresh_outlined, size: 20), - title: const Text('重载视频', style: titleStyle), - ), - ListTile( - dense: true, - leading: const Icon( - Icons.stay_current_landscape_outlined, - size: 20), - title: Row( - children: [ - const Text( - '超分辨率', - strutStyle: StrutStyle(leading: 0, height: 1), - style: TextStyle( - height: 1, - fontSize: 14, - ), + ), + Expanded( + child: Material( + child: MediaQuery.removePadding( + context: context, + removeBottom: true, + child: ListView( + children: [ + // ListTile( + // onTap: () {}, + // dense: true, + // enabled: false, + // leading: + // const Icon(Icons.network_cell_outlined, size: 20), + // title: Text('省流模式', style: titleStyle), + // subtitle: Text('低画质 | 减少视频缓存', style: subTitleStyle), + // trailing: Transform.scale( + // scale: 0.75, + // child: Switch( + // thumbIcon: WidgetStateProperty.resolveWith( + // (Set states) { + // if (states.isNotEmpty && + // states.first == WidgetState.selected) { + // return const Icon(Icons.done); + // } + // return null; // All other states will use the default thumbIcon. + // }), + // value: false, + // onChanged: (value) => {}, + // ), + // ), + // ), + // if (widget.videoDetailCtr.userInfo != null) + ListTile( + dense: true, + onTap: () async { + Get.back(); + final res = await UserHttp.toViewLater( + bvid: widget.videoDetailCtr.bvid); + SmartDialog.showToast(res['msg']); + }, + leading: + const Icon(Icons.watch_later_outlined, size: 20), + title: const Text('添加至「稍后再看」', style: titleStyle), + ), + ListTile( + dense: true, + onTap: () => {Get.back(), scheduleExit()}, + leading: + const Icon(Icons.hourglass_top_outlined, size: 20), + title: const Text('定时关闭', style: titleStyle), + ), + ListTile( + dense: true, + onTap: () => + {Get.back(), widget.videoDetailCtr.queryVideoUrl()}, + leading: const Icon(Icons.refresh_outlined, size: 20), + title: const Text('重载视频', style: titleStyle), + ), + ListTile( + dense: true, + leading: const Icon( + Icons.stay_current_landscape_outlined, + size: 20), + title: Row( + children: [ + const Text( + '超分辨率', + strutStyle: StrutStyle(leading: 0, height: 1), + style: TextStyle( + height: 1, + fontSize: 14, ), - const SizedBox(width: 10), - Builder( - builder: (context) => PopupMenuButton( - initialValue: SuperResolutionType.values[ - widget.controller.superResolutionType], - child: Padding( - padding: const EdgeInsets.all(4), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - SuperResolutionType - .values[widget.controller - .superResolutionType] - .title, - strutStyle: - StrutStyle(leading: 0, height: 1), - style: TextStyle( - height: 1, - fontSize: 14, - color: Theme.of(context) - .colorScheme - .secondary, - ), - ), - Icon( - MdiIcons.unfoldMoreHorizontal, - size: MediaQuery.textScalerOf(context) - .scale(14), + ), + const SizedBox(width: 10), + Builder( + builder: (context) => PopupMenuButton( + initialValue: SuperResolutionType.values[ + widget.controller.superResolutionType], + child: Padding( + padding: const EdgeInsets.all(4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + SuperResolutionType + .values[widget + .controller.superResolutionType] + .title, + strutStyle: + StrutStyle(leading: 0, height: 1), + style: TextStyle( + height: 1, + fontSize: 14, color: Theme.of(context) .colorScheme .secondary, - ) - ], - ), + ), + ), + Icon( + MdiIcons.unfoldMoreHorizontal, + size: MediaQuery.textScalerOf(context) + .scale(14), + color: Theme.of(context) + .colorScheme + .secondary, + ) + ], ), - onSelected: (value) { - widget.controller.setShader(value.index); - if (context.mounted) { - (context as Element).markNeedsBuild(); - } + ), + onSelected: (value) { + widget.controller.setShader(value.index); + if (context.mounted) { + (context as Element).markNeedsBuild(); + } + }, + itemBuilder: (context) => + SuperResolutionType.values + .map((item) => PopupMenuItem( + value: item, + child: Text(item.title), + )) + .toList(), + ), + ), + ], + ), + ), + ListTile( + dense: true, + title: const Text('CDN 设置', style: titleStyle), + leading: Icon(MdiIcons.cloudPlusOutline, size: 20), + subtitle: Text( + '当前:${CDNServiceCode.fromCode(defaultCDNService)!.description},无法播放请切换', + style: subTitleStyle, + ), + onTap: () async { + Get.back(); + String? result = await showDialog( + context: context, + builder: (context) { + return SelectDialog( + title: 'CDN 设置', + value: defaultCDNService, + values: CDNService.values.map((e) { + return { + 'title': e.description, + 'value': e.code + }; + }).toList()); + }, + ); + if (result != null) { + defaultCDNService = result; + setting.put(SettingBoxKey.CDNService, result); + SmartDialog.showToast( + '已设置为 ${CDNServiceCode.fromCode(result)!.description},正在重载视频'); + setState(() {}); + widget.videoDetailCtr.queryVideoUrl(); + } + }, + ), + SelfSizedHorizontalList( + itemCount: 4, + gapSize: 10, + padding: const EdgeInsets.symmetric(horizontal: 16), + childBuilder: (index) { + return switch (index) { + 0 => Obx( + () => ActionRowLineItem( + iconData: Icons.flip, + onTap: () { + widget.controller.flipX.value = + !widget.controller.flipX.value; }, - itemBuilder: (context) => - SuperResolutionType.values - .map((item) => PopupMenuItem( - value: item, - child: Text(item.title), - )) - .toList(), + text: " 左右翻转 ", + selectStatus: widget.controller.flipX.value, ), ), - ], - ), - ), - ListTile( - dense: true, - title: const Text('CDN 设置', style: titleStyle), - leading: Icon(MdiIcons.cloudPlusOutline, size: 20), - subtitle: Text( - '当前:${CDNServiceCode.fromCode(defaultCDNService)!.description},无法播放请切换', - style: subTitleStyle, - ), - onTap: () async { - Get.back(); - String? result = await showDialog( - context: context, - builder: (context) { - return SelectDialog( - title: 'CDN 设置', - value: defaultCDNService, - values: CDNService.values.map((e) { - return { - 'title': e.description, - 'value': e.code - }; - }).toList()); - }, - ); - if (result != null) { - defaultCDNService = result; - setting.put(SettingBoxKey.CDNService, result); - SmartDialog.showToast( - '已设置为 ${CDNServiceCode.fromCode(result)!.description},正在重载视频'); - setState(() {}); - widget.videoDetailCtr.queryVideoUrl(); - } - }, - ), - SelfSizedHorizontalList( - itemCount: 4, - gapSize: 10, - padding: const EdgeInsets.symmetric(horizontal: 16), - childBuilder: (index) { - return switch (index) { - 0 => Obx( - () => ActionRowLineItem( - iconData: Icons.flip, - onTap: () { - widget.controller.flipX.value = - !widget.controller.flipX.value; - }, - text: " 左右翻转 ", - selectStatus: widget.controller.flipX.value, - ), - ), - 1 => Obx( - () => ActionRowLineItem( - icon: Transform.rotate( - angle: pi / 2, - child: Icon( - Icons.flip, - size: 13, - color: widget.controller.flipY.value - ? Theme.of(context) - .colorScheme - .onSecondaryContainer - : Theme.of(context) - .colorScheme - .outline, - ), + 1 => Obx( + () => ActionRowLineItem( + icon: Transform.rotate( + angle: pi / 2, + child: Icon( + Icons.flip, + size: 13, + color: widget.controller.flipY.value + ? Theme.of(context) + .colorScheme + .onSecondaryContainer + : Theme.of(context) + .colorScheme + .outline, ), - onTap: () { - widget.controller.flipY.value = - !widget.controller.flipY.value; - }, - text: " 上下翻转 ", - selectStatus: widget.controller.flipY.value, ), + onTap: () { + widget.controller.flipY.value = + !widget.controller.flipY.value; + }, + text: " 上下翻转 ", + selectStatus: widget.controller.flipY.value, ), - 2 => Obx( - () => ActionRowLineItem( - iconData: Icons.headphones, - onTap: widget.controller.setOnlyPlayAudio, - text: " 听视频 ", - selectStatus: - widget.controller.onlyPlayAudio.value, - ), + ), + 2 => Obx( + () => ActionRowLineItem( + iconData: Icons.headphones, + onTap: widget.controller.setOnlyPlayAudio, + text: " 听视频 ", + selectStatus: + widget.controller.onlyPlayAudio.value, ), - 3 => Obx( - () => ActionRowLineItem( - iconData: Icons.play_circle_outline, - onTap: widget - .controller.setContinuePlayInBackground, - text: " 后台播放 ", - selectStatus: widget.controller - .continuePlayInBackground.value, - ), + ), + 3 => Obx( + () => ActionRowLineItem( + iconData: Icons.play_circle_outline, + onTap: widget + .controller.setContinuePlayInBackground, + text: " 后台播放 ", + selectStatus: widget.controller + .continuePlayInBackground.value, ), - int() => throw UnimplementedError(), - }; - }, - ), + ), + int() => throw UnimplementedError(), + }; + }, + ), + ListTile( + dense: true, + onTap: () => {Get.back(), showSetVideoQa()}, + leading: + const Icon(Icons.play_circle_outline, size: 20), + title: const Text('选择画质', style: titleStyle), + subtitle: Text( + '当前画质 ${widget.videoDetailCtr.currentVideoQa.description}', + style: subTitleStyle), + ), + if (widget.videoDetailCtr.currentAudioQa != null) ListTile( dense: true, - onTap: () => {Get.back(), showSetVideoQa()}, - leading: - const Icon(Icons.play_circle_outline, size: 20), - title: const Text('选择画质', style: titleStyle), + onTap: () => {Get.back(), showSetAudioQa()}, + leading: const Icon(Icons.album_outlined, size: 20), + title: const Text('选择音质', style: titleStyle), subtitle: Text( - '当前画质 ${widget.videoDetailCtr.currentVideoQa.description}', + '当前音质 ${widget.videoDetailCtr.currentAudioQa!.description}', style: subTitleStyle), ), - if (widget.videoDetailCtr.currentAudioQa != null) - ListTile( - dense: true, - onTap: () => {Get.back(), showSetAudioQa()}, - leading: const Icon(Icons.album_outlined, size: 20), - title: const Text('选择音质', style: titleStyle), - subtitle: Text( - '当前音质 ${widget.videoDetailCtr.currentAudioQa!.description}', - style: subTitleStyle), - ), - ListTile( - dense: true, - onTap: () => {Get.back(), showSetDecodeFormats()}, - leading: - const Icon(Icons.av_timer_outlined, size: 20), - title: const Text('解码格式', style: titleStyle), - subtitle: Text( - '当前解码格式 ${widget.videoDetailCtr.currentDecodeFormats.description}', - style: subTitleStyle), - ), - ListTile( - dense: true, - onTap: () => {Get.back(), showSetRepeat()}, - leading: const Icon(Icons.repeat, size: 20), - title: const Text('播放顺序', style: titleStyle), - subtitle: Text( - widget.controller.playRepeat.description, - style: subTitleStyle), - ), - ListTile( - dense: true, - onTap: () => {Get.back(), showSetDanmaku()}, - leading: - const Icon(Icons.subtitles_outlined, size: 20), - title: const Text('弹幕/字幕设置', style: titleStyle), - ), - ListTile( - dense: true, - title: const Text('播放信息', style: titleStyle), - leading: const Icon(Icons.info_outline, size: 20), - onTap: () { - Player? player = - widget.controller.videoPlayerController; - if (player == null) { - SmartDialog.showToast('播放器未初始化'); - return; - } - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: const Text('播放信息'), - content: SizedBox( - width: double.maxFinite, - child: ListView( - children: [ - ListTile( + ListTile( + dense: true, + onTap: () => {Get.back(), showSetDecodeFormats()}, + leading: const Icon(Icons.av_timer_outlined, size: 20), + title: const Text('解码格式', style: titleStyle), + subtitle: Text( + '当前解码格式 ${widget.videoDetailCtr.currentDecodeFormats.description}', + style: subTitleStyle), + ), + ListTile( + dense: true, + onTap: () => {Get.back(), showSetRepeat()}, + leading: const Icon(Icons.repeat, size: 20), + title: const Text('播放顺序', style: titleStyle), + subtitle: Text(widget.controller.playRepeat.description, + style: subTitleStyle), + ), + ListTile( + dense: true, + onTap: () => {Get.back(), showSetDanmaku()}, + leading: const Icon(Icons.subtitles_outlined, size: 20), + title: const Text('弹幕/字幕设置', style: titleStyle), + ), + ListTile( + dense: true, + title: const Text('播放信息', style: titleStyle), + leading: const Icon(Icons.info_outline, size: 20), + onTap: () { + Player? player = + widget.controller.videoPlayerController; + if (player == null) { + SmartDialog.showToast('播放器未初始化'); + return; + } + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('播放信息'), + content: SizedBox( + width: double.maxFinite, + child: ListView( + children: [ + ListTile( + dense: true, + title: const Text("Resolution"), + subtitle: Text( + '${player.state.width}x${player.state.height}'), + onTap: () { + Utils.copyText( + 'Resolution\n${player.state.width}x${player.state.height}', + needToast: false, + ); + }, + ), + ListTile( + dense: true, + title: const Text("VideoParams"), + subtitle: Text(player.state.videoParams + .toString()), + onTap: () { + Utils.copyText( + 'VideoParams\n${player.state.videoParams}', + needToast: false, + ); + }, + ), + ListTile( + dense: true, + title: const Text("AudioParams"), + subtitle: Text(player.state.audioParams + .toString()), + onTap: () { + Utils.copyText( + 'AudioParams\n${player.state.audioParams}', + needToast: false, + ); + }, + ), + ListTile( + dense: true, + title: const Text("Media"), + subtitle: Text( + player.state.playlist.toString()), + onTap: () { + Utils.copyText( + 'Media\n${player.state.playlist}', + needToast: false, + ); + }, + ), + ListTile( + dense: true, + title: const Text("AudioTrack"), + subtitle: Text(player.state.track.audio + .toString()), + onTap: () { + Utils.copyText( + 'AudioTrack\n${player.state.track.audio}', + needToast: false, + ); + }, + ), + ListTile( + dense: true, + title: const Text("VideoTrack"), + subtitle: Text(player.state.track.video + .toString()), + onTap: () { + Utils.copyText( + 'VideoTrack\n${player.state.track.audio}', + needToast: false, + ); + }, + ), + ListTile( dense: true, - title: const Text("Resolution"), + title: const Text("pitch"), subtitle: Text( - '${player.state.width}x${player.state.height}'), + player.state.pitch.toString()), onTap: () { Utils.copyText( - 'Resolution\n${player.state.width}x${player.state.height}', + 'pitch\n${player.state.pitch}', needToast: false, ); - }, - ), - ListTile( + }), + ListTile( dense: true, - title: const Text("VideoParams"), - subtitle: Text(player - .state.videoParams - .toString()), - onTap: () { - Utils.copyText( - 'VideoParams\n${player.state.videoParams}', - needToast: false, - ); - }, - ), - ListTile( - dense: true, - title: const Text("AudioParams"), - subtitle: Text(player - .state.audioParams - .toString()), - onTap: () { - Utils.copyText( - 'AudioParams\n${player.state.audioParams}', - needToast: false, - ); - }, - ), - ListTile( - dense: true, - title: const Text("Media"), + title: const Text("rate"), subtitle: Text( - player.state.playlist.toString()), + player.state.rate.toString()), onTap: () { Utils.copyText( - 'Media\n${player.state.playlist}', + 'rate\n${player.state.rate}', needToast: false, ); - }, - ), - ListTile( - dense: true, - title: const Text("AudioTrack"), - subtitle: Text(player - .state.track.audio - .toString()), - onTap: () { - Utils.copyText( - 'AudioTrack\n${player.state.track.audio}', - needToast: false, - ); - }, - ), - ListTile( - dense: true, - title: const Text("VideoTrack"), - subtitle: Text(player - .state.track.video - .toString()), - onTap: () { - Utils.copyText( - 'VideoTrack\n${player.state.track.audio}', - needToast: false, - ); - }, - ), - ListTile( - dense: true, - title: const Text("pitch"), - subtitle: Text( - player.state.pitch.toString()), - onTap: () { - Utils.copyText( - 'pitch\n${player.state.pitch}', - needToast: false, - ); - }), - ListTile( - dense: true, - title: const Text("rate"), - subtitle: Text( - player.state.rate.toString()), - onTap: () { - Utils.copyText( - 'rate\n${player.state.rate}', - needToast: false, - ); - }), - ListTile( - dense: true, - title: const Text("AudioBitrate"), - subtitle: Text(player - .state.audioBitrate - .toString()), - onTap: () { - Utils.copyText( - 'AudioBitrate\n${player.state.audioBitrate}', - needToast: false, - ); - }, - ), - ListTile( - dense: true, - title: const Text("Volume"), - subtitle: Text( - player.state.volume.toString()), - onTap: () { - Utils.copyText( - 'Volume\n${player.state.volume}', - needToast: false, - ); - }, - ), - ], + }), + ListTile( + dense: true, + title: const Text("AudioBitrate"), + subtitle: Text(player.state.audioBitrate + .toString()), + onTap: () { + Utils.copyText( + 'AudioBitrate\n${player.state.audioBitrate}', + needToast: false, + ); + }, + ), + ListTile( + dense: true, + title: const Text("Volume"), + subtitle: Text( + player.state.volume.toString()), + onTap: () { + Utils.copyText( + 'Volume\n${player.state.volume}', + needToast: false, + ); + }, + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + '确定', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline), ), ), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: Text( - '确定', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .outline), - ), - ), - ], - ); - }, - ); - }, - ), - ListTile( - dense: true, - onTap: () { - if (widget.videoDetailCtr.userInfo == null) { - SmartDialog.showToast('账号未登录'); - return; - } - Get.back(); - Get.toNamed('/webview', parameters: { - 'url': - 'https://www.bilibili.com/appeal/?avid=${IdUtils.bv2av(widget.videoDetailCtr.bvid)}&bvid=${widget.videoDetailCtr.bvid}' - }); - }, - leading: const Icon(Icons.error_outline, size: 20), - title: const Text('举报', style: titleStyle), - ), - const SizedBox(height: 14), - ], - ), + ], + ); + }, + ); + }, + ), + ListTile( + dense: true, + onTap: () { + if (widget.videoDetailCtr.userInfo == null) { + SmartDialog.showToast('账号未登录'); + return; + } + Get.back(); + Get.toNamed('/webview', parameters: { + 'url': + 'https://www.bilibili.com/appeal/?avid=${IdUtils.bv2av(widget.videoDetailCtr.bvid)}&bvid=${widget.videoDetailCtr.bvid}' + }); + }, + leading: const Icon(Icons.error_outline, size: 20), + title: const Text('举报', style: titleStyle), + ), + const SizedBox(height: 14), + ], ), ), ), - ], - ), - ); - }, + ), + ], + ), + ), ); } /// 定时关闭 void scheduleExit() async { const List scheduleTimeChoices = [0, 15, 30, 45, 60]; - showModalBottomSheet( - context: context, - elevation: 0, - backgroundColor: Colors.transparent, - builder: (context) { - return StatefulBuilder( - builder: (context, setState) { - return Container( - width: double.infinity, - height: 500, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: EdgeInsets.only( - left: 12, - top: 12, - right: 12, - bottom: 12 + MediaQuery.paddingOf(context).bottom, - ), - padding: const EdgeInsets.only(left: 14, right: 14), - child: SingleChildScrollView( - child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 0, horizontal: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 30), - const Center(child: Text('定时关闭', style: titleStyle)), - const SizedBox(height: 10), + Utils.showFSSheet( + isFullScreen: isFullScreen, + child: StatefulBuilder( + builder: (context, setState) { + return Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + padding: const EdgeInsets.only(left: 14, right: 14), + child: SingleChildScrollView( + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 0, horizontal: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 30), + const Center(child: Text('定时关闭', style: titleStyle)), + const SizedBox(height: 10), + ...[ ...[ - ...[ - ...scheduleTimeChoices, - if (scheduleTimeChoices - .contains( - shutdownTimerService.scheduledExitInMinutes) - .not) - shutdownTimerService.scheduledExitInMinutes, - ]..sort(), - -1, - ].map( - (choice) => ListTile( - dense: true, - onTap: () { - if (choice == -1) { - showDialog( - context: context, - builder: (context) { - String duration = ''; - return AlertDialog( - title: const Text('自定义时长'), - content: TextField( - autofocus: true, - onChanged: (value) => duration = value, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.allow( - RegExp(r'\d+')), - ], - decoration: const InputDecoration( - suffixText: 'min'), - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .outline), - ), - ), - TextButton( - onPressed: () { - Get.back(); - int choice = - int.tryParse(duration) ?? 0; - shutdownTimerService - .scheduledExitInMinutes = choice; - shutdownTimerService - .startShutdownTimer(); - setState(() {}); - }, - child: Text('确定'), - ), - ], - ); - }, - ); - } else { - Get.back(); - shutdownTimerService.scheduledExitInMinutes = - choice; - shutdownTimerService.startShutdownTimer(); - } - }, - contentPadding: const EdgeInsets.only(), - title: Text(choice == -1 - ? '自定义' - : choice == 0 - ? "禁用" - : "$choice分钟后"), - trailing: shutdownTimerService - .scheduledExitInMinutes == - choice - ? Icon( - Icons.done, - color: Theme.of(context).colorScheme.primary, - ) - : null, - ), - ), - const SizedBox(height: 6), - const Center( - child: SizedBox( - width: 125, - child: Divider(height: 1), - ), - ), - const SizedBox(height: 10), - ListTile( + ...scheduleTimeChoices, + if (scheduleTimeChoices + .contains( + shutdownTimerService.scheduledExitInMinutes) + .not) + shutdownTimerService.scheduledExitInMinutes, + ]..sort(), + -1, + ].map( + (choice) => ListTile( dense: true, onTap: () { - shutdownTimerService.waitForPlayingCompleted = - !shutdownTimerService.waitForPlayingCompleted; - setState(() {}); + if (choice == -1) { + showDialog( + context: context, + builder: (context) { + String duration = ''; + return AlertDialog( + title: const Text('自定义时长'), + content: TextField( + autofocus: true, + onChanged: (value) => duration = value, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'\d+')), + ], + decoration: const InputDecoration( + suffixText: 'min'), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline), + ), + ), + TextButton( + onPressed: () { + Get.back(); + int choice = + int.tryParse(duration) ?? 0; + shutdownTimerService + .scheduledExitInMinutes = choice; + shutdownTimerService + .startShutdownTimer(); + setState(() {}); + }, + child: Text('确定'), + ), + ], + ); + }, + ); + } else { + Get.back(); + shutdownTimerService.scheduledExitInMinutes = + choice; + shutdownTimerService.startShutdownTimer(); + } }, contentPadding: const EdgeInsets.only(), - title: const Text("额外等待视频播放完毕", style: titleStyle), - trailing: Transform.scale( - alignment: Alignment - .centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大 - scale: 0.8, - child: Switch( - thumbIcon: WidgetStateProperty.resolveWith( - (Set states) { - if (states.isNotEmpty && - states.first == WidgetState.selected) { - return const Icon(Icons.done); - } - return null; - }), - value: shutdownTimerService.waitForPlayingCompleted, - onChanged: (value) => setState(() => - shutdownTimerService.waitForPlayingCompleted = - value), - ), + title: Text(choice == -1 + ? '自定义' + : choice == 0 + ? "禁用" + : "$choice分钟后"), + trailing: shutdownTimerService.scheduledExitInMinutes == + choice + ? Icon( + Icons.done, + color: Theme.of(context).colorScheme.primary, + ) + : null, + ), + ), + const SizedBox(height: 6), + const Center( + child: SizedBox( + width: 125, + child: Divider(height: 1), + ), + ), + const SizedBox(height: 10), + ListTile( + dense: true, + onTap: () { + shutdownTimerService.waitForPlayingCompleted = + !shutdownTimerService.waitForPlayingCompleted; + setState(() {}); + }, + contentPadding: const EdgeInsets.only(), + title: const Text("额外等待视频播放完毕", style: titleStyle), + trailing: Transform.scale( + alignment: Alignment + .centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大 + scale: 0.8, + child: Switch( + thumbIcon: WidgetStateProperty.resolveWith( + (Set states) { + if (states.isNotEmpty && + states.first == WidgetState.selected) { + return const Icon(Icons.done); + } + return null; + }), + value: shutdownTimerService.waitForPlayingCompleted, + onChanged: (value) => setState(() => + shutdownTimerService.waitForPlayingCompleted = + value), ), ), - const SizedBox(height: 10), - Row( - children: [ - const Text('倒计时结束:', style: titleStyle), - const Spacer(), - ActionRowLineItem( - onTap: () { - shutdownTimerService.exitApp = false; - setState(() {}); - // Get.back(); - }, - text: " 暂停视频 ", - selectStatus: !shutdownTimerService.exitApp, - ), - const Spacer(), - // const SizedBox(width: 10), - ActionRowLineItem( - onTap: () { - shutdownTimerService.exitApp = true; - setState(() {}); - // Get.back(); - }, - text: " 退出APP ", - selectStatus: shutdownTimerService.exitApp, - ) - ], - ), - const SizedBox(height: 10), - ], - ), + ), + const SizedBox(height: 10), + Row( + children: [ + const Text('倒计时结束:', style: titleStyle), + const Spacer(), + ActionRowLineItem( + onTap: () { + shutdownTimerService.exitApp = false; + setState(() {}); + // Get.back(); + }, + text: " 暂停视频 ", + selectStatus: !shutdownTimerService.exitApp, + ), + const Spacer(), + // const SizedBox(width: 10), + ActionRowLineItem( + onTap: () { + shutdownTimerService.exitApp = true; + setState(() {}); + // Get.back(); + }, + text: " 退出APP ", + selectStatus: shutdownTimerService.exitApp, + ) + ], + ), + const SizedBox(height: 10), + ], ), ), - ); - }, - ); - }, + ), + ); + }, + ), ); } @@ -822,182 +796,91 @@ class _HeaderControlState extends State { } } - showModalBottomSheet( - context: context, - elevation: 0, - backgroundColor: Colors.transparent, - builder: (BuildContext context) { - return Container( - width: double.infinity, - height: 310, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: EdgeInsets.only( - left: 12, - top: 12, - right: 12, - bottom: 12 + MediaQuery.paddingOf(context).bottom, - ), - child: Column( - children: [ - SizedBox( - height: 45, - child: GestureDetector( - onTap: () { - SmartDialog.showToast( - '标灰画质需要bilibili会员(已是会员?请关闭无痕模式);4k和杜比视界播放效果可能不佳'); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('选择画质', style: titleStyle), - SizedBox(width: buttonSpace), - Icon( - Icons.info_outline, - size: 16, - color: Theme.of(context).colorScheme.outline, - ) - ], - ), + Utils.showFSSheet( + isFullScreen: isFullScreen, + child: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + child: Column( + children: [ + SizedBox( + height: 45, + child: GestureDetector( + onTap: () { + SmartDialog.showToast( + '标灰画质需要bilibili会员(已是会员?请关闭无痕模式);4k和杜比视界播放效果可能不佳'); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('选择画质', style: titleStyle), + SizedBox(width: buttonSpace), + Icon( + Icons.info_outline, + size: 16, + color: Theme.of(context).colorScheme.outline, + ) + ], ), ), - Expanded( - child: Material( - child: Scrollbar( - child: MediaQuery.removePadding( - context: context, - removeBottom: true, - child: ListView( - children: [ - for (int i = 0; i < totalQaSam; i++) ...[ - ListTile( - dense: true, - onTap: () { - if (currentVideoQa.code == - videoFormat[i].quality) { - return; - } - Get.back(); - final int quality = videoFormat[i].quality!; - widget.videoDetailCtr.currentVideoQa = - VideoQualityCode.fromCode(quality)!; - widget.videoDetailCtr.updatePlayer(); - // String oldQualityDesc = - // VideoQualityCode.fromCode(setting.get( - // SettingBoxKey.defaultVideoQa, - // defaultValue: - // VideoQuality.values.last.code))! - // .description; - // setting.put( - // SettingBoxKey.defaultVideoQa, quality); - // SmartDialog.showToast( - // "默认画质由:$oldQualityDesc 变为:${VideoQualityCode.fromCode(quality)!.description}"); - }, - // 可能包含会员解锁画质 - enabled: i >= totalQaSam - userfulQaSam, - contentPadding: - const EdgeInsets.only(left: 20, right: 20), - title: Text(videoFormat[i].newDesc!), - trailing: currentVideoQa.code == - videoFormat[i].quality - ? Icon( - Icons.done, - color: - Theme.of(context).colorScheme.primary, - ) - : Text( - videoFormat[i].format!, - style: subTitleStyle, - ), - ), - ] - ], - ), - ), - ), - ), - ), - ], - ), - ); - }, - ); - } - - /// 选择音质 - void showSetAudioQa() { - final AudioQuality currentAudioQa = widget.videoDetailCtr.currentAudioQa!; - final List audio = videoInfo.dash!.audio!; - showModalBottomSheet( - context: context, - elevation: 0, - backgroundColor: Colors.transparent, - builder: (BuildContext context) { - return Container( - width: double.infinity, - height: 250, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: EdgeInsets.only( - left: 12, - top: 12, - right: 12, - bottom: 12 + MediaQuery.paddingOf(context).bottom, - ), - child: Column( - children: [ - const SizedBox( - height: 45, - child: Center(child: Text('选择音质', style: titleStyle))), - Expanded( - child: Material( + ), + Expanded( + child: Material( + child: Scrollbar( child: MediaQuery.removePadding( context: context, removeBottom: true, child: ListView( children: [ - for (final AudioItem i in audio) ...[ + for (int i = 0; i < totalQaSam; i++) ...[ ListTile( dense: true, onTap: () { - if (currentAudioQa.code == i.id) { + if (currentVideoQa.code == + videoFormat[i].quality) { return; } Get.back(); - final int quality = i.id!; - widget.videoDetailCtr.currentAudioQa = - AudioQualityCode.fromCode(quality)!; + final int quality = videoFormat[i].quality!; + widget.videoDetailCtr.currentVideoQa = + VideoQualityCode.fromCode(quality)!; widget.videoDetailCtr.updatePlayer(); - // String oldQualityDesc = AudioQualityCode.fromCode( - // setting.get(SettingBoxKey.defaultAudioQa, + // String oldQualityDesc = + // VideoQualityCode.fromCode(setting.get( + // SettingBoxKey.defaultVideoQa, // defaultValue: - // AudioQuality.values.last.code))! - // .description; + // VideoQuality.values.last.code))! + // .description; // setting.put( - // SettingBoxKey.defaultAudioQa, quality); + // SettingBoxKey.defaultVideoQa, quality); // SmartDialog.showToast( - // "默认音质由:$oldQualityDesc 变为:${AudioQualityCode.fromCode(quality)!.description}"); + // "默认画质由:$oldQualityDesc 变为:${VideoQualityCode.fromCode(quality)!.description}"); }, + // 可能包含会员解锁画质 + enabled: i >= totalQaSam - userfulQaSam, contentPadding: const EdgeInsets.only(left: 20, right: 20), - title: Text(i.quality!), - subtitle: Text( - i.codecs!, - style: subTitleStyle, - ), - trailing: currentAudioQa.code == i.id + title: Text(videoFormat[i].newDesc!), + trailing: currentVideoQa.code == + videoFormat[i].quality ? Icon( Icons.done, color: Theme.of(context).colorScheme.primary, ) - : const SizedBox(), + : Text( + videoFormat[i].format!, + style: subTitleStyle, + ), ), ] ], @@ -1005,10 +888,88 @@ class _HeaderControlState extends State { ), ), ), - ], - ), - ); - }, + ), + ], + ), + ), + ); + } + + /// 选择音质 + void showSetAudioQa() { + final AudioQuality currentAudioQa = widget.videoDetailCtr.currentAudioQa!; + final List audio = videoInfo.dash!.audio!; + Utils.showFSSheet( + isFullScreen: isFullScreen, + child: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + child: Column( + children: [ + const SizedBox( + height: 45, + child: Center(child: Text('选择音质', style: titleStyle))), + Expanded( + child: Material( + child: MediaQuery.removePadding( + context: context, + removeBottom: true, + child: ListView( + children: [ + for (final AudioItem i in audio) ...[ + ListTile( + dense: true, + onTap: () { + if (currentAudioQa.code == i.id) { + return; + } + Get.back(); + final int quality = i.id!; + widget.videoDetailCtr.currentAudioQa = + AudioQualityCode.fromCode(quality)!; + widget.videoDetailCtr.updatePlayer(); + // String oldQualityDesc = AudioQualityCode.fromCode( + // setting.get(SettingBoxKey.defaultAudioQa, + // defaultValue: + // AudioQuality.values.last.code))! + // .description; + // setting.put( + // SettingBoxKey.defaultAudioQa, quality); + // SmartDialog.showToast( + // "默认音质由:$oldQualityDesc 变为:${AudioQualityCode.fromCode(quality)!.description}"); + }, + contentPadding: + const EdgeInsets.only(left: 20, right: 20), + title: Text(i.quality!), + subtitle: Text( + i.codecs!, + style: subTitleStyle, + ), + trailing: currentAudioQa.code == i.id + ? Icon( + Icons.done, + color: Theme.of(context).colorScheme.primary, + ) + : const SizedBox(), + ), + ] + ], + ), + ), + ), + ), + ], + ), + ), ); } @@ -1028,75 +989,68 @@ class _HeaderControlState extends State { return; } - showModalBottomSheet( - context: context, - elevation: 0, - backgroundColor: Colors.transparent, - builder: (BuildContext context) { - return Container( - width: double.infinity, - height: 250, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: EdgeInsets.only( - left: 12, - top: 12, - right: 12, - bottom: 12 + MediaQuery.paddingOf(context).bottom, - ), - child: Column( - children: [ - const SizedBox( - height: 45, - child: Center(child: Text('选择解码格式', style: titleStyle))), - Expanded( - child: Material( - child: MediaQuery.removePadding( - context: context, - removeBottom: true, - child: ListView( - children: [ - for (var i in list) ...[ - ListTile( - dense: true, - onTap: () { - if (i.startsWith(currentDecodeFormats.code)) { - return; - } - widget.videoDetailCtr.currentDecodeFormats = - VideoDecodeFormatsCode.fromString(i)!; - widget.videoDetailCtr.updatePlayer(); - Get.back(); - }, - contentPadding: - const EdgeInsets.only(left: 20, right: 20), - title: Text(VideoDecodeFormatsCode.fromString(i)! - .description!), - subtitle: Text( - i!, - style: subTitleStyle, - ), - trailing: i.startsWith(currentDecodeFormats.code) - ? Icon( - Icons.done, - color: - Theme.of(context).colorScheme.primary, - ) - : const SizedBox(), + Utils.showFSSheet( + isFullScreen: isFullScreen, + child: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + child: Column( + children: [ + const SizedBox( + height: 45, + child: Center(child: Text('选择解码格式', style: titleStyle))), + Expanded( + child: Material( + child: MediaQuery.removePadding( + context: context, + removeBottom: true, + child: ListView( + children: [ + for (var i in list) ...[ + ListTile( + dense: true, + onTap: () { + if (i.startsWith(currentDecodeFormats.code)) { + return; + } + widget.videoDetailCtr.currentDecodeFormats = + VideoDecodeFormatsCode.fromString(i)!; + widget.videoDetailCtr.updatePlayer(); + Get.back(); + }, + contentPadding: + const EdgeInsets.only(left: 20, right: 20), + title: Text(VideoDecodeFormatsCode.fromString(i)! + .description!), + subtitle: Text( + i!, + style: subTitleStyle, ), - ] - ], - ), + trailing: i.startsWith(currentDecodeFormats.code) + ? Icon( + Icons.done, + color: Theme.of(context).colorScheme.primary, + ) + : const SizedBox(), + ), + ] + ], ), ), ), - ], - ), - ); - }, + ), + ], + ), + ), ); } @@ -1143,646 +1097,11 @@ class _HeaderControlState extends State { final DanmakuController? danmakuController = widget.controller.danmakuController; - await showModalBottomSheet( - context: context, - elevation: 0, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (BuildContext context) { - return StatefulBuilder( - builder: (BuildContext context, StateSetter setState) { - return Container( - width: double.infinity, - height: Get.height / 2, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: EdgeInsets.only( - left: 12, - top: 12, - right: 12, - bottom: (widget.controller.isFullScreen.value == true ? 70 : 12) + - MediaQuery.paddingOf(context).bottom, - ), - padding: const EdgeInsets.only(left: 14, right: 14), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 45, - child: Center(child: Text('弹幕/字幕设置', style: titleStyle)), - ), - const SizedBox(height: 10), - Row( - children: [ - Text('智能云屏蔽 $danmakuWeight 级'), - const Spacer(), - TextButton( - style: TextButton.styleFrom( - padding: EdgeInsets.zero, - minimumSize: Size.zero, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - onPressed: () => { - Get.back(), - Get.toNamed('/danmakuBlock', - arguments: widget.controller) - }, - child: Text( - "屏蔽管理(${widget.controller.danmakuFilterRule.length})")), - ], - ), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 10, - value: danmakuWeight.toDouble(), - divisions: 10, - label: '$danmakuWeight', - onChanged: (double val) { - danmakuWeight = val.toInt(); - widget.controller - ..danmakuWeight = danmakuWeight - ..putDanmakuSettings(); - setState(() {}); - }, - ), - ), - ), - const Text('按类型屏蔽'), - Padding( - padding: const EdgeInsets.only(top: 12, bottom: 18), - child: Row( - children: [ - for (final Map i - in blockTypesList) ...[ - ActionRowLineItem( - onTap: () async { - final bool isChoose = - blockTypes.contains(i['value']); - if (isChoose) { - blockTypes.remove(i['value']); - } else { - blockTypes.add(i['value']); - } - widget.controller - ..blockTypes = blockTypes - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option.copyWith( - hideTop: blockTypes.contains(5), - hideBottom: blockTypes.contains(4), - hideScroll: blockTypes.contains(2), - // 添加或修改其他需要修改的选项属性 - ), - ); - } catch (_) {} - }, - text: i['label'], - selectStatus: blockTypes.contains(i['value']), - ), - const SizedBox(width: 10), - ] - ], - ), - ), - const Text('显示区域'), - Padding( - padding: const EdgeInsets.only(top: 12), - child: Row( - children: [ - for (final Map i in showAreas) ...[ - ActionRowLineItem( - onTap: () { - showArea = i['value']; - widget.controller - ..showArea = showArea - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option - .copyWith(area: i['value']), - ); - } catch (_) {} - }, - text: i['label'], - selectStatus: showArea == i['value'], - ), - const SizedBox(width: 10), - ] - ], - ), - ), - SetSwitchItem( - title: '海量弹幕', - contentPadding: EdgeInsets.all(0), - titleStyle: TextStyle(fontSize: 14), - defaultVal: massiveMode, - setKey: SettingBoxKey.danmakuMassiveMode, - onChanged: (value) { - massiveMode = value; - widget.controller.massiveMode = value; - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option.copyWith(massiveMode: value), - ); - } catch (_) {} - }, - ), - Text('不透明度 ${opacityVal * 100}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 1, - value: opacityVal, - divisions: 10, - label: '${opacityVal * 100}%', - onChanged: (double val) { - opacityVal = val; - widget.controller - ..opacityVal = opacityVal - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option.copyWith(opacity: val), - ); - } catch (_) {} - }, - ), - ), - ), - Text('字体粗细 ${fontWeight + 1}(可能无法精确调节)'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 8, - value: fontWeight.toDouble(), - divisions: 8, - label: '${fontWeight + 1}', - onChanged: (double val) { - fontWeight = val.toInt(); - widget.controller - ..fontWeight = fontWeight - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option - .copyWith(fontWeight: fontWeight), - ); - } catch (_) {} - }, - ), - ), - ), - Text('描边粗细 $strokeWidth'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 3, - value: strokeWidth, - divisions: 6, - label: '$strokeWidth', - onChanged: (double val) { - strokeWidth = val; - widget.controller - ..strokeWidth = val - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option - .copyWith(strokeWidth: val), - ); - } catch (_) {} - }, - ), - ), - ), - Text('字体大小 ${(fontSizeVal * 100).toStringAsFixed(1)}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0.5, - max: 2.5, - value: fontSizeVal, - divisions: 20, - label: '${(fontSizeVal * 100).toStringAsFixed(1)}%', - onChanged: (double val) { - fontSizeVal = val; - widget.controller - ..fontSizeVal = fontSizeVal - ..putDanmakuSettings(); - setState(() {}); - if (widget.controller.isFullScreen.value == false) { - try { - danmakuController?.updateOption( - danmakuController.option.copyWith( - fontSize: (15 * fontSizeVal).toDouble(), - ), - ); - } catch (_) {} - } - }, - ), - ), - ), - Text('全屏字体大小 ${(fontSizeFSVal * 100).toStringAsFixed(1)}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0.5, - max: 2.5, - value: fontSizeFSVal, - divisions: 20, - label: '${(fontSizeFSVal * 100).toStringAsFixed(1)}%', - onChanged: (double val) { - fontSizeFSVal = val; - widget.controller - ..fontSizeFSVal = fontSizeFSVal - ..putDanmakuSettings(); - setState(() {}); - if (widget.controller.isFullScreen.value == true) { - try { - danmakuController?.updateOption( - danmakuController.option.copyWith( - fontSize: (15 * fontSizeFSVal).toDouble(), - ), - ); - } catch (_) {} - } - }, - ), - ), - ), - Text('弹幕时长 $danmakuDurationVal 秒'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 1, - max: 4, - value: pow(danmakuDurationVal, 1 / 4) as double, - divisions: 60, - label: danmakuDurationVal.toString(), - onChanged: (double val) { - danmakuDurationVal = - (pow(val, 4) as double).toPrecision(2); - widget.controller - ..danmakuDurationVal = danmakuDurationVal - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option.copyWith( - duration: danmakuDurationVal ~/ - widget.controller.playbackSpeed), - ); - } catch (_) {} - }, - ), - ), - ), - Text('弹幕行高 $danmakuLineHeight'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 1.0, - max: 3.0, - value: danmakuLineHeight, - // label: '$danmakuLineHeight', - onChanged: (double val) { - danmakuLineHeight = val.toPrecision(1); - widget.controller - ..danmakuLineHeight = danmakuLineHeight - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option.copyWith( - lineHeight: danmakuLineHeight, - ), - ); - } catch (_) {} - }, - ), - ), - ), - Text( - '字幕字体大小 ${(subtitleFontScale * 100).toStringAsFixed(1)}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0.5, - max: 2.5, - value: subtitleFontScale, - divisions: 20, - label: - '${(subtitleFontScale * 100).toStringAsFixed(1)}%', - onChanged: (double val) { - subtitleFontScale = val; - widget.controller - ..subtitleFontScale = subtitleFontScale - ..updateSubtitleStyle() - ..putDanmakuSettings(); - setState(() {}); - }, - ), - ), - ), - Text( - '全屏字幕字体大小 ${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0.5, - max: 2.5, - value: subtitleFontScaleFS, - divisions: 20, - label: - '${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%', - onChanged: (double val) { - subtitleFontScaleFS = val; - widget.controller - ..subtitleFontScaleFS = subtitleFontScaleFS - ..updateSubtitleStyle() - ..putDanmakuSettings(); - setState(() {}); - }, - ), - ), - ), - Text('字幕左右边距 $subtitlePaddingH'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 100, - value: subtitlePaddingH.toDouble(), - divisions: 100, - label: '$subtitlePaddingH', - onChanged: (double val) { - subtitlePaddingH = val.round(); - widget.controller - ..subtitlePaddingH = subtitlePaddingH - ..updateSubtitleStyle() - ..putDanmakuSettings(); - setState(() {}); - }, - ), - ), - ), - Text('字幕底部边距 $subtitlePaddingB'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 100, - value: subtitlePaddingB.toDouble(), - divisions: 100, - label: '$subtitlePaddingB', - onChanged: (double val) { - subtitlePaddingB = val.round(); - widget.controller - ..subtitlePaddingB = subtitlePaddingB - ..updateSubtitleStyle() - ..putDanmakuSettings(); - setState(() {}); - }, - ), - ), - ), - Text('字幕背景不透明度 ${(subtitleBgOpaticy * 100).toInt()}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 1, - value: subtitleBgOpaticy, - // label: '${(subtitleBgOpaticy * 100).toInt()}%', - onChanged: (double val) { - subtitleBgOpaticy = val.toPrecision(2); - widget.controller - ..subtitleBgOpaticy = subtitleBgOpaticy - ..updateSubtitleStyle() - ..putDanmakuSettings(); - setState(() {}); - }, - ), - ), - ), - ], - ), - ), - ); - }); - }, - ); - } - - /// 播放顺序 - void showSetRepeat() async { - showModalBottomSheet( - context: context, - elevation: 0, - backgroundColor: Colors.transparent, - builder: (BuildContext context) { + Utils.showFSSheet( + isFullScreen: isFullScreen, + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { return Container( - width: double.infinity, - height: 300, clipBehavior: Clip.hardEdge, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, @@ -1792,48 +1111,664 @@ class _HeaderControlState extends State { left: 12, top: 12, right: 12, - bottom: 12 + MediaQuery.paddingOf(context).bottom, + bottom: MediaQuery.paddingOf(context).bottom + 12, ), - child: Column( - children: [ - const SizedBox( + padding: const EdgeInsets.only(left: 14, right: 14), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( height: 45, - child: Center(child: Text('选择播放顺序', style: titleStyle))), - Expanded( - child: Material( - child: MediaQuery.removePadding( - context: context, - removeBottom: true, - child: ListView( - children: [ - for (final PlayRepeat i in PlayRepeat.values) ...[ - ListTile( - dense: true, - onTap: () { - widget.controller.setPlayRepeat(i); - Get.back(); + child: Center(child: Text('弹幕/字幕设置', style: titleStyle)), + ), + const SizedBox(height: 10), + Row( + children: [ + Text('智能云屏蔽 $danmakuWeight 级'), + const Spacer(), + TextButton( + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + onPressed: () => { + Get.back(), + Get.toNamed('/danmakuBlock', + arguments: widget.controller) }, - contentPadding: - const EdgeInsets.only(left: 20, right: 20), - title: Text(i.description), - trailing: widget.controller.playRepeat == i - ? Icon( - Icons.done, - color: - Theme.of(context).colorScheme.primary, - ) - : const SizedBox(), - ) - ], - ], + child: Text( + "屏蔽管理(${widget.controller.danmakuFilterRule.length})")), + ], + ), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 10, + value: danmakuWeight.toDouble(), + divisions: 10, + label: '$danmakuWeight', + onChanged: (double val) { + danmakuWeight = val.toInt(); + widget.controller + ..danmakuWeight = danmakuWeight + ..putDanmakuSettings(); + setState(() {}); + }, ), ), ), - ), - ], + const Text('按类型屏蔽'), + Padding( + padding: const EdgeInsets.only(top: 12, bottom: 18), + child: Row( + children: [ + for (final Map i in blockTypesList) ...[ + ActionRowLineItem( + onTap: () async { + final bool isChoose = + blockTypes.contains(i['value']); + if (isChoose) { + blockTypes.remove(i['value']); + } else { + blockTypes.add(i['value']); + } + widget.controller + ..blockTypes = blockTypes + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith( + hideTop: blockTypes.contains(5), + hideBottom: blockTypes.contains(4), + hideScroll: blockTypes.contains(2), + // 添加或修改其他需要修改的选项属性 + ), + ); + } catch (_) {} + }, + text: i['label'], + selectStatus: blockTypes.contains(i['value']), + ), + const SizedBox(width: 10), + ] + ], + ), + ), + const Text('显示区域'), + Padding( + padding: const EdgeInsets.only(top: 12), + child: Row( + children: [ + for (final Map i in showAreas) ...[ + ActionRowLineItem( + onTap: () { + showArea = i['value']; + widget.controller + ..showArea = showArea + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option + .copyWith(area: i['value']), + ); + } catch (_) {} + }, + text: i['label'], + selectStatus: showArea == i['value'], + ), + const SizedBox(width: 10), + ] + ], + ), + ), + SetSwitchItem( + title: '海量弹幕', + contentPadding: EdgeInsets.all(0), + titleStyle: TextStyle(fontSize: 14), + defaultVal: massiveMode, + setKey: SettingBoxKey.danmakuMassiveMode, + onChanged: (value) { + massiveMode = value; + widget.controller.massiveMode = value; + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith(massiveMode: value), + ); + } catch (_) {} + }, + ), + Text('不透明度 ${opacityVal * 100}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 1, + value: opacityVal, + divisions: 10, + label: '${opacityVal * 100}%', + onChanged: (double val) { + opacityVal = val; + widget.controller + ..opacityVal = opacityVal + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith(opacity: val), + ); + } catch (_) {} + }, + ), + ), + ), + Text('字体粗细 ${fontWeight + 1}(可能无法精确调节)'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 8, + value: fontWeight.toDouble(), + divisions: 8, + label: '${fontWeight + 1}', + onChanged: (double val) { + fontWeight = val.toInt(); + widget.controller + ..fontWeight = fontWeight + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option + .copyWith(fontWeight: fontWeight), + ); + } catch (_) {} + }, + ), + ), + ), + Text('描边粗细 $strokeWidth'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 3, + value: strokeWidth, + divisions: 6, + label: '$strokeWidth', + onChanged: (double val) { + strokeWidth = val; + widget.controller + ..strokeWidth = val + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith(strokeWidth: val), + ); + } catch (_) {} + }, + ), + ), + ), + Text('字体大小 ${(fontSizeVal * 100).toStringAsFixed(1)}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0.5, + max: 2.5, + value: fontSizeVal, + divisions: 20, + label: '${(fontSizeVal * 100).toStringAsFixed(1)}%', + onChanged: (double val) { + fontSizeVal = val; + widget.controller + ..fontSizeVal = fontSizeVal + ..putDanmakuSettings(); + setState(() {}); + if (widget.controller.isFullScreen.value == false) { + try { + danmakuController?.updateOption( + danmakuController.option.copyWith( + fontSize: (15 * fontSizeVal).toDouble(), + ), + ); + } catch (_) {} + } + }, + ), + ), + ), + Text('全屏字体大小 ${(fontSizeFSVal * 100).toStringAsFixed(1)}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0.5, + max: 2.5, + value: fontSizeFSVal, + divisions: 20, + label: '${(fontSizeFSVal * 100).toStringAsFixed(1)}%', + onChanged: (double val) { + fontSizeFSVal = val; + widget.controller + ..fontSizeFSVal = fontSizeFSVal + ..putDanmakuSettings(); + setState(() {}); + if (widget.controller.isFullScreen.value == true) { + try { + danmakuController?.updateOption( + danmakuController.option.copyWith( + fontSize: (15 * fontSizeFSVal).toDouble(), + ), + ); + } catch (_) {} + } + }, + ), + ), + ), + Text('弹幕时长 $danmakuDurationVal 秒'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 1, + max: 4, + value: pow(danmakuDurationVal, 1 / 4) as double, + divisions: 60, + label: danmakuDurationVal.toString(), + onChanged: (double val) { + danmakuDurationVal = + (pow(val, 4) as double).toPrecision(2); + widget.controller + ..danmakuDurationVal = danmakuDurationVal + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith( + duration: danmakuDurationVal ~/ + widget.controller.playbackSpeed), + ); + } catch (_) {} + }, + ), + ), + ), + Text('弹幕行高 $danmakuLineHeight'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 1.0, + max: 3.0, + value: danmakuLineHeight, + // label: '$danmakuLineHeight', + onChanged: (double val) { + danmakuLineHeight = val.toPrecision(1); + widget.controller + ..danmakuLineHeight = danmakuLineHeight + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith( + lineHeight: danmakuLineHeight, + ), + ); + } catch (_) {} + }, + ), + ), + ), + Text('字幕字体大小 ${(subtitleFontScale * 100).toStringAsFixed(1)}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0.5, + max: 2.5, + value: subtitleFontScale, + divisions: 20, + label: '${(subtitleFontScale * 100).toStringAsFixed(1)}%', + onChanged: (double val) { + subtitleFontScale = val; + widget.controller + ..subtitleFontScale = subtitleFontScale + ..updateSubtitleStyle() + ..putDanmakuSettings(); + setState(() {}); + }, + ), + ), + ), + Text( + '全屏字幕字体大小 ${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0.5, + max: 2.5, + value: subtitleFontScaleFS, + divisions: 20, + label: + '${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%', + onChanged: (double val) { + subtitleFontScaleFS = val; + widget.controller + ..subtitleFontScaleFS = subtitleFontScaleFS + ..updateSubtitleStyle() + ..putDanmakuSettings(); + setState(() {}); + }, + ), + ), + ), + Text('字幕左右边距 $subtitlePaddingH'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 100, + value: subtitlePaddingH.toDouble(), + divisions: 100, + label: '$subtitlePaddingH', + onChanged: (double val) { + subtitlePaddingH = val.round(); + widget.controller + ..subtitlePaddingH = subtitlePaddingH + ..updateSubtitleStyle() + ..putDanmakuSettings(); + setState(() {}); + }, + ), + ), + ), + Text('字幕底部边距 $subtitlePaddingB'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 100, + value: subtitlePaddingB.toDouble(), + divisions: 100, + label: '$subtitlePaddingB', + onChanged: (double val) { + subtitlePaddingB = val.round(); + widget.controller + ..subtitlePaddingB = subtitlePaddingB + ..updateSubtitleStyle() + ..putDanmakuSettings(); + setState(() {}); + }, + ), + ), + ), + Text('字幕背景不透明度 ${(subtitleBgOpaticy * 100).toInt()}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 1, + value: subtitleBgOpaticy, + // label: '${(subtitleBgOpaticy * 100).toInt()}%', + onChanged: (double val) { + subtitleBgOpaticy = val.toPrecision(2); + widget.controller + ..subtitleBgOpaticy = subtitleBgOpaticy + ..updateSubtitleStyle() + ..putDanmakuSettings(); + setState(() {}); + }, + ), + ), + ), + ], + ), ), ); - }, + }), + ); + } + + /// 播放顺序 + void showSetRepeat() async { + Utils.showFSSheet( + isFullScreen: isFullScreen, + child: Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + child: Column( + children: [ + const SizedBox( + height: 45, + child: Center(child: Text('选择播放顺序', style: titleStyle))), + Expanded( + child: Material( + child: MediaQuery.removePadding( + context: context, + removeBottom: true, + child: ListView( + children: [ + for (final PlayRepeat i in PlayRepeat.values) ...[ + ListTile( + dense: true, + onTap: () { + widget.controller.setPlayRepeat(i); + Get.back(); + }, + contentPadding: + const EdgeInsets.only(left: 20, right: 20), + title: Text(i.description), + trailing: widget.controller.playRepeat == i + ? Icon( + Icons.done, + color: Theme.of(context).colorScheme.primary, + ) + : const SizedBox(), + ) + ], + ], + ), + ), + ), + ), + ], + ), + ), ); } @@ -1874,10 +1809,7 @@ class _HeaderControlState extends State { color: Colors.white, ), onPressed: () { - if (widget.videoDetailCtr.bsController != null) { - widget.videoDetailCtr.bsController!.close(); - widget.videoDetailCtr.bsController = null; - } else if (isFullScreen) { + if (isFullScreen) { widget.controller.triggerFullScreen(status: false); } else if (MediaQuery.of(context).orientation == Orientation.landscape && @@ -2091,58 +2023,60 @@ class _HeaderControlState extends State { // }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Column(children: [ - const Row( - children: [ - Icon( - Icons.check, - color: Colors.green, - ), - SizedBox(width: 10), - Text('画中画', - style: - TextStyle(fontSize: 15, height: 1.5)) - ], - ), - const SizedBox(height: 10), - const Text( - '建议开启【后台音频服务】\n' - '避免画中画没有暂停按钮', - style: - TextStyle(fontSize: 12.5, height: 1.5)), - Row(children: [ - TextButton( - style: ButtonStyle( - foregroundColor: - WidgetStateProperty.resolveWith( - (states) { - return Theme.of(context) - .snackBarTheme - .actionTextColor; - }), + content: Column( + children: [ + const Row( + children: [ + Icon( + Icons.check, + color: Colors.green, ), - onPressed: () async { - plPlayerController - .setBackgroundPlay(true); - SmartDialog.showToast("请重新载入本页面刷新"); - // Get.back(); - }, - child: const Text('启用后台音频服务')), - const SizedBox(width: 10), - TextButton( - style: ButtonStyle( - foregroundColor: - WidgetStateProperty.resolveWith( - (states) { - return Theme.of(context) - .snackBarTheme - .actionTextColor; - }), - ), - onPressed: () {}, - child: const Text('不启用')) - ]) - ]), + SizedBox(width: 10), + Text('画中画', + style: TextStyle( + fontSize: 15, height: 1.5)) + ], + ), + const SizedBox(height: 10), + const Text( + '建议开启【后台音频服务】\n' + '避免画中画没有暂停按钮', + style: + TextStyle(fontSize: 12.5, height: 1.5)), + Row(children: [ + TextButton( + style: ButtonStyle( + foregroundColor: + WidgetStateProperty.resolveWith( + (states) { + return Theme.of(context) + .snackBarTheme + .actionTextColor; + }), + ), + onPressed: () async { + plPlayerController + .setBackgroundPlay(true); + SmartDialog.showToast("请重新载入本页面刷新"); + // Get.back(); + }, + child: const Text('启用后台音频服务')), + const SizedBox(width: 10), + TextButton( + style: ButtonStyle( + foregroundColor: + WidgetStateProperty.resolveWith( + (states) { + return Theme.of(context) + .snackBarTheme + .actionTextColor; + }), + ), + onPressed: () {}, + child: const Text('不启用')) + ]) + ], + ), duration: const Duration(seconds: 2), showCloseIcon: true, ), diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 8502306c1..1bb962dac 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -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) { diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 1eb0f0bff..3486f5746 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -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);