Compare commits
2 Commits
feature-ad
...
feature-up
| Author | SHA1 | Date | |
|---|---|---|---|
| c0f3b4f3a2 | |||
| 8f9fbf5d41 |
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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';
|
||||||
@ -16,7 +17,6 @@ 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 '../../../../http/user.dart';
|
||||||
import '../widgets/expandable_section.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';
|
||||||
@ -140,6 +140,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
late bool enableAi;
|
late bool enableAi;
|
||||||
bool isProcessing = false;
|
bool isProcessing = false;
|
||||||
RxBool isExpand = false.obs;
|
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
|
||||||
@ -163,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
@ -216,6 +219,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
showIntroDetail() {
|
showIntroDetail() {
|
||||||
feedBack();
|
feedBack();
|
||||||
isExpand.value = !(isExpand.value);
|
isExpand.value = !(isExpand.value);
|
||||||
|
_expandableCtr.toggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户主页
|
// 用户主页
|
||||||
@ -239,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);
|
||||||
@ -256,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(
|
||||||
@ -328,12 +358,16 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
|
|
||||||
/// 视频简介
|
/// 视频简介
|
||||||
Obx(
|
ExpandablePanel(
|
||||||
() => ExpandedSection(
|
controller: _expandableCtr,
|
||||||
expand: isExpand.value,
|
collapsed: const SizedBox(height: 0),
|
||||||
begin: 0,
|
expanded: IntroDetail(videoDetail: widget.videoDetail!),
|
||||||
end: 1,
|
theme: const ExpandableThemeData(
|
||||||
child: IntroDetail(videoDetail: widget.videoDetail!),
|
animationDuration: Duration(milliseconds: 300),
|
||||||
|
scrollAnimationDuration: Duration(milliseconds: 300),
|
||||||
|
crossFadePoint: 0,
|
||||||
|
fadeCurve: Curves.ease,
|
||||||
|
sizeCurve: Curves.linear,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
Reference in New Issue
Block a user