Compare commits
7 Commits
feature-re
...
feature-up
Author | SHA1 | Date | |
---|---|---|---|
c0f3b4f3a2 | |||
8f9fbf5d41 | |||
d6b972a8ab | |||
f8326e7cb5 | |||
02d2598d01 | |||
11e907d74b | |||
6298711528 |
@ -9,6 +9,7 @@ PODS:
|
||||
- Flutter
|
||||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- ReachabilitySwift
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
@ -38,7 +39,7 @@ PODS:
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.1.1):
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- saver_gallery (0.0.1):
|
||||
@ -71,7 +72,7 @@ DEPENDENCIES:
|
||||
- audio_service (from `.symlinks/plugins/audio_service/ios`)
|
||||
- audio_session (from `.symlinks/plugins/audio_session/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`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
|
||||
@ -113,7 +114,7 @@ EXTERNAL SOURCES:
|
||||
auto_orientation:
|
||||
:path: ".symlinks/plugins/auto_orientation/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
:path: ".symlinks/plugins/connectivity_plus/darwin"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
Flutter:
|
||||
@ -166,7 +167,7 @@ SPEC CHECKSUMS:
|
||||
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
||||
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
||||
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||
connectivity_plus: e2dad488011aeb593e219360e804c43cc1af5770
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
|
||||
@ -180,7 +181,7 @@ SPEC CHECKSUMS:
|
||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||
@ -193,7 +194,7 @@ SPEC CHECKSUMS:
|
||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
|
||||
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
||||
webview_flutter_wkwebview: 4f3e50f7273d31e5500066ed267e3ae4309c5ae4
|
||||
|
||||
PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:expandable/expandable.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.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/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import '../../../../http/user.dart';
|
||||
import 'widgets/action_item.dart';
|
||||
import 'widgets/fav_panel.dart';
|
||||
import 'widgets/intro_detail.dart';
|
||||
@ -137,6 +139,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
late String memberHeroTag;
|
||||
late bool enableAi;
|
||||
bool isProcessing = false;
|
||||
RxBool isExpand = false.obs;
|
||||
late ExpandableController _expandableCtr;
|
||||
|
||||
void Function()? handleState(Future Function() action) {
|
||||
return isProcessing
|
||||
? null
|
||||
@ -160,6 +165,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
follower = Utils.numFormat(videoIntroController.userStat['follower']);
|
||||
followStatus = videoIntroController.followStatus;
|
||||
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
||||
_expandableCtr = ExpandableController(initialExpanded: false);
|
||||
}
|
||||
|
||||
// 收藏
|
||||
@ -212,13 +218,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
// 视频介绍
|
||||
showIntroDetail() {
|
||||
feedBack();
|
||||
showBottomSheet(
|
||||
context: context,
|
||||
enableDrag: true,
|
||||
builder: (BuildContext context) {
|
||||
return IntroDetail(videoDetail: widget.videoDetail!);
|
||||
},
|
||||
);
|
||||
isExpand.value = !(isExpand.value);
|
||||
_expandableCtr.toggle();
|
||||
}
|
||||
|
||||
// 用户主页
|
||||
@ -242,6 +243,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_expandableCtr.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData t = Theme.of(context);
|
||||
@ -259,14 +266,34 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => showIntroDetail(),
|
||||
child: Text(
|
||||
widget.videoDetail!.title!,
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
child: ExpandablePanel(
|
||||
controller: _expandableCtr,
|
||||
collapsed: Text(
|
||||
widget.videoDetail!.title!,
|
||||
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(
|
||||
@ -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),
|
||||
// 合集
|
||||
@ -438,6 +479,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
margin: const EdgeInsets.only(top: 6, bottom: 4),
|
||||
height: constraints.maxWidth / 5 * 0.8,
|
||||
child: GridView.count(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
primary: false,
|
||||
padding: EdgeInsets.zero,
|
||||
crossAxisCount: 5,
|
||||
@ -451,12 +493,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
text: widget.videoDetail!.stat!.like!.toString()),
|
||||
),
|
||||
// ActionItem(
|
||||
// icon: const Icon(FontAwesomeIcons.clock),
|
||||
// onTap: () => videoIntroController.actionShareVideo(),
|
||||
// selectStatus: false,
|
||||
// loadingStatus: loadingStatus,
|
||||
// text: '稍后再看'),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
@ -477,10 +513,14 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
),
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
onTap: () => videoDetailCtr.tabCtr.animateTo(1),
|
||||
icon: const Icon(FontAwesomeIcons.clock),
|
||||
onTap: () async {
|
||||
final res =
|
||||
await UserHttp.toViewLater(bvid: widget.videoDetail!.bvid);
|
||||
SmartDialog.showToast(res['msg']);
|
||||
},
|
||||
selectStatus: false,
|
||||
text: widget.videoDetail!.stat!.reply!.toString(),
|
||||
text: '稍后看',
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
|
@ -1,16 +1,10 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
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: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';
|
||||
|
||||
Box localCache = GStrorage.localCache;
|
||||
late double sheetHeight;
|
||||
|
||||
class IntroDetail extends StatelessWidget {
|
||||
const IntroDetail({
|
||||
super.key,
|
||||
@ -20,105 +14,39 @@ class IntroDetail extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
sheetHeight = localCache.get('sheetHeight');
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
padding: EdgeInsets.only(
|
||||
left: 14,
|
||||
right: 14,
|
||||
bottom: MediaQuery.of(context).padding.bottom + 20),
|
||||
height: sheetHeight,
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
child: SelectableRegion(
|
||||
focusNode: FocusNode(),
|
||||
selectionControls: MaterialTextSelectionControls(),
|
||||
child: Column(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => Get.back(),
|
||||
child: Container(
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Center(
|
||||
child: Container(
|
||||
width: 32,
|
||||
height: 3,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(3))),
|
||||
),
|
||||
),
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 4),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: videoDetail!.bvid!));
|
||||
SmartDialog.showToast('已复制');
|
||||
},
|
||||
child: Text(
|
||||
videoDetail!.bvid!,
|
||||
style: TextStyle(
|
||||
fontSize: 13, color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
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!),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text.rich(
|
||||
style: const TextStyle(height: 1.4),
|
||||
TextSpan(
|
||||
children: [
|
||||
buildContent(context, videoDetail!),
|
||||
],
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
));
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
InlineSpan buildContent(BuildContext context, content) {
|
||||
|
@ -148,14 +148,35 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
floating: true,
|
||||
delegate: _MySliverPersistentHeaderDelegate(
|
||||
child: Container(
|
||||
height: 40,
|
||||
padding: const EdgeInsets.fromLTRB(12, 6, 6, 0),
|
||||
height: 45,
|
||||
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(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'${_videoReplyController.sortTypeLabel.value}评论',
|
||||
style: const TextStyle(fontSize: 13),
|
||||
Obx(
|
||||
() => AnimatedSwitcher(
|
||||
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(
|
||||
height: 35,
|
||||
@ -163,12 +184,10 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
onPressed: () =>
|
||||
_videoReplyController.queryBySort(),
|
||||
icon: const Icon(Icons.sort, size: 16),
|
||||
label: Obx(
|
||||
() => Text(
|
||||
_videoReplyController.sortTypeLabel.value,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
),
|
||||
),
|
||||
label: Obx(() => Text(
|
||||
_videoReplyController.sortTypeLabel.value,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
)),
|
||||
),
|
||||
)
|
||||
],
|
||||
@ -310,8 +329,8 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
|
||||
class _MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
_MySliverPersistentHeaderDelegate({required this.child});
|
||||
final double _minExtent = 40;
|
||||
final double _maxExtent = 40;
|
||||
final double _minExtent = 45;
|
||||
final double _maxExtent = 45;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
|
@ -368,10 +368,13 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
!(plPlayerController?.isOpenDanmu.value ??
|
||||
false);
|
||||
},
|
||||
icon: (plPlayerController?.isOpenDanmu.value ??
|
||||
icon: !(plPlayerController?.isOpenDanmu.value ??
|
||||
false)
|
||||
? SvgPicture.asset(
|
||||
'assets/images/video/danmu_close.svg',
|
||||
// ignore: deprecated_member_use
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
)
|
||||
: SvgPicture.asset(
|
||||
'assets/images/video/danmu_open.svg',
|
||||
|
@ -17,12 +17,16 @@ class ScrollAppBar extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final double statusBarHeight = MediaQuery.of(context).padding.top;
|
||||
final videoHeight = MediaQuery.sizeOf(context).width * 9 / 16;
|
||||
double scrollDistance = scrollVal;
|
||||
if (scrollVal > videoHeight - kToolbarHeight) {
|
||||
scrollDistance = videoHeight - kToolbarHeight;
|
||||
}
|
||||
return Positioned(
|
||||
top: -videoHeight + scrollVal + kToolbarHeight + 0.5,
|
||||
top: -videoHeight + scrollDistance + kToolbarHeight + 0.5,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Opacity(
|
||||
opacity: scrollVal / (videoHeight - kToolbarHeight),
|
||||
opacity: scrollDistance / (videoHeight - kToolbarHeight),
|
||||
child: Container(
|
||||
height: statusBarHeight + kToolbarHeight,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
|
@ -32,28 +32,14 @@ class _ExpandedSectionState extends State<ExpandedSection>
|
||||
_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() {
|
||||
expandController = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 400));
|
||||
Animation<double> curve = CurvedAnimation(
|
||||
parent: expandController,
|
||||
curve: Curves.fastOutSlowIn,
|
||||
curve: Curves.linear,
|
||||
);
|
||||
animation = Tween(begin: widget.begin, end: widget.end).animate(curve);
|
||||
// animation = CurvedAnimation(
|
||||
// parent: expandController,
|
||||
// curve: Curves.fastOutSlowIn,
|
||||
// );
|
||||
}
|
||||
|
||||
void _runExpandCheck() {
|
||||
@ -67,7 +53,9 @@ class _ExpandedSectionState extends State<ExpandedSection>
|
||||
@override
|
||||
void didUpdateWidget(ExpandedSection oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_runExpandCheck();
|
||||
if (widget.expand != oldWidget.expand) {
|
||||
_runExpandCheck();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -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/utils/storage.dart';
|
||||
import 'package:pilipala/services/shutdown_timer_service.dart';
|
||||
import '../../../../http/danmaku.dart';
|
||||
import '../../../../models/common/search_type.dart';
|
||||
import '../../../../models/video_detail_res.dart';
|
||||
import '../introduction/index.dart';
|
||||
@ -52,7 +53,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
final Box<dynamic> videoStorage = GStrorage.video;
|
||||
late List<double> speedsList;
|
||||
double buttonSpace = 8;
|
||||
bool showTitle = false;
|
||||
RxBool isFullScreen = false.obs;
|
||||
late String heroTag;
|
||||
late VideoIntroController videoIntroController;
|
||||
late VideoDetailData videoDetail;
|
||||
@ -69,13 +70,8 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
}
|
||||
|
||||
void fullScreenStatusListener() {
|
||||
widget.videoDetailCtr!.plPlayerController.isFullScreen
|
||||
.listen((bool isFullScreen) {
|
||||
if (isFullScreen) {
|
||||
showTitle = true;
|
||||
} else {
|
||||
showTitle = false;
|
||||
}
|
||||
widget.videoDetailCtr!.plPlayerController.isFullScreen.listen((bool val) {
|
||||
isFullScreen.value = val;
|
||||
|
||||
/// TODO setState() called after dispose()
|
||||
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 {
|
||||
const List<int> scheduleTimeChoices = [
|
||||
@ -1029,7 +1106,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
},
|
||||
),
|
||||
SizedBox(width: buttonSpace),
|
||||
if (showTitle &&
|
||||
if (isFullScreen.value &&
|
||||
isLandscape &&
|
||||
widget.videoType == SearchType.video) ...[
|
||||
Column(
|
||||
@ -1081,6 +1158,43 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
// ),
|
||||
// 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),
|
||||
if (Platform.isAndroid) ...<Widget>[
|
||||
SizedBox(
|
||||
|
@ -433,6 +433,14 @@ packages:
|
||||
url: "https://pub.flutter-io.cn"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -142,6 +142,8 @@ dependencies:
|
||||
path: 1.8.3
|
||||
# 电池优化
|
||||
disable_battery_optimization: ^1.1.1
|
||||
# 展开/收起
|
||||
expandable: ^5.0.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
Reference in New Issue
Block a user