Compare commits
2 Commits
v1.0.23.05
...
feature-re
Author | SHA1 | Date | |
---|---|---|---|
c9265897d1 | |||
48b412bdbf |
2
.github/workflows/beta_ci.yml
vendored
2
.github/workflows/beta_ci.yml
vendored
@ -4,7 +4,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "x-main"
|
- "main"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "**.md"
|
- "**.md"
|
||||||
- "**.txt"
|
- "**.txt"
|
||||||
|
@ -26,13 +26,13 @@
|
|||||||
Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
[✓] Flutter (Channel stable, 3.16.5, on macOS 14.1.2 23B92 darwin-arm64, locale
|
[✓] Flutter (Channel stable, 3.16.4, on macOS 14.1.2 23B92 darwin-arm64, locale
|
||||||
zh-Hans-CN)
|
zh-Hans-CN)
|
||||||
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
|
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
|
||||||
[✓] Xcode - develop for iOS and macOS (Xcode 15.1)
|
[✓] Xcode - develop for iOS and macOS (Xcode 15.1)
|
||||||
[✓] Chrome - develop for the web
|
[✓] Chrome - develop for the web
|
||||||
[✓] Android Studio (version 2022.3)
|
[✓] Android Studio (version 2022.3)
|
||||||
[✓] VS Code (version 1.87.2)
|
[✓] VS Code (version 1.85.1)
|
||||||
[✓] Connected device (3 available)
|
[✓] Connected device (3 available)
|
||||||
[✓] Network resources
|
[✓] Network resources
|
||||||
|
|
||||||
@ -44,9 +44,6 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
|||||||
## 技术交流
|
## 技术交流
|
||||||
|
|
||||||
Telegram: https://t.me/+lm_oOVmF0RJiODk1
|
Telegram: https://t.me/+lm_oOVmF0RJiODk1
|
||||||
|
|
||||||
Tg Beta版本:@PiliPala_Beta
|
|
||||||
|
|
||||||
QQ频道: https://pd.qq.com/s/365esodk3
|
QQ频道: https://pd.qq.com/s/365esodk3
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
## 1.0.22
|
|
||||||
|
|
||||||
明天晚上或者后天晚上应该会更新
|
|
||||||
|
|
||||||
### 功能
|
|
||||||
+ 字幕
|
|
||||||
+ 全屏时选集
|
|
||||||
+ 动态转发
|
|
||||||
+ 评论视频并转发
|
|
||||||
+ 收藏夹删除
|
|
||||||
+ 合集显示封面
|
|
||||||
+ 底部导航栏编辑、排序功能
|
|
||||||
+ 历史记录进度条展示
|
|
||||||
+ 直播画质切换
|
|
||||||
+ 排行榜功能
|
|
||||||
+ 视频详情页推荐视频开关
|
|
||||||
+ 显示联合投稿up
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 收藏夹个数错误
|
|
||||||
+ 封面保存权限问题
|
|
||||||
+ 合集最后1p未展示
|
|
||||||
+ up主页关注按钮触发灰屏
|
|
||||||
|
|
||||||
### 优化
|
|
||||||
+ 视频简介查看逻辑
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
@ -1,15 +0,0 @@
|
|||||||
## 1.0.23
|
|
||||||
|
|
||||||
### 功能
|
|
||||||
+ 封面下载
|
|
||||||
|
|
||||||
|
|
||||||
### 修复
|
|
||||||
+ 全屏问题
|
|
||||||
+ 视频播放器灰屏问题
|
|
||||||
+ 评论区点击区域问题
|
|
||||||
+ 动态详情跳转异常问题
|
|
||||||
|
|
||||||
|
|
||||||
更多更新日志可在Github上查看
|
|
||||||
问题反馈、功能建议请查看「关于」页面。
|
|
@ -9,7 +9,6 @@ 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
|
||||||
@ -39,7 +38,7 @@ PODS:
|
|||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- permission_handler_apple (9.3.0):
|
- permission_handler_apple (9.1.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift (5.0.0)
|
- ReachabilitySwift (5.0.0)
|
||||||
- saver_gallery (0.0.1):
|
- saver_gallery (0.0.1):
|
||||||
@ -72,7 +71,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/darwin`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- 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`)
|
||||||
@ -114,7 +113,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/darwin"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
@ -167,7 +166,7 @@ SPEC CHECKSUMS:
|
|||||||
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
|
||||||
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
|
||||||
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
||||||
connectivity_plus: e2dad488011aeb593e219360e804c43cc1af5770
|
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
|
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
|
||||||
@ -181,7 +180,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: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
|
||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
@ -194,7 +193,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: 4f3e50f7273d31e5500066ed267e3ae4309c5ae4
|
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
|
||||||
|
|
||||||
PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be
|
PODFILE CHECKSUM: 637cd290bed23275b5f5ffcc7eb1e73d0a5fb2be
|
||||||
|
|
||||||
|
@ -484,9 +484,6 @@ class Api {
|
|||||||
/// 激活buvid3
|
/// 激活buvid3
|
||||||
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
|
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
|
||||||
|
|
||||||
/// 获取字幕配置
|
|
||||||
static const getSubtitleConfig = '/x/player/v2';
|
|
||||||
|
|
||||||
/// 我的订阅
|
/// 我的订阅
|
||||||
static const userSubFolder = '/x/v3/fav/folder/collected/list';
|
static const userSubFolder = '/x/v3/fav/folder/collected/list';
|
||||||
|
|
||||||
@ -506,6 +503,4 @@ class Api {
|
|||||||
/// 排行榜
|
/// 排行榜
|
||||||
static const String getRankApi = "/x/web-interface/ranking/v2";
|
static const String getRankApi = "/x/web-interface/ranking/v2";
|
||||||
|
|
||||||
/// 取消订阅
|
|
||||||
static const String cancelSub = '/x/v3/fav/season/unfav';
|
|
||||||
}
|
}
|
||||||
|
@ -349,21 +349,4 @@ class UserHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消订阅
|
|
||||||
static Future cancelSub({required int seasonId}) async {
|
|
||||||
var res = await Request().post(
|
|
||||||
Api.cancelSub,
|
|
||||||
queryParameters: {
|
|
||||||
'platform': 'web',
|
|
||||||
'season_id': seasonId,
|
|
||||||
'csrf': await Request.getCsrf(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {'status': true};
|
|
||||||
} else {
|
|
||||||
return {'status': false, 'msg': res.data['message']};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,9 @@ import '../models/model_rec_video_item.dart';
|
|||||||
import '../models/user/fav_folder.dart';
|
import '../models/user/fav_folder.dart';
|
||||||
import '../models/video/ai.dart';
|
import '../models/video/ai.dart';
|
||||||
import '../models/video/play/url.dart';
|
import '../models/video/play/url.dart';
|
||||||
import '../models/video/subTitile/result.dart';
|
|
||||||
import '../models/video_detail_res.dart';
|
import '../models/video_detail_res.dart';
|
||||||
import '../utils/recommend_filter.dart';
|
import '../utils/recommend_filter.dart';
|
||||||
import '../utils/storage.dart';
|
import '../utils/storage.dart';
|
||||||
import '../utils/subtitle.dart';
|
|
||||||
import '../utils/wbi_sign.dart';
|
import '../utils/wbi_sign.dart';
|
||||||
import 'api.dart';
|
import 'api.dart';
|
||||||
import 'init.dart';
|
import 'init.dart';
|
||||||
@ -478,25 +476,6 @@ class VideoHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future getSubtitle({int? cid, String? bvid}) async {
|
|
||||||
var res = await Request().get(Api.getSubtitleConfig, data: {
|
|
||||||
'cid': cid,
|
|
||||||
'bvid': bvid,
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
if (res.data['code'] == 0) {
|
|
||||||
return {
|
|
||||||
'status': true,
|
|
||||||
'data': SubTitlteModel.fromJson(res.data['data']),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {'status': false, 'data': [], 'msg': res.data['msg']};
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
print(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 视频排行
|
// 视频排行
|
||||||
static Future getRankVideoList(int rid) async {
|
static Future getRankVideoList(int rid) async {
|
||||||
try {
|
try {
|
||||||
@ -519,12 +498,4 @@ class VideoHttp {
|
|||||||
return {'status': false, 'data': [], 'msg': err};
|
return {'status': false, 'data': [], 'msg': err};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取字幕内容
|
|
||||||
static Future<Map<String, dynamic>> getSubtitleContent(url) async {
|
|
||||||
var res = await Request().get('https:$url');
|
|
||||||
final String content = SubTitleUtils.convertToWebVTT(res.data['body']);
|
|
||||||
final List body = res.data['body'];
|
|
||||||
return {'content': content, 'body': body};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,6 @@ void main() async {
|
|||||||
.then((_) async {
|
.then((_) async {
|
||||||
await GStrorage.init();
|
await GStrorage.init();
|
||||||
await setupServiceLocator();
|
await setupServiceLocator();
|
||||||
clearLogs();
|
|
||||||
Request();
|
Request();
|
||||||
await Request.setCookie();
|
await Request.setCookie();
|
||||||
RecommendFilter();
|
RecommendFilter();
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
enum SubtitleType {
|
|
||||||
// 中文(中国)
|
|
||||||
zhCN,
|
|
||||||
// 中文(自动翻译)
|
|
||||||
aizh,
|
|
||||||
// 英语(自动生成)
|
|
||||||
aien,
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SubtitleTypeExtension on SubtitleType {
|
|
||||||
String get description {
|
|
||||||
switch (this) {
|
|
||||||
case SubtitleType.zhCN:
|
|
||||||
return '中文(中国)';
|
|
||||||
case SubtitleType.aizh:
|
|
||||||
return '中文(自动翻译)';
|
|
||||||
case SubtitleType.aien:
|
|
||||||
return '英语(自动生成)';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SubtitleIdExtension on SubtitleType {
|
|
||||||
String get id {
|
|
||||||
switch (this) {
|
|
||||||
case SubtitleType.zhCN:
|
|
||||||
return 'zh-CN';
|
|
||||||
case SubtitleType.aizh:
|
|
||||||
return 'ai-zh';
|
|
||||||
case SubtitleType.aien:
|
|
||||||
return 'ai-en';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SubtitleCodeExtension on SubtitleType {
|
|
||||||
int get code {
|
|
||||||
switch (this) {
|
|
||||||
case SubtitleType.zhCN:
|
|
||||||
return 1;
|
|
||||||
case SubtitleType.aizh:
|
|
||||||
return 2;
|
|
||||||
case SubtitleType.aien:
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -47,23 +47,18 @@ class Vip {
|
|||||||
this.status,
|
this.status,
|
||||||
this.dueDate,
|
this.dueDate,
|
||||||
this.label,
|
this.label,
|
||||||
this.nicknameColor,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
int? type;
|
int? type;
|
||||||
int? status;
|
int? status;
|
||||||
int? dueDate;
|
int? dueDate;
|
||||||
Map? label;
|
Map? label;
|
||||||
int? nicknameColor;
|
|
||||||
|
|
||||||
Vip.fromJson(Map<String, dynamic> json) {
|
Vip.fromJson(Map<String, dynamic> json) {
|
||||||
type = json['type'];
|
type = json['type'];
|
||||||
status = json['status'];
|
status = json['status'];
|
||||||
dueDate = json['due_date'];
|
dueDate = json['due_date'];
|
||||||
label = json['label'];
|
label = json['label'];
|
||||||
nicknameColor = json['nickname_color'] == ''
|
|
||||||
? null
|
|
||||||
: int.parse("0xFF${json['nickname_color'].replaceAll('#', '')}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,8 +437,7 @@ class SearchArticleItemModel {
|
|||||||
pubTime = json['pub_time'];
|
pubTime = json['pub_time'];
|
||||||
like = json['like'];
|
like = json['like'];
|
||||||
title = Em.regTitle(json['title']);
|
title = Em.regTitle(json['title']);
|
||||||
subTitle =
|
subTitle = json['title'].replaceAll(RegExp(r'<[^>]*>'), '');
|
||||||
Em.decodeHtmlEntities(json['title'].replaceAll(RegExp(r'<[^>]*>'), ''));
|
|
||||||
rankOffset = json['rank_offset'];
|
rankOffset = json['rank_offset'];
|
||||||
mid = json['mid'];
|
mid = json['mid'];
|
||||||
imageUrls = json['image_urls'];
|
imageUrls = json['image_urls'];
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
class SubTitileContentModel {
|
|
||||||
double? from;
|
|
||||||
double? to;
|
|
||||||
int? location;
|
|
||||||
String? content;
|
|
||||||
|
|
||||||
SubTitileContentModel({
|
|
||||||
this.from,
|
|
||||||
this.to,
|
|
||||||
this.location,
|
|
||||||
this.content,
|
|
||||||
});
|
|
||||||
|
|
||||||
SubTitileContentModel.fromJson(Map<String, dynamic> json) {
|
|
||||||
from = json['from'];
|
|
||||||
to = json['to'];
|
|
||||||
location = json['location'];
|
|
||||||
content = json['content'];
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,89 +0,0 @@
|
|||||||
import 'package:get/get.dart';
|
|
||||||
import '../../common/subtitle_type.dart';
|
|
||||||
|
|
||||||
class SubTitlteModel {
|
|
||||||
SubTitlteModel({
|
|
||||||
this.aid,
|
|
||||||
this.bvid,
|
|
||||||
this.cid,
|
|
||||||
this.loginMid,
|
|
||||||
this.loginMidHash,
|
|
||||||
this.isOwner,
|
|
||||||
this.name,
|
|
||||||
this.subtitles,
|
|
||||||
});
|
|
||||||
|
|
||||||
int? aid;
|
|
||||||
String? bvid;
|
|
||||||
int? cid;
|
|
||||||
int? loginMid;
|
|
||||||
String? loginMidHash;
|
|
||||||
bool? isOwner;
|
|
||||||
String? name;
|
|
||||||
List<SubTitlteItemModel>? subtitles;
|
|
||||||
|
|
||||||
factory SubTitlteModel.fromJson(Map<String, dynamic> json) => SubTitlteModel(
|
|
||||||
aid: json["aid"],
|
|
||||||
bvid: json["bvid"],
|
|
||||||
cid: json["cid"],
|
|
||||||
loginMid: json["login_mid"],
|
|
||||||
loginMidHash: json["login_mid_hash"],
|
|
||||||
isOwner: json["is_owner"],
|
|
||||||
name: json["name"],
|
|
||||||
subtitles: json["subtitle"] != null
|
|
||||||
? json["subtitle"]["subtitles"]
|
|
||||||
.map<SubTitlteItemModel>((x) => SubTitlteItemModel.fromJson(x))
|
|
||||||
.toList()
|
|
||||||
: [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SubTitlteItemModel {
|
|
||||||
SubTitlteItemModel({
|
|
||||||
this.id,
|
|
||||||
this.lan,
|
|
||||||
this.lanDoc,
|
|
||||||
this.isLock,
|
|
||||||
this.subtitleUrl,
|
|
||||||
this.type,
|
|
||||||
this.aiType,
|
|
||||||
this.aiStatus,
|
|
||||||
this.title,
|
|
||||||
this.code,
|
|
||||||
this.content,
|
|
||||||
this.body,
|
|
||||||
});
|
|
||||||
|
|
||||||
int? id;
|
|
||||||
String? lan;
|
|
||||||
String? lanDoc;
|
|
||||||
bool? isLock;
|
|
||||||
String? subtitleUrl;
|
|
||||||
int? type;
|
|
||||||
int? aiType;
|
|
||||||
int? aiStatus;
|
|
||||||
String? title;
|
|
||||||
int? code;
|
|
||||||
String? content;
|
|
||||||
List? body;
|
|
||||||
|
|
||||||
factory SubTitlteItemModel.fromJson(Map<String, dynamic> json) =>
|
|
||||||
SubTitlteItemModel(
|
|
||||||
id: json["id"],
|
|
||||||
lan: json["lan"].replaceAll('-', ''),
|
|
||||||
lanDoc: json["lan_doc"],
|
|
||||||
isLock: json["is_lock"],
|
|
||||||
subtitleUrl: json["subtitle_url"],
|
|
||||||
type: json["type"],
|
|
||||||
aiType: json["ai_type"],
|
|
||||||
aiStatus: json["ai_status"],
|
|
||||||
title: json["lan_doc"],
|
|
||||||
code: SubtitleType.values
|
|
||||||
.firstWhereOrNull(
|
|
||||||
(element) => element.id.toString() == json["lan"])
|
|
||||||
?.index ??
|
|
||||||
-1,
|
|
||||||
content: '',
|
|
||||||
body: [],
|
|
||||||
);
|
|
||||||
}
|
|
@ -185,7 +185,7 @@ class HistoryItem extends StatelessWidget {
|
|||||||
? '已看完'
|
? '已看完'
|
||||||
: '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}',
|
: '${Utils.timeFormat(videoItem.progress!)}/${Utils.timeFormat(videoItem.duration!)}',
|
||||||
right: 6.0,
|
right: 6.0,
|
||||||
bottom: 8.0,
|
bottom: 6.0,
|
||||||
type: 'gray',
|
type: 'gray',
|
||||||
),
|
),
|
||||||
// 右上角
|
// 右上角
|
||||||
@ -258,27 +258,6 @@ class HistoryItem extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
videoItem.progress != 0
|
|
||||||
? Positioned(
|
|
||||||
left: 3,
|
|
||||||
right: 3,
|
|
||||||
bottom: 0,
|
|
||||||
child: ClipRRect(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
bottomLeft: Radius.circular(
|
|
||||||
StyleString.imgRadius.x),
|
|
||||||
bottomRight: Radius.circular(
|
|
||||||
StyleString.imgRadius.x),
|
|
||||||
),
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
value: videoItem.progress == -1
|
|
||||||
? 100
|
|
||||||
: videoItem.progress /
|
|
||||||
videoItem.duration,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox()
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
VideoContent(videoItem: videoItem, ctr: ctr)
|
VideoContent(videoItem: videoItem, ctr: ctr)
|
||||||
|
@ -281,8 +281,8 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
future: _futureBuilderFuture,
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
Map? data = snapshot.data;
|
Map data = snapshot.data!;
|
||||||
if (data != null && data['status']) {
|
if (data['status']) {
|
||||||
return Obx(
|
return Obx(
|
||||||
() => Stack(
|
() => Stack(
|
||||||
alignment: AlignmentDirectional.center,
|
alignment: AlignmentDirectional.center,
|
||||||
@ -302,14 +302,7 @@ class _MemberPageState extends State<MemberPage>
|
|||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.titleMedium!
|
.titleMedium!
|
||||||
.copyWith(
|
.copyWith(fontWeight: FontWeight.bold),
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: _memberController.memberInfo.value
|
|
||||||
.vip!.nicknameColor !=
|
|
||||||
null
|
|
||||||
? Color(_memberController.memberInfo
|
|
||||||
.value.vip!.nicknameColor!)
|
|
||||||
: null),
|
|
||||||
)),
|
)),
|
||||||
const SizedBox(width: 2),
|
const SizedBox(width: 2),
|
||||||
if (_memberController.memberInfo.value.sex == '女')
|
if (_memberController.memberInfo.value.sex == '女')
|
||||||
|
@ -18,32 +18,45 @@ class MemberSeasonsPanel extends StatelessWidget {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
MemberSeasonsList item = data!.seasonsList![index];
|
MemberSeasonsList item = data!.seasonsList![index];
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12),
|
padding: const EdgeInsets.only(bottom: 12, right: 4),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
Padding(
|
||||||
onTap: () => Get.toNamed(
|
padding: const EdgeInsets.only(bottom: 12, left: 4),
|
||||||
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
|
child: Row(
|
||||||
title: Text(
|
children: [
|
||||||
item.meta!.name!,
|
Text(
|
||||||
maxLines: 1,
|
item.meta!.name!,
|
||||||
overflow: TextOverflow.ellipsis,
|
maxLines: 1,
|
||||||
style: Theme.of(context).textTheme.titleSmall!,
|
style: Theme.of(context).textTheme.titleSmall!,
|
||||||
),
|
),
|
||||||
dense: true,
|
const SizedBox(width: 10),
|
||||||
leading: PBadge(
|
PBadge(
|
||||||
stack: 'relative',
|
stack: 'relative',
|
||||||
size: 'small',
|
size: 'small',
|
||||||
text: item.meta!.total.toString(),
|
text: item.meta!.total.toString(),
|
||||||
),
|
),
|
||||||
trailing: const Icon(
|
const Spacer(),
|
||||||
Icons.arrow_forward,
|
SizedBox(
|
||||||
size: 20,
|
width: 35,
|
||||||
|
height: 35,
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: () => Get.toNamed(
|
||||||
|
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.arrow_forward,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, boxConstraints) {
|
builder: (context, boxConstraints) {
|
||||||
return GridView.builder(
|
return GridView.builder(
|
||||||
|
@ -9,7 +9,7 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
class RankController extends GetxController with GetTickerProviderStateMixin {
|
class RankController extends GetxController with GetTickerProviderStateMixin {
|
||||||
bool flag = false;
|
bool flag = false;
|
||||||
late RxList tabs = [].obs;
|
late RxList tabs = [].obs;
|
||||||
RxInt initialIndex = 0.obs;
|
RxInt initialIndex = 1.obs;
|
||||||
late TabController tabController;
|
late TabController tabController;
|
||||||
late List tabsCtrList;
|
late List tabsCtrList;
|
||||||
late List<Widget> tabsPageList;
|
late List<Widget> tabsPageList;
|
||||||
@ -50,5 +50,21 @@ class RankController extends GetxController with GetTickerProviderStateMixin {
|
|||||||
length: tabs.length,
|
length: tabs.length,
|
||||||
vsync: this,
|
vsync: this,
|
||||||
);
|
);
|
||||||
|
// 监听 tabController 切换
|
||||||
|
if (enableGradientBg) {
|
||||||
|
tabController.animation!.addListener(() {
|
||||||
|
if (tabController.indexIsChanging) {
|
||||||
|
if (initialIndex.value != tabController.index) {
|
||||||
|
initialIndex.value = tabController.index;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final int temp = tabController.animation!.value.round();
|
||||||
|
if (initialIndex.value != temp) {
|
||||||
|
initialIndex.value = temp;
|
||||||
|
tabController.index = initialIndex.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,20 +22,15 @@ class ZonePage extends StatefulWidget {
|
|||||||
State<ZonePage> createState() => _ZonePageState();
|
State<ZonePage> createState() => _ZonePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ZonePageState extends State<ZonePage>
|
class _ZonePageState extends State<ZonePage> {
|
||||||
with AutomaticKeepAliveClientMixin {
|
final ZoneController _zoneController = Get.put(ZoneController());
|
||||||
late ZoneController _zoneController;
|
|
||||||
List videoList = [];
|
List videoList = [];
|
||||||
Future? _futureBuilderFuture;
|
Future? _futureBuilderFuture;
|
||||||
late ScrollController scrollController;
|
late ScrollController scrollController;
|
||||||
|
|
||||||
@override
|
|
||||||
bool get wantKeepAlive => true;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_zoneController = Get.put(ZoneController(), tag: widget.rid.toString());
|
|
||||||
_futureBuilderFuture = _zoneController.queryRankFeed('init', widget.rid);
|
_futureBuilderFuture = _zoneController.queryRankFeed('init', widget.rid);
|
||||||
scrollController = _zoneController.scrollController;
|
scrollController = _zoneController.scrollController;
|
||||||
StreamController<bool> mainStream =
|
StreamController<bool> mainStream =
|
||||||
@ -73,7 +68,6 @@ class _ZonePageState extends State<ZonePage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
return await _zoneController.onRefresh();
|
return await _zoneController.onRefresh();
|
||||||
|
@ -44,7 +44,6 @@ class _SelectDialogState<T> extends State<SelectDialog<T>> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_tempValue = value as T;
|
_tempValue = value as T;
|
||||||
});
|
});
|
||||||
Navigator.pop(context, _tempValue);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -52,6 +51,19 @@ class _SelectDialogState<T> extends State<SelectDialog<T>> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => Navigator.pop(context, _tempValue),
|
||||||
|
child: const Text('确定'),
|
||||||
|
)
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,40 +46,4 @@ class SubController extends GetxController {
|
|||||||
Future onLoad() async {
|
Future onLoad() async {
|
||||||
querySubFolder(type: 'onload');
|
querySubFolder(type: 'onload');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 取消订阅
|
|
||||||
Future<void> cancelSub(SubFolderItemData subFolderItem) async {
|
|
||||||
showDialog(
|
|
||||||
context: Get.context!,
|
|
||||||
builder: (context) => AlertDialog(
|
|
||||||
title: const Text('提示'),
|
|
||||||
content: const Text('确定取消订阅吗?'),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
'取消',
|
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: () async {
|
|
||||||
var res = await UserHttp.cancelSub(seasonId: subFolderItem.id!);
|
|
||||||
if (res['status']) {
|
|
||||||
subFolderData.value.list!.remove(subFolderItem);
|
|
||||||
subFolderData.update((val) {});
|
|
||||||
SmartDialog.showToast('取消订阅成功');
|
|
||||||
} else {
|
|
||||||
SmartDialog.showToast(res['msg']);
|
|
||||||
}
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
child: const Text('确定'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -58,8 +58,7 @@ class _SubPageState extends State<SubPage> {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return SubItem(
|
return SubItem(
|
||||||
subFolderItem:
|
subFolderItem:
|
||||||
_subController.subFolderData.value.list![index],
|
_subController.subFolderData.value.list![index]);
|
||||||
cancelSub: _subController.cancelSub);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -8,12 +8,7 @@ import '../../../models/user/sub_folder.dart';
|
|||||||
|
|
||||||
class SubItem extends StatelessWidget {
|
class SubItem extends StatelessWidget {
|
||||||
final SubFolderItemData subFolderItem;
|
final SubFolderItemData subFolderItem;
|
||||||
final Function(SubFolderItemData) cancelSub;
|
const SubItem({super.key, required this.subFolderItem});
|
||||||
const SubItem({
|
|
||||||
super.key,
|
|
||||||
required this.subFolderItem,
|
|
||||||
required this.cancelSub,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -56,10 +51,7 @@ class SubItem extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VideoContent(
|
VideoContent(subFolderItem: subFolderItem)
|
||||||
subFolderItem: subFolderItem,
|
|
||||||
cancelSub: cancelSub,
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -72,8 +64,7 @@ class SubItem extends StatelessWidget {
|
|||||||
|
|
||||||
class VideoContent extends StatelessWidget {
|
class VideoContent extends StatelessWidget {
|
||||||
final SubFolderItemData subFolderItem;
|
final SubFolderItemData subFolderItem;
|
||||||
final Function(SubFolderItemData)? cancelSub;
|
const VideoContent({super.key, required this.subFolderItem});
|
||||||
const VideoContent({super.key, required this.subFolderItem, this.cancelSub});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -109,24 +100,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
height: 35,
|
|
||||||
width: 35,
|
|
||||||
child: IconButton(
|
|
||||||
onPressed: () => cancelSub?.call(subFolderItem),
|
|
||||||
style: TextButton.styleFrom(
|
|
||||||
foregroundColor: Theme.of(context).colorScheme.outline,
|
|
||||||
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.delete_outline, size: 18),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -20,7 +20,6 @@ import 'package:pilipala/utils/utils.dart';
|
|||||||
import 'package:pilipala/utils/video_utils.dart';
|
import 'package:pilipala/utils/video_utils.dart';
|
||||||
import 'package:screen_brightness/screen_brightness.dart';
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
|
|
||||||
import '../../../models/video/subTitile/content.dart';
|
|
||||||
import '../../../http/danmaku.dart';
|
import '../../../http/danmaku.dart';
|
||||||
import '../../../utils/id_utils.dart';
|
import '../../../utils/id_utils.dart';
|
||||||
import 'widgets/header_control.dart';
|
import 'widgets/header_control.dart';
|
||||||
@ -91,13 +90,10 @@ class VideoDetailController extends GetxController
|
|||||||
late bool enableCDN;
|
late bool enableCDN;
|
||||||
late int? cacheVideoQa;
|
late int? cacheVideoQa;
|
||||||
late String cacheDecode;
|
late String cacheDecode;
|
||||||
late int defaultAudioQa;
|
late int cacheAudioQa;
|
||||||
|
|
||||||
PersistentBottomSheetController? replyReplyBottomSheetCtr;
|
PersistentBottomSheetController? replyReplyBottomSheetCtr;
|
||||||
RxList<SubTitileContentModel> subtitleContents =
|
|
||||||
<SubTitileContentModel>[].obs;
|
|
||||||
late bool enableRelatedVideo;
|
late bool enableRelatedVideo;
|
||||||
List subtitles = [];
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
@ -146,10 +142,9 @@ class VideoDetailController extends GetxController
|
|||||||
// 预设的解码格式
|
// 预设的解码格式
|
||||||
cacheDecode = setting.get(SettingBoxKey.defaultDecode,
|
cacheDecode = setting.get(SettingBoxKey.defaultDecode,
|
||||||
defaultValue: VideoDecodeFormats.values.last.code);
|
defaultValue: VideoDecodeFormats.values.last.code);
|
||||||
defaultAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
cacheAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
|
||||||
defaultValue: AudioQuality.hiRes.code);
|
defaultValue: AudioQuality.hiRes.code);
|
||||||
oid.value = IdUtils.bv2av(Get.parameters['bvid']!);
|
oid.value = IdUtils.bv2av(Get.parameters['bvid']!);
|
||||||
getSubtitle();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showReplyReplyPanel() {
|
showReplyReplyPanel() {
|
||||||
@ -256,8 +251,6 @@ class VideoDetailController extends GetxController
|
|||||||
|
|
||||||
/// 开启自动全屏时,在player初始化完成后立即传入headerControl
|
/// 开启自动全屏时,在player初始化完成后立即传入headerControl
|
||||||
plPlayerController.headerControl = headerControl;
|
plPlayerController.headerControl = headerControl;
|
||||||
|
|
||||||
plPlayerController.subtitles.value = subtitles;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 视频链接
|
// 视频链接
|
||||||
@ -353,9 +346,9 @@ class VideoDetailController extends GetxController
|
|||||||
|
|
||||||
if (audiosList.isNotEmpty) {
|
if (audiosList.isNotEmpty) {
|
||||||
final List<int> numbers = audiosList.map((map) => map.id!).toList();
|
final List<int> numbers = audiosList.map((map) => map.id!).toList();
|
||||||
int closestNumber = Utils.findClosestNumber(defaultAudioQa, numbers);
|
int closestNumber = Utils.findClosestNumber(cacheAudioQa, numbers);
|
||||||
if (!numbers.contains(defaultAudioQa) &&
|
if (!numbers.contains(cacheAudioQa) &&
|
||||||
numbers.any((e) => e > defaultAudioQa)) {
|
numbers.any((e) => e > cacheAudioQa)) {
|
||||||
closestNumber = 30280;
|
closestNumber = 30280;
|
||||||
}
|
}
|
||||||
firstAudio = audiosList.firstWhere((e) => e.id == closestNumber);
|
firstAudio = audiosList.firstWhere((e) => e.id == closestNumber);
|
||||||
@ -395,45 +388,6 @@ class VideoDetailController extends GetxController
|
|||||||
: print('replyReplyBottomSheetCtr is null');
|
: print('replyReplyBottomSheetCtr is null');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取字幕配置
|
|
||||||
Future getSubtitle() async {
|
|
||||||
var result = await VideoHttp.getSubtitle(bvid: bvid, cid: cid.value);
|
|
||||||
if (result['status']) {
|
|
||||||
if (result['data'].subtitles.isNotEmpty) {
|
|
||||||
subtitles = result['data'].subtitles;
|
|
||||||
if (subtitles.isNotEmpty) {
|
|
||||||
for (var i in subtitles) {
|
|
||||||
final Map<String, dynamic> res = await VideoHttp.getSubtitleContent(
|
|
||||||
i.subtitleUrl,
|
|
||||||
);
|
|
||||||
i.content = res['content'];
|
|
||||||
i.body = res['body'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result['data'];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取字幕内容
|
|
||||||
// Future getSubtitleContent(String url) async {
|
|
||||||
// var res = await Request().get('https:$url');
|
|
||||||
// subtitleContents.value = res.data['body'].map<SubTitileContentModel>((e) {
|
|
||||||
// return SubTitileContentModel.fromJson(e);
|
|
||||||
// }).toList();
|
|
||||||
// setSubtitleContent();
|
|
||||||
// }
|
|
||||||
|
|
||||||
setSubtitleContent() {
|
|
||||||
plPlayerController.subtitleContent.value = '';
|
|
||||||
plPlayerController.subtitles.value = subtitles;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearSubtitleContent() {
|
|
||||||
plPlayerController.subtitleContent.value = '';
|
|
||||||
plPlayerController.subtitles.value = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 发送弹幕
|
/// 发送弹幕
|
||||||
void showShootDanmakuSheet() {
|
void showShootDanmakuSheet() {
|
||||||
final TextEditingController textController = TextEditingController();
|
final TextEditingController textController = TextEditingController();
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
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 +15,6 @@ 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';
|
||||||
@ -139,9 +137,6 @@ 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
|
||||||
@ -165,7 +160,6 @@ 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收藏
|
// 收藏
|
||||||
@ -218,8 +212,13 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
// 视频介绍
|
// 视频介绍
|
||||||
showIntroDetail() {
|
showIntroDetail() {
|
||||||
feedBack();
|
feedBack();
|
||||||
isExpand.value = !(isExpand.value);
|
showBottomSheet(
|
||||||
_expandableCtr.toggle();
|
context: context,
|
||||||
|
enableDrag: true,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return IntroDetail(videoDetail: widget.videoDetail!);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 用户主页
|
// 用户主页
|
||||||
@ -243,12 +242,6 @@ 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);
|
||||||
@ -266,34 +259,14 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
GestureDetector(
|
GestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () => showIntroDetail(),
|
onTap: () => showIntroDetail(),
|
||||||
child: ExpandablePanel(
|
child: Text(
|
||||||
controller: _expandableCtr,
|
widget.videoDetail!.title!,
|
||||||
collapsed: Text(
|
style: const TextStyle(
|
||||||
widget.videoDetail!.title!,
|
fontSize: 18,
|
||||||
softWrap: true,
|
fontWeight: FontWeight.bold,
|
||||||
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(
|
||||||
@ -357,20 +330,6 @@ 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),
|
||||||
// 合集
|
// 合集
|
||||||
@ -479,7 +438,6 @@ 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,
|
||||||
@ -493,6 +451,12 @@ 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),
|
||||||
@ -513,14 +477,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
ActionItem(
|
ActionItem(
|
||||||
icon: const Icon(FontAwesomeIcons.clock),
|
icon: const Icon(FontAwesomeIcons.comment),
|
||||||
onTap: () async {
|
onTap: () => videoDetailCtr.tabCtr.animateTo(1),
|
||||||
final res =
|
|
||||||
await UserHttp.toViewLater(bvid: widget.videoDetail!.bvid);
|
|
||||||
SmartDialog.showToast(res['msg']);
|
|
||||||
},
|
|
||||||
selectStatus: false,
|
selectStatus: false,
|
||||||
text: '稍后看',
|
text: widget.videoDetail!.stat!.reply!.toString(),
|
||||||
),
|
),
|
||||||
ActionItem(
|
ActionItem(
|
||||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||||
|
@ -1,10 +1,16 @@
|
|||||||
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,
|
||||||
@ -14,39 +20,105 @@ class IntroDetail extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
sheetHeight = localCache.get('sheetHeight');
|
||||||
width: double.infinity,
|
return Container(
|
||||||
child: SelectableRegion(
|
color: Theme.of(context).colorScheme.background,
|
||||||
focusNode: FocusNode(),
|
padding: EdgeInsets.only(
|
||||||
selectionControls: MaterialTextSelectionControls(),
|
left: 14,
|
||||||
|
right: 14,
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 20),
|
||||||
|
height: sheetHeight,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
children: [
|
||||||
children: <Widget>[
|
InkWell(
|
||||||
const SizedBox(height: 4),
|
onTap: () => Get.back(),
|
||||||
GestureDetector(
|
child: Container(
|
||||||
onTap: () {
|
height: 35,
|
||||||
Clipboard.setData(ClipboardData(text: videoDetail!.bvid!));
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
SmartDialog.showToast('已复制');
|
child: Center(
|
||||||
},
|
child: Container(
|
||||||
child: Text(
|
width: 32,
|
||||||
videoDetail!.bvid!,
|
height: 3,
|
||||||
style: TextStyle(
|
decoration: BoxDecoration(
|
||||||
fontSize: 13, color: Theme.of(context).colorScheme.primary),
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius.all(Radius.circular(3))),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
Expanded(
|
||||||
Text.rich(
|
child: SingleChildScrollView(
|
||||||
style: const TextStyle(height: 1.4),
|
child: Column(
|
||||||
TextSpan(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
buildContent(context, videoDetail!),
|
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!),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
),
|
));
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InlineSpan buildContent(BuildContext context, content) {
|
InlineSpan buildContent(BuildContext context, content) {
|
||||||
|
@ -149,16 +149,13 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
delegate: _MySliverPersistentHeaderDelegate(
|
delegate: _MySliverPersistentHeaderDelegate(
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 40,
|
height: 40,
|
||||||
padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
|
padding: const EdgeInsets.fromLTRB(12, 6, 6, 0),
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Obx(
|
Text(
|
||||||
() => Text(
|
'${_videoReplyController.sortTypeLabel.value}评论',
|
||||||
'${_videoReplyController.sortTypeLabel.value}评论',
|
style: const TextStyle(fontSize: 13),
|
||||||
style: const TextStyle(fontSize: 13),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 35,
|
height: 35,
|
||||||
|
@ -498,7 +498,7 @@ InlineSpan buildContent(
|
|||||||
return str;
|
return str;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' ');
|
// content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' ');
|
||||||
content.message = content.message
|
content.message = content.message
|
||||||
.replaceAll('&', '&')
|
.replaceAll('&', '&')
|
||||||
.replaceAll('<', '<')
|
.replaceAll('<', '<')
|
||||||
@ -525,18 +525,14 @@ InlineSpan buildContent(
|
|||||||
if (jumpUrlKeysList.isNotEmpty) {
|
if (jumpUrlKeysList.isNotEmpty) {
|
||||||
patternStr += '|${jumpUrlKeysList.join('|')}';
|
patternStr += '|${jumpUrlKeysList.join('|')}';
|
||||||
}
|
}
|
||||||
RegExp bv23Regex = RegExp(r'https://b23\.tv/[a-zA-Z0-9]{7}');
|
|
||||||
final RegExp pattern = RegExp(patternStr);
|
final RegExp pattern = RegExp(patternStr);
|
||||||
List<String> matchedStrs = [];
|
List<String> matchedStrs = [];
|
||||||
void addPlainTextSpan(str) {
|
void addPlainTextSpan(str) {
|
||||||
spanChilds.add(
|
spanChilds.add(TextSpan(
|
||||||
TextSpan(
|
|
||||||
text: str,
|
text: str,
|
||||||
recognizer: TapGestureRecognizer()
|
recognizer: TapGestureRecognizer()
|
||||||
..onTap = () =>
|
..onTap = () =>
|
||||||
replyReply?.call(replyItem.root == 0 ? replyItem : fReplyItem),
|
replyReply?.call(replyItem.root == 0 ? replyItem : fReplyItem)));
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分割文本并处理每个部分
|
// 分割文本并处理每个部分
|
||||||
@ -738,36 +734,8 @@ InlineSpan buildContent(
|
|||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
onNonMatch: (String nonMatchStr) {
|
onNonMatch: (String nonMatchStr) {
|
||||||
return nonMatchStr.splitMapJoin(
|
addPlainTextSpan(nonMatchStr);
|
||||||
bv23Regex,
|
return nonMatchStr;
|
||||||
onMatch: (Match match) {
|
|
||||||
String matchStr = match[0]!;
|
|
||||||
spanChilds.add(
|
|
||||||
TextSpan(
|
|
||||||
text: ' $matchStr ',
|
|
||||||
style: isVideoPage
|
|
||||||
? TextStyle(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
recognizer: TapGestureRecognizer()
|
|
||||||
..onTap = () => Get.toNamed(
|
|
||||||
'/webview',
|
|
||||||
parameters: {
|
|
||||||
'url': matchStr,
|
|
||||||
'type': 'url',
|
|
||||||
'pageTitle': matchStr
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
onNonMatch: (String nonMatchOtherStr) {
|
|
||||||
addPlainTextSpan(nonMatchOtherStr);
|
|
||||||
return nonMatchOtherStr;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -142,10 +142,9 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
double _keyboardHeight = EdgeInsets.fromViewPadding(
|
double keyboardHeight = EdgeInsets.fromViewPadding(
|
||||||
View.of(context).viewInsets, View.of(context).devicePixelRatio)
|
View.of(context).viewInsets, View.of(context).devicePixelRatio)
|
||||||
.bottom;
|
.bottom;
|
||||||
print('_keyboardHeight: $_keyboardHeight');
|
|
||||||
return Container(
|
return Container(
|
||||||
clipBehavior: Clip.hardEdge,
|
clipBehavior: Clip.hardEdge,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@ -236,11 +235,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
|||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: toolbarType == 'input'
|
height: toolbarType == 'input' ? keyboardHeight : emoteHeight,
|
||||||
? (_keyboardHeight > keyboardHeight
|
|
||||||
? _keyboardHeight
|
|
||||||
: keyboardHeight)
|
|
||||||
: emoteHeight,
|
|
||||||
child: EmotePanel(
|
child: EmotePanel(
|
||||||
onChoose: (package, emote) => onChooseEmote(package, emote),
|
onChoose: (package, emote) => onChooseEmote(package, emote),
|
||||||
),
|
),
|
||||||
|
@ -212,7 +212,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
videoIntroController.isPaused = true;
|
videoIntroController.isPaused = true;
|
||||||
plPlayerController!.removeStatusLister(playerListener);
|
plPlayerController!.removeStatusLister(playerListener);
|
||||||
plPlayerController!.pause();
|
plPlayerController!.pause();
|
||||||
vdCtr.clearSubtitleContent();
|
|
||||||
}
|
}
|
||||||
setState(() => isShowing = false);
|
setState(() => isShowing = false);
|
||||||
super.didPushNext();
|
super.didPushNext();
|
||||||
@ -223,10 +222,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
void didPopNext() async {
|
void didPopNext() async {
|
||||||
if (plPlayerController != null &&
|
if (plPlayerController != null &&
|
||||||
plPlayerController!.videoPlayerController != null) {
|
plPlayerController!.videoPlayerController != null) {
|
||||||
setState(() {
|
setState(() => isShowing = true);
|
||||||
vdCtr.setSubtitleContent();
|
|
||||||
isShowing = true;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
vdCtr.isFirstTime = false;
|
vdCtr.isFirstTime = false;
|
||||||
final bool autoplay = autoPlayEnable;
|
final bool autoplay = autoPlayEnable;
|
||||||
@ -372,13 +368,10 @@ 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',
|
||||||
|
@ -17,16 +17,12 @@ 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 + scrollDistance + kToolbarHeight + 0.5,
|
top: -videoHeight + scrollVal + kToolbarHeight + 0.5,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: Opacity(
|
child: Opacity(
|
||||||
opacity: scrollDistance / (videoHeight - kToolbarHeight),
|
opacity: scrollVal / (videoHeight - kToolbarHeight),
|
||||||
child: Container(
|
child: Container(
|
||||||
height: statusBarHeight + kToolbarHeight,
|
height: statusBarHeight + kToolbarHeight,
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
@ -32,14 +32,28 @@ 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.linear,
|
curve: Curves.fastOutSlowIn,
|
||||||
);
|
);
|
||||||
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() {
|
||||||
@ -53,9 +67,7 @@ class _ExpandedSectionState extends State<ExpandedSection>
|
|||||||
@override
|
@override
|
||||||
void didUpdateWidget(ExpandedSection oldWidget) {
|
void didUpdateWidget(ExpandedSection oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.expand != oldWidget.expand) {
|
_runExpandCheck();
|
||||||
_runExpandCheck();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -17,7 +17,6 @@ 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';
|
||||||
@ -53,7 +52,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;
|
||||||
RxBool isFullScreen = false.obs;
|
bool showTitle = false;
|
||||||
late String heroTag;
|
late String heroTag;
|
||||||
late VideoIntroController videoIntroController;
|
late VideoIntroController videoIntroController;
|
||||||
late VideoDetailData videoDetail;
|
late VideoDetailData videoDetail;
|
||||||
@ -70,8 +69,13 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void fullScreenStatusListener() {
|
void fullScreenStatusListener() {
|
||||||
widget.videoDetailCtr!.plPlayerController.isFullScreen.listen((bool val) {
|
widget.videoDetailCtr!.plPlayerController.isFullScreen
|
||||||
isFullScreen.value = val;
|
.listen((bool isFullScreen) {
|
||||||
|
if (isFullScreen) {
|
||||||
|
showTitle = true;
|
||||||
|
} else {
|
||||||
|
showTitle = false;
|
||||||
|
}
|
||||||
|
|
||||||
/// TODO setState() called after dispose()
|
/// TODO setState() called after dispose()
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
@ -214,87 +218,6 @@ 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 = [
|
||||||
@ -421,56 +344,6 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 选择字幕
|
|
||||||
void showSubtitleDialog() async {
|
|
||||||
int tempThemeValue = widget.controller!.subTitleCode.value;
|
|
||||||
int len = widget.videoDetailCtr!.subtitles.length;
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: const Text('选择字幕'),
|
|
||||||
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 18),
|
|
||||||
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
|
||||||
return len == 0
|
|
||||||
? const SizedBox(
|
|
||||||
height: 60,
|
|
||||||
child: Center(
|
|
||||||
child: Text('没有字幕'),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
RadioListTile(
|
|
||||||
value: -1,
|
|
||||||
title: const Text('关闭弹幕'),
|
|
||||||
groupValue: tempThemeValue,
|
|
||||||
onChanged: (value) {
|
|
||||||
tempThemeValue = value!;
|
|
||||||
widget.controller?.toggleSubtitle(value);
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
...widget.videoDetailCtr!.subtitles
|
|
||||||
.map((e) => RadioListTile(
|
|
||||||
value: e.code,
|
|
||||||
title: Text(e.title),
|
|
||||||
groupValue: tempThemeValue,
|
|
||||||
onChanged: (value) {
|
|
||||||
tempThemeValue = value!;
|
|
||||||
widget.controller?.toggleSubtitle(value);
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 选择倍速
|
/// 选择倍速
|
||||||
void showSetSpeedSheet() {
|
void showSetSpeedSheet() {
|
||||||
final double currentSpeed = widget.controller!.playbackSpeed;
|
final double currentSpeed = widget.controller!.playbackSpeed;
|
||||||
@ -1156,7 +1029,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
SizedBox(width: buttonSpace),
|
SizedBox(width: buttonSpace),
|
||||||
if (isFullScreen.value &&
|
if (showTitle &&
|
||||||
isLandscape &&
|
isLandscape &&
|
||||||
widget.videoType == SearchType.video) ...[
|
widget.videoType == SearchType.video) ...[
|
||||||
Column(
|
Column(
|
||||||
@ -1208,43 +1081,6 @@ 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(
|
||||||
@ -1279,31 +1115,6 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
),
|
),
|
||||||
SizedBox(width: buttonSpace),
|
SizedBox(width: buttonSpace),
|
||||||
],
|
],
|
||||||
|
|
||||||
/// 字幕
|
|
||||||
// SizedBox(
|
|
||||||
// width: 34,
|
|
||||||
// height: 34,
|
|
||||||
// child: IconButton(
|
|
||||||
// style: ButtonStyle(
|
|
||||||
// padding: MaterialStateProperty.all(EdgeInsets.zero),
|
|
||||||
// ),
|
|
||||||
// onPressed: () => showSubtitleDialog(),
|
|
||||||
// icon: const Icon(
|
|
||||||
// Icons.closed_caption_off,
|
|
||||||
// size: 22,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
ComBtn(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.closed_caption_off,
|
|
||||||
size: 22,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
fuc: () => showSubtitleDialog(),
|
|
||||||
),
|
|
||||||
SizedBox(width: buttonSpace),
|
|
||||||
Obx(
|
Obx(
|
||||||
() => SizedBox(
|
() => SizedBox(
|
||||||
width: 45,
|
width: 45,
|
||||||
|
@ -21,8 +21,6 @@ import 'package:pilipala/utils/storage.dart';
|
|||||||
import 'package:screen_brightness/screen_brightness.dart';
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
import 'package:status_bar_control/status_bar_control.dart';
|
import 'package:status_bar_control/status_bar_control.dart';
|
||||||
import 'package:universal_platform/universal_platform.dart';
|
import 'package:universal_platform/universal_platform.dart';
|
||||||
import '../../models/video/subTitile/content.dart';
|
|
||||||
import '../../models/video/subTitile/result.dart';
|
|
||||||
// import 'package:wakelock_plus/wakelock_plus.dart';
|
// import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
Box videoStorage = GStrorage.video;
|
Box videoStorage = GStrorage.video;
|
||||||
@ -75,8 +73,6 @@ class PlPlayerController {
|
|||||||
final Rx<bool> _doubleSpeedStatus = false.obs;
|
final Rx<bool> _doubleSpeedStatus = false.obs;
|
||||||
final Rx<bool> _controlsLock = false.obs;
|
final Rx<bool> _controlsLock = false.obs;
|
||||||
final Rx<bool> _isFullScreen = false.obs;
|
final Rx<bool> _isFullScreen = false.obs;
|
||||||
final Rx<bool> _subTitleOpen = false.obs;
|
|
||||||
final Rx<int> _subTitleCode = (-1).obs;
|
|
||||||
// 默认投稿视频格式
|
// 默认投稿视频格式
|
||||||
static Rx<String> _videoType = 'archive'.obs;
|
static Rx<String> _videoType = 'archive'.obs;
|
||||||
|
|
||||||
@ -122,7 +118,6 @@ class PlPlayerController {
|
|||||||
PreferredSizeWidget? headerControl;
|
PreferredSizeWidget? headerControl;
|
||||||
PreferredSizeWidget? bottomControl;
|
PreferredSizeWidget? bottomControl;
|
||||||
Widget? danmuWidget;
|
Widget? danmuWidget;
|
||||||
late RxList subtitles;
|
|
||||||
|
|
||||||
/// 数据加载监听
|
/// 数据加载监听
|
||||||
Stream<DataStatus> get onDataStatusChanged => dataStatus.status.stream;
|
Stream<DataStatus> get onDataStatusChanged => dataStatus.status.stream;
|
||||||
@ -152,11 +147,6 @@ class PlPlayerController {
|
|||||||
Rx<bool> get mute => _mute;
|
Rx<bool> get mute => _mute;
|
||||||
Stream<bool> get onMuteChanged => _mute.stream;
|
Stream<bool> get onMuteChanged => _mute.stream;
|
||||||
|
|
||||||
/// 字幕开启状态
|
|
||||||
Rx<bool> get subTitleOpen => _subTitleOpen;
|
|
||||||
Rx<int> get subTitleCode => _subTitleCode;
|
|
||||||
// Stream<bool> get onSubTitleOpenChanged => _subTitleOpen.stream;
|
|
||||||
|
|
||||||
/// [videoPlayerController] instace of Player
|
/// [videoPlayerController] instace of Player
|
||||||
Player? get videoPlayerController => _videoPlayerController;
|
Player? get videoPlayerController => _videoPlayerController;
|
||||||
|
|
||||||
@ -241,10 +231,6 @@ class PlPlayerController {
|
|||||||
// 播放顺序相关
|
// 播放顺序相关
|
||||||
PlayRepeat playRepeat = PlayRepeat.pause;
|
PlayRepeat playRepeat = PlayRepeat.pause;
|
||||||
|
|
||||||
RxList<SubTitileContentModel> subtitleContents =
|
|
||||||
<SubTitileContentModel>[].obs;
|
|
||||||
RxString subtitleContent = ''.obs;
|
|
||||||
|
|
||||||
void updateSliderPositionSecond() {
|
void updateSliderPositionSecond() {
|
||||||
int newSecond = _sliderPosition.value.inSeconds;
|
int newSecond = _sliderPosition.value.inSeconds;
|
||||||
if (sliderPositionSeconds.value != newSecond) {
|
if (sliderPositionSeconds.value != newSecond) {
|
||||||
@ -364,8 +350,6 @@ class PlPlayerController {
|
|||||||
bool enableHeart = true,
|
bool enableHeart = true,
|
||||||
// 是否首次加载
|
// 是否首次加载
|
||||||
bool isFirstTime = true,
|
bool isFirstTime = true,
|
||||||
// 是否开启字幕
|
|
||||||
bool enableSubTitle = false,
|
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
_autoPlay = autoplay;
|
_autoPlay = autoplay;
|
||||||
@ -380,9 +364,7 @@ class PlPlayerController {
|
|||||||
_cid = cid;
|
_cid = cid;
|
||||||
_enableHeart = enableHeart;
|
_enableHeart = enableHeart;
|
||||||
_isFirstTime = isFirstTime;
|
_isFirstTime = isFirstTime;
|
||||||
_subTitleOpen.value = enableSubTitle;
|
|
||||||
subtitles = [].obs;
|
|
||||||
subtitleContent.value = '';
|
|
||||||
if (_videoPlayerController != null &&
|
if (_videoPlayerController != null &&
|
||||||
_videoPlayerController!.state.playing) {
|
_videoPlayerController!.state.playing) {
|
||||||
await pause(notify: false);
|
await pause(notify: false);
|
||||||
@ -593,8 +575,6 @@ class PlPlayerController {
|
|||||||
_sliderPosition.value = event;
|
_sliderPosition.value = event;
|
||||||
updateSliderPositionSecond();
|
updateSliderPositionSecond();
|
||||||
}
|
}
|
||||||
querySubtitleContent(
|
|
||||||
videoPlayerController!.state.position.inSeconds.toDouble());
|
|
||||||
|
|
||||||
/// 触发回调事件
|
/// 触发回调事件
|
||||||
for (var element in _positionListeners) {
|
for (var element in _positionListeners) {
|
||||||
@ -629,10 +609,6 @@ class PlPlayerController {
|
|||||||
const Duration(seconds: 1),
|
const Duration(seconds: 1),
|
||||||
() => videoPlayerServiceHandler.onPositionChange(event));
|
() => videoPlayerServiceHandler.onPositionChange(event));
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// onSubTitleOpenChanged.listen((bool event) {
|
|
||||||
// toggleSubtitle(event ? subTitleCode.value : -1);
|
|
||||||
// })
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1071,61 +1047,12 @@ class PlPlayerController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 字幕
|
|
||||||
void toggleSubtitle(int code) {
|
|
||||||
_subTitleOpen.value = code != -1;
|
|
||||||
_subTitleCode.value = code;
|
|
||||||
// if (code == -1) {
|
|
||||||
// // 关闭字幕
|
|
||||||
// _subTitleOpen.value = false;
|
|
||||||
// _subTitleCode.value = code;
|
|
||||||
// _videoPlayerController?.setSubtitleTrack(SubtitleTrack.no());
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// final SubTitlteItemModel? subtitle = subtitles?.firstWhereOrNull(
|
|
||||||
// (element) => element.code == code,
|
|
||||||
// );
|
|
||||||
// _subTitleOpen.value = true;
|
|
||||||
// _subTitleCode.value = code;
|
|
||||||
// _videoPlayerController?.setSubtitleTrack(
|
|
||||||
// SubtitleTrack.data(
|
|
||||||
// subtitle!.content!,
|
|
||||||
// title: subtitle.title,
|
|
||||||
// language: subtitle.lan,
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
}
|
|
||||||
|
|
||||||
void querySubtitleContent(double progress) {
|
|
||||||
if (subTitleCode.value == -1) {
|
|
||||||
subtitleContent.value = '';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (subtitles.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final SubTitlteItemModel? subtitle = subtitles.firstWhereOrNull(
|
|
||||||
(element) => element.code == subTitleCode.value,
|
|
||||||
);
|
|
||||||
if (subtitle != null && subtitle.body!.isNotEmpty) {
|
|
||||||
for (var content in subtitle.body!) {
|
|
||||||
if (progress >= content['from']! && progress <= content['to']!) {
|
|
||||||
subtitleContent.value = content['content']!;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPlayRepeat(PlayRepeat type) {
|
setPlayRepeat(PlayRepeat type) {
|
||||||
playRepeat = type;
|
playRepeat = type;
|
||||||
videoStorage.put(VideoBoxKey.playRepeat, type.value);
|
videoStorage.put(VideoBoxKey.playRepeat, type.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> dispose({String type = 'single'}) async {
|
Future<void> dispose({String type = 'single'}) async {
|
||||||
print('dispose');
|
|
||||||
print('dispose: ${playerCount.value}');
|
|
||||||
|
|
||||||
// 每次减1,最后销毁
|
// 每次减1,最后销毁
|
||||||
if (type == 'single' && playerCount.value > 1) {
|
if (type == 'single' && playerCount.value > 1) {
|
||||||
_playerCount.value -= 1;
|
_playerCount.value -= 1;
|
||||||
@ -1135,7 +1062,6 @@ class PlPlayerController {
|
|||||||
}
|
}
|
||||||
_playerCount.value = 0;
|
_playerCount.value = 0;
|
||||||
try {
|
try {
|
||||||
print('dispose dispose ---------');
|
|
||||||
_timer?.cancel();
|
_timer?.cancel();
|
||||||
_timerForVolume?.cancel();
|
_timerForVolume?.cancel();
|
||||||
_timerForGettingVolume?.cancel();
|
_timerForGettingVolume?.cancel();
|
||||||
|
@ -580,45 +580,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
if (widget.danmuWidget != null)
|
if (widget.danmuWidget != null)
|
||||||
Positioned.fill(top: 4, child: widget.danmuWidget!),
|
Positioned.fill(top: 4, child: widget.danmuWidget!),
|
||||||
|
|
||||||
/// 开启且有字幕时展示
|
|
||||||
Stack(
|
|
||||||
children: [
|
|
||||||
Positioned(
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 30,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: Obx(
|
|
||||||
() => Visibility(
|
|
||||||
visible: widget.controller.subTitleCode.value != -1,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
color: widget.controller.subtitleContent.value != ''
|
|
||||||
? Colors.black.withOpacity(0.6)
|
|
||||||
: Colors.transparent,
|
|
||||||
),
|
|
||||||
padding: widget.controller.subTitleCode.value != -1
|
|
||||||
? const EdgeInsets.symmetric(
|
|
||||||
horizontal: 10,
|
|
||||||
vertical: 4,
|
|
||||||
)
|
|
||||||
: EdgeInsets.zero,
|
|
||||||
child: Text(
|
|
||||||
widget.controller.subtitleContent.value,
|
|
||||||
style: const TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
|
|
||||||
/// 手势
|
/// 手势
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
left: 16,
|
left: 16,
|
||||||
|
@ -19,7 +19,15 @@ class Em {
|
|||||||
return regCate(matchStr);
|
return regCate(matchStr);
|
||||||
}, onNonMatch: (String str) {
|
}, onNonMatch: (String str) {
|
||||||
if (str != '') {
|
if (str != '') {
|
||||||
str = decodeHtmlEntities(str);
|
str = str
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll(''', "'")
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll(''', "'")
|
||||||
|
.replaceAll(' ', " ")
|
||||||
|
.replaceAll('&', "&");
|
||||||
Map map = {'type': 'text', 'text': str};
|
Map map = {'type': 'text', 'text': str};
|
||||||
res.add(map);
|
res.add(map);
|
||||||
}
|
}
|
||||||
@ -27,17 +35,4 @@ class Em {
|
|||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String decodeHtmlEntities(String title) {
|
|
||||||
return title
|
|
||||||
.replaceAll('<', '<')
|
|
||||||
.replaceAll('>', '>')
|
|
||||||
.replaceAll('"', '"')
|
|
||||||
.replaceAll(''', "'")
|
|
||||||
.replaceAll('"', '"')
|
|
||||||
.replaceAll(''', "'")
|
|
||||||
.replaceAll(' ', " ")
|
|
||||||
.replaceAll('&', "&")
|
|
||||||
.replaceAll(''', "'");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
class SubTitleUtils {
|
|
||||||
// 格式整理
|
|
||||||
static String convertToWebVTT(List jsonData) {
|
|
||||||
String webVTTContent = 'WEBVTT FILE\n\n';
|
|
||||||
|
|
||||||
for (int i = 0; i < jsonData.length; i++) {
|
|
||||||
final item = jsonData[i];
|
|
||||||
double from = item['from'] as double;
|
|
||||||
double to = item['to'] as double;
|
|
||||||
int sid = (item['sid'] ?? 0) as int;
|
|
||||||
String content = item['content'] as String;
|
|
||||||
|
|
||||||
webVTTContent += '$sid\n';
|
|
||||||
webVTTContent += '${formatTime(from)} --> ${formatTime(to)}\n';
|
|
||||||
webVTTContent += '$content\n\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
return webVTTContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
static String formatTime(num seconds) {
|
|
||||||
final String h = (seconds / 3600).floor().toString().padLeft(2, '0');
|
|
||||||
final String m = (seconds % 3600 / 60).floor().toString().padLeft(2, '0');
|
|
||||||
final String s = (seconds % 60).floor().toString().padLeft(2, '0');
|
|
||||||
final String ms =
|
|
||||||
(seconds * 1000 % 1000).floor().toString().padLeft(3, '0');
|
|
||||||
if (h == '00') {
|
|
||||||
return "$m:$s.$ms";
|
|
||||||
}
|
|
||||||
return "$h:$m:$s.$ms";
|
|
||||||
}
|
|
||||||
}
|
|
@ -51,7 +51,7 @@ class Utils {
|
|||||||
}
|
}
|
||||||
if (time < 3600) {
|
if (time < 3600) {
|
||||||
if (time == 0) {
|
if (time == 0) {
|
||||||
return '00:00';
|
return time;
|
||||||
}
|
}
|
||||||
final int minute = time ~/ 60;
|
final int minute = time ~/ 60;
|
||||||
final double res = time / 60;
|
final double res = time / 60;
|
||||||
@ -208,35 +208,17 @@ class Utils {
|
|||||||
|
|
||||||
static int findClosestNumber(int target, List<int> numbers) {
|
static int findClosestNumber(int target, List<int> numbers) {
|
||||||
int minDiff = 127;
|
int minDiff = 127;
|
||||||
int closestNumber = 0; // 初始化为0,表示没有找到比目标值小的整数
|
late int closestNumber;
|
||||||
|
|
||||||
// 向下查找
|
|
||||||
try {
|
try {
|
||||||
for (int number in numbers) {
|
for (int number in numbers) {
|
||||||
if (number < target) {
|
int diff = (number - target).abs();
|
||||||
int diff = target - number; // 计算目标值与当前整数的差值
|
|
||||||
|
|
||||||
if (diff < minDiff) {
|
if (diff < minDiff) {
|
||||||
minDiff = diff;
|
minDiff = diff;
|
||||||
closestNumber = number;
|
closestNumber = number;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
// 向上查找
|
|
||||||
if (closestNumber == 0) {
|
|
||||||
try {
|
|
||||||
for (int number in numbers) {
|
|
||||||
int diff = (number - target).abs();
|
|
||||||
|
|
||||||
if (diff < minDiff) {
|
|
||||||
minDiff = diff;
|
|
||||||
closestNumber = number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
return closestNumber;
|
return closestNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -433,14 +433,6 @@ 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:
|
||||||
|
@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
# In Windows, build-name is used as the major, minor, and patch parts
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
# of the product and file versions while build-number is used as the build suffix.
|
||||||
version: 1.0.23+1023
|
version: 1.0.21+1021
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
@ -142,8 +142,6 @@ 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