Compare commits

..

7 Commits

10 changed files with 278 additions and 171 deletions

View File

@ -9,6 +9,7 @@ PODS:
- Flutter - Flutter
- connectivity_plus (0.0.1): - connectivity_plus (0.0.1):
- Flutter - Flutter
- FlutterMacOS
- ReachabilitySwift - ReachabilitySwift
- device_info_plus (0.0.1): - device_info_plus (0.0.1):
- Flutter - Flutter
@ -38,7 +39,7 @@ PODS:
- path_provider_foundation (0.0.1): - path_provider_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- permission_handler_apple (9.1.1): - permission_handler_apple (9.3.0):
- Flutter - Flutter
- ReachabilitySwift (5.0.0) - ReachabilitySwift (5.0.0)
- saver_gallery (0.0.1): - saver_gallery (0.0.1):
@ -71,7 +72,7 @@ DEPENDENCIES:
- audio_service (from `.symlinks/plugins/audio_service/ios`) - audio_service (from `.symlinks/plugins/audio_service/ios`)
- audio_session (from `.symlinks/plugins/audio_session/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`)
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`) - auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`) - flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
@ -113,7 +114,7 @@ EXTERNAL SOURCES:
auto_orientation: auto_orientation:
:path: ".symlinks/plugins/auto_orientation/ios" :path: ".symlinks/plugins/auto_orientation/ios"
connectivity_plus: connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios" :path: ".symlinks/plugins/connectivity_plus/darwin"
device_info_plus: device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios" :path: ".symlinks/plugins/device_info_plus/ios"
Flutter: Flutter:
@ -166,7 +167,7 @@ SPEC CHECKSUMS:
audio_service: f509d65da41b9521a61f1c404dd58651f265a567 audio_service: f509d65da41b9521a61f1c404dd58651f265a567
audio_session: 4f3e461722055d21515cf3261b64c973c062f345 audio_session: 4f3e461722055d21515cf3261b64c973c062f345
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a connectivity_plus: e2dad488011aeb593e219360e804c43cc1af5770
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83 flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
@ -180,7 +181,7 @@ SPEC CHECKSUMS:
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78 saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
@ -193,7 +194,7 @@ SPEC CHECKSUMS:
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9 volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7 webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a webview_flutter_wkwebview: 4f3e50f7273d31e5500066ed267e3ae4309c5ae4
PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be

View File

@ -1,3 +1,4 @@
import 'package:expandable/expandable.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -15,6 +16,7 @@ import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import '../../../../http/user.dart';
import 'widgets/action_item.dart'; import 'widgets/action_item.dart';
import 'widgets/fav_panel.dart'; import 'widgets/fav_panel.dart';
import 'widgets/intro_detail.dart'; import 'widgets/intro_detail.dart';
@ -137,6 +139,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
late String memberHeroTag; late String memberHeroTag;
late bool enableAi; late bool enableAi;
bool isProcessing = false; bool isProcessing = false;
RxBool isExpand = false.obs;
late ExpandableController _expandableCtr;
void Function()? handleState(Future Function() action) { void Function()? handleState(Future Function() action) {
return isProcessing return isProcessing
? null ? null
@ -160,6 +165,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
follower = Utils.numFormat(videoIntroController.userStat['follower']); follower = Utils.numFormat(videoIntroController.userStat['follower']);
followStatus = videoIntroController.followStatus; followStatus = videoIntroController.followStatus;
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true); enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
_expandableCtr = ExpandableController(initialExpanded: false);
} }
// 收藏 // 收藏
@ -212,13 +218,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
// 视频介绍 // 视频介绍
showIntroDetail() { showIntroDetail() {
feedBack(); feedBack();
showBottomSheet( isExpand.value = !(isExpand.value);
context: context, _expandableCtr.toggle();
enableDrag: true,
builder: (BuildContext context) {
return IntroDetail(videoDetail: widget.videoDetail!);
},
);
} }
// 用户主页 // 用户主页
@ -242,6 +243,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
); );
} }
@override
void dispose() {
_expandableCtr.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ThemeData t = Theme.of(context); final ThemeData t = Theme.of(context);
@ -259,14 +266,34 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
GestureDetector( GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () => showIntroDetail(), onTap: () => showIntroDetail(),
child: Text( child: ExpandablePanel(
widget.videoDetail!.title!, controller: _expandableCtr,
style: const TextStyle( collapsed: Text(
fontSize: 18, widget.videoDetail!.title!,
fontWeight: FontWeight.bold, softWrap: true,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
expanded: Text(
widget.videoDetail!.title!,
softWrap: true,
maxLines: 4,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
theme: const ExpandableThemeData(
animationDuration: Duration(milliseconds: 300),
scrollAnimationDuration: Duration(milliseconds: 300),
crossFadePoint: 0,
fadeCurve: Curves.ease,
sizeCurve: Curves.linear,
), ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
), ),
Stack( Stack(
@ -330,6 +357,20 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
], ],
), ),
/// 视频简介
ExpandablePanel(
controller: _expandableCtr,
collapsed: const SizedBox(height: 0),
expanded: IntroDetail(videoDetail: widget.videoDetail!),
theme: const ExpandableThemeData(
animationDuration: Duration(milliseconds: 300),
scrollAnimationDuration: Duration(milliseconds: 300),
crossFadePoint: 0,
fadeCurve: Curves.ease,
sizeCurve: Curves.linear,
),
),
/// 点赞收藏转发 /// 点赞收藏转发
actionGrid(context, videoIntroController), actionGrid(context, videoIntroController),
// 合集 // 合集
@ -438,6 +479,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
margin: const EdgeInsets.only(top: 6, bottom: 4), margin: const EdgeInsets.only(top: 6, bottom: 4),
height: constraints.maxWidth / 5 * 0.8, height: constraints.maxWidth / 5 * 0.8,
child: GridView.count( child: GridView.count(
physics: const NeverScrollableScrollPhysics(),
primary: false, primary: false,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
crossAxisCount: 5, crossAxisCount: 5,
@ -451,12 +493,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
selectStatus: videoIntroController.hasLike.value, selectStatus: videoIntroController.hasLike.value,
text: widget.videoDetail!.stat!.like!.toString()), text: widget.videoDetail!.stat!.like!.toString()),
), ),
// ActionItem(
// icon: const Icon(FontAwesomeIcons.clock),
// onTap: () => videoIntroController.actionShareVideo(),
// selectStatus: false,
// loadingStatus: loadingStatus,
// text: '稍后再看'),
Obx( Obx(
() => ActionItem( () => ActionItem(
icon: const Icon(FontAwesomeIcons.b), icon: const Icon(FontAwesomeIcons.b),
@ -477,10 +513,14 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
), ),
), ),
ActionItem( ActionItem(
icon: const Icon(FontAwesomeIcons.comment), icon: const Icon(FontAwesomeIcons.clock),
onTap: () => videoDetailCtr.tabCtr.animateTo(1), onTap: () async {
final res =
await UserHttp.toViewLater(bvid: widget.videoDetail!.bvid);
SmartDialog.showToast(res['msg']);
},
selectStatus: false, selectStatus: false,
text: widget.videoDetail!.stat!.reply!.toString(), text: '稍后看',
), ),
ActionItem( ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare), icon: const Icon(FontAwesomeIcons.shareFromSquare),

View File

@ -1,16 +1,10 @@
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
Box localCache = GStrorage.localCache;
late double sheetHeight;
class IntroDetail extends StatelessWidget { class IntroDetail extends StatelessWidget {
const IntroDetail({ const IntroDetail({
super.key, super.key,
@ -20,105 +14,39 @@ class IntroDetail extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
sheetHeight = localCache.get('sheetHeight'); return SizedBox(
return Container( width: double.infinity,
color: Theme.of(context).colorScheme.background, child: SelectableRegion(
padding: EdgeInsets.only( focusNode: FocusNode(),
left: 14, selectionControls: MaterialTextSelectionControls(),
right: 14,
bottom: MediaQuery.of(context).padding.bottom + 20),
height: sheetHeight,
child: Column( child: Column(
children: [ crossAxisAlignment: CrossAxisAlignment.start,
InkWell( children: <Widget>[
onTap: () => Get.back(), const SizedBox(height: 4),
child: Container( GestureDetector(
height: 35, onTap: () {
padding: const EdgeInsets.only(bottom: 2), Clipboard.setData(ClipboardData(text: videoDetail!.bvid!));
child: Center( SmartDialog.showToast('已复制');
child: Container( },
width: 32, child: Text(
height: 3, videoDetail!.bvid!,
decoration: BoxDecoration( style: TextStyle(
color: Theme.of(context).colorScheme.primary, fontSize: 13, color: Theme.of(context).colorScheme.primary),
borderRadius:
const BorderRadius.all(Radius.circular(3))),
),
),
), ),
), ),
Expanded( const SizedBox(height: 4),
child: SingleChildScrollView( Text.rich(
child: Column( style: const TextStyle(height: 1.4),
crossAxisAlignment: CrossAxisAlignment.start, TextSpan(
children: [ children: [
Text( buildContent(context, videoDetail!),
videoDetail!.title, ],
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 6),
Row(
children: [
StatView(
theme: 'gray',
view: videoDetail!.stat!.view,
size: 'medium',
),
const SizedBox(width: 10),
StatDanMu(
theme: 'gray',
danmu: videoDetail!.stat!.danmaku,
size: 'medium',
),
const SizedBox(width: 10),
Text(
Utils.dateFormat(videoDetail!.pubdate,
formatType: 'detail'),
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.outline,
),
),
],
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: SelectableRegion(
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
videoDetail!.bvid!,
style: const TextStyle(fontSize: 13),
),
const SizedBox(height: 4),
Text.rich(
style: const TextStyle(
height: 1.4,
// fontSize: 13,
),
TextSpan(
children: [
buildContent(context, videoDetail!),
],
),
),
],
),
),
),
],
),
), ),
) ),
], ],
)); ),
),
);
} }
InlineSpan buildContent(BuildContext context, content) { InlineSpan buildContent(BuildContext context, content) {

View File

@ -148,14 +148,35 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
floating: true, floating: true,
delegate: _MySliverPersistentHeaderDelegate( delegate: _MySliverPersistentHeaderDelegate(
child: Container( child: Container(
height: 40, height: 45,
padding: const EdgeInsets.fromLTRB(12, 6, 6, 0), padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background,
border: Border(
bottom: BorderSide(
color: Theme.of(context)
.colorScheme
.outline
.withOpacity(0.1)),
),
),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Obx(
'${_videoReplyController.sortTypeLabel.value}评论', () => AnimatedSwitcher(
style: const TextStyle(fontSize: 13), duration: const Duration(milliseconds: 400),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Text(
'${_videoReplyController.count.value}条回复',
key: ValueKey<int>(
_videoReplyController.count.value),
),
),
), ),
SizedBox( SizedBox(
height: 35, height: 35,
@ -163,12 +184,10 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
onPressed: () => onPressed: () =>
_videoReplyController.queryBySort(), _videoReplyController.queryBySort(),
icon: const Icon(Icons.sort, size: 16), icon: const Icon(Icons.sort, size: 16),
label: Obx( label: Obx(() => Text(
() => Text( _videoReplyController.sortTypeLabel.value,
_videoReplyController.sortTypeLabel.value, style: const TextStyle(fontSize: 13),
style: const TextStyle(fontSize: 13), )),
),
),
), ),
) )
], ],
@ -310,8 +329,8 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
class _MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { class _MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
_MySliverPersistentHeaderDelegate({required this.child}); _MySliverPersistentHeaderDelegate({required this.child});
final double _minExtent = 40; final double _minExtent = 45;
final double _maxExtent = 40; final double _maxExtent = 45;
final Widget child; final Widget child;
@override @override

View File

@ -368,10 +368,13 @@ class _VideoDetailPageState extends State<VideoDetailPage>
!(plPlayerController?.isOpenDanmu.value ?? !(plPlayerController?.isOpenDanmu.value ??
false); false);
}, },
icon: (plPlayerController?.isOpenDanmu.value ?? icon: !(plPlayerController?.isOpenDanmu.value ??
false) false)
? SvgPicture.asset( ? SvgPicture.asset(
'assets/images/video/danmu_close.svg', 'assets/images/video/danmu_close.svg',
// ignore: deprecated_member_use
color:
Theme.of(context).colorScheme.outline,
) )
: SvgPicture.asset( : SvgPicture.asset(
'assets/images/video/danmu_open.svg', 'assets/images/video/danmu_open.svg',

View File

@ -17,12 +17,16 @@ class ScrollAppBar extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final double statusBarHeight = MediaQuery.of(context).padding.top; final double statusBarHeight = MediaQuery.of(context).padding.top;
final videoHeight = MediaQuery.sizeOf(context).width * 9 / 16; final videoHeight = MediaQuery.sizeOf(context).width * 9 / 16;
double scrollDistance = scrollVal;
if (scrollVal > videoHeight - kToolbarHeight) {
scrollDistance = videoHeight - kToolbarHeight;
}
return Positioned( return Positioned(
top: -videoHeight + scrollVal + kToolbarHeight + 0.5, top: -videoHeight + scrollDistance + kToolbarHeight + 0.5,
left: 0, left: 0,
right: 0, right: 0,
child: Opacity( child: Opacity(
opacity: scrollVal / (videoHeight - kToolbarHeight), opacity: scrollDistance / (videoHeight - kToolbarHeight),
child: Container( child: Container(
height: statusBarHeight + kToolbarHeight, height: statusBarHeight + kToolbarHeight,
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,

View File

@ -32,28 +32,14 @@ class _ExpandedSectionState extends State<ExpandedSection>
_runExpandCheck(); _runExpandCheck();
} }
///Setting up the animation
// void prepareAnimations() {
// expandController = AnimationController(
// vsync: this, duration: const Duration(milliseconds: 500));
// animation = CurvedAnimation(
// parent: expandController,
// curve: Curves.fastOutSlowIn,
// );
// }
void prepareAnimations() { void prepareAnimations() {
expandController = AnimationController( expandController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 400)); vsync: this, duration: const Duration(milliseconds: 400));
Animation<double> curve = CurvedAnimation( Animation<double> curve = CurvedAnimation(
parent: expandController, parent: expandController,
curve: Curves.fastOutSlowIn, curve: Curves.linear,
); );
animation = Tween(begin: widget.begin, end: widget.end).animate(curve); animation = Tween(begin: widget.begin, end: widget.end).animate(curve);
// animation = CurvedAnimation(
// parent: expandController,
// curve: Curves.fastOutSlowIn,
// );
} }
void _runExpandCheck() { void _runExpandCheck() {
@ -67,7 +53,9 @@ class _ExpandedSectionState extends State<ExpandedSection>
@override @override
void didUpdateWidget(ExpandedSection oldWidget) { void didUpdateWidget(ExpandedSection oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
_runExpandCheck(); if (widget.expand != oldWidget.expand) {
_runExpandCheck();
}
} }
@override @override

View File

@ -17,6 +17,7 @@ import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/plugin/pl_player/models/play_repeat.dart'; import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/services/shutdown_timer_service.dart'; import 'package:pilipala/services/shutdown_timer_service.dart';
import '../../../../http/danmaku.dart';
import '../../../../models/common/search_type.dart'; import '../../../../models/common/search_type.dart';
import '../../../../models/video_detail_res.dart'; import '../../../../models/video_detail_res.dart';
import '../introduction/index.dart'; import '../introduction/index.dart';
@ -52,7 +53,7 @@ class _HeaderControlState extends State<HeaderControl> {
final Box<dynamic> videoStorage = GStrorage.video; final Box<dynamic> videoStorage = GStrorage.video;
late List<double> speedsList; late List<double> speedsList;
double buttonSpace = 8; double buttonSpace = 8;
bool showTitle = false; RxBool isFullScreen = false.obs;
late String heroTag; late String heroTag;
late VideoIntroController videoIntroController; late VideoIntroController videoIntroController;
late VideoDetailData videoDetail; late VideoDetailData videoDetail;
@ -69,13 +70,8 @@ class _HeaderControlState extends State<HeaderControl> {
} }
void fullScreenStatusListener() { void fullScreenStatusListener() {
widget.videoDetailCtr!.plPlayerController.isFullScreen widget.videoDetailCtr!.plPlayerController.isFullScreen.listen((bool val) {
.listen((bool isFullScreen) { isFullScreen.value = val;
if (isFullScreen) {
showTitle = true;
} else {
showTitle = false;
}
/// TODO setState() called after dispose() /// TODO setState() called after dispose()
if (mounted) { if (mounted) {
@ -218,6 +214,87 @@ class _HeaderControlState extends State<HeaderControl> {
); );
} }
/// 发送弹幕
void showShootDanmakuSheet() {
final TextEditingController textController = TextEditingController();
bool isSending = false; // 追踪是否正在发送
showDialog(
context: Get.context!,
builder: (BuildContext context) {
// TODO: 支持更多类型和颜色的弹幕
return AlertDialog(
title: const Text('发送弹幕(测试)'),
content: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return TextField(
controller: textController,
);
}),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return TextButton(
onPressed: isSending
? null
: () async {
final String msg = textController.text;
if (msg.isEmpty) {
SmartDialog.showToast('弹幕内容不能为空');
return;
} else if (msg.length > 100) {
SmartDialog.showToast('弹幕内容不能超过100个字符');
return;
}
setState(() {
isSending = true; // 开始发送,更新状态
});
//修改按钮文字
final dynamic res = await DanmakaHttp.shootDanmaku(
oid: widget.videoDetailCtr!.cid.value,
msg: textController.text,
bvid: widget.videoDetailCtr!.bvid,
progress:
widget.controller!.position.value.inMilliseconds,
type: 1,
);
setState(() {
isSending = false; // 发送结束,更新状态
});
if (res['status']) {
SmartDialog.showToast('发送成功');
// 发送成功,自动预览该弹幕,避免重新请求
// TODO: 暂停状态下预览弹幕仍会移动与计时可考虑添加到dmSegList或其他方式实现
widget.controller!.danmakuController!.addItems([
DanmakuItem(
msg,
color: Colors.white,
time: widget
.controller!.position.value.inMilliseconds,
type: DanmakuItemType.scroll,
isSend: true,
)
]);
Get.back();
} else {
SmartDialog.showToast('发送失败,错误信息为${res['msg']}');
}
},
child: Text(isSending ? '发送中...' : '发送'),
);
})
],
);
},
);
}
/// 定时关闭 /// 定时关闭
void scheduleExit() async { void scheduleExit() async {
const List<int> scheduleTimeChoices = [ const List<int> scheduleTimeChoices = [
@ -1029,7 +1106,7 @@ class _HeaderControlState extends State<HeaderControl> {
}, },
), ),
SizedBox(width: buttonSpace), SizedBox(width: buttonSpace),
if (showTitle && if (isFullScreen.value &&
isLandscape && isLandscape &&
widget.videoType == SearchType.video) ...[ widget.videoType == SearchType.video) ...[
Column( Column(
@ -1081,6 +1158,43 @@ class _HeaderControlState extends State<HeaderControl> {
// ), // ),
// fuc: () => _.screenshot(), // fuc: () => _.screenshot(),
// ), // ),
if (isFullScreen.value) ...[
SizedBox(
width: 56,
height: 34,
child: TextButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () => showShootDanmakuSheet(),
child: const Text(
'发弹幕',
style: textStyle,
),
),
),
SizedBox(
width: 34,
height: 34,
child: Obx(
() => IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
_.isOpenDanmu.value = !_.isOpenDanmu.value;
},
icon: Icon(
_.isOpenDanmu.value
? Icons.subtitles_outlined
: Icons.subtitles_off_outlined,
size: 19,
color: Colors.white,
),
),
),
),
],
SizedBox(width: buttonSpace), SizedBox(width: buttonSpace),
if (Platform.isAndroid) ...<Widget>[ if (Platform.isAndroid) ...<Widget>[
SizedBox( SizedBox(

View File

@ -433,6 +433,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "5.0.3" version: "5.0.3"
expandable:
dependency: "direct main"
description:
name: expandable
sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2"
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.0.1"
extended_image: extended_image:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -142,6 +142,8 @@ dependencies:
path: 1.8.3 path: 1.8.3
# 电池优化 # 电池优化
disable_battery_optimization: ^1.1.1 disable_battery_optimization: ^1.1.1
# 展开/收起
expandable: ^5.0.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: