Compare commits

...

29 Commits

Author SHA1 Message Date
53d4379bb9 v1.0.5 更新 2023-08-26 15:51:14 +08:00
a928c575ef fix: supportFormats codecs match dvh1 2023-08-26 15:23:22 +08:00
a0e51c86fc Merge branch 'design' 2023-08-26 12:38:54 +08:00
d670b8123a feat: 下载对应版本apk 2023-08-26 12:38:31 +08:00
2621b096ac Merge branch 'main' of github.com:guozhigq/pilipala 2023-08-26 11:33:40 +08:00
05631f7803 fix: 大会员切换番剧 2023-08-26 11:33:10 +08:00
495ba57ca8 Update README.md 2023-08-26 10:56:47 +08:00
8990c4ae92 feat: 高帧率 2023-08-26 10:51:14 +08:00
8bc6a32b06 feat: 播放器亮度记忆 2023-08-25 23:51:40 +08:00
6083578f93 fix: 评论渲染异常、评论总数 2023-08-25 21:16:16 +08:00
aa7419f352 Merge branch 'design' 2023-08-25 13:56:08 +08:00
c90a6cd86c feat: 增加iOS路由切换效果 2023-08-25 13:55:46 +08:00
2e04c27292 Merge branch 'design' 2023-08-25 10:21:49 +08:00
5741f80536 feat: 动态合集查看 2023-08-25 10:21:32 +08:00
161ba1c313 mod: 倍速选择 2023-08-25 10:07:00 +08:00
5d9dc6c1a9 Merge branch 'fix' 2023-08-24 22:02:06 +08:00
1abe70d4d4 fix: 重复进入个人中心页面数据未刷新 2023-08-24 22:01:48 +08:00
5fc959eb59 feat: #30 动态默认展示某项 2023-08-24 21:31:19 +08:00
6322b29aef Merge branch 'main' into design 2023-08-24 15:31:06 +08:00
6461f72b5e fix: #28 合集数据渲染错误 2023-08-24 15:24:56 +08:00
e3d561bffd fix: 快速返回首页&销毁播放器 2023-08-24 14:22:15 +08:00
535cf69967 feat: 评论默认排序 2023-08-24 12:23:38 +08:00
4314b0fc3c fix: 首页搜索框频繁点击消失、评论排序切换空白 2023-08-24 09:54:24 +08:00
9da113726b fix: 小白条、导航栏沉浸 2023-08-24 09:25:32 +08:00
201422c150 feat: 同时在看人数 2023-08-24 08:38:01 +08:00
b67127123a fix: 动态goods数据异常 2023-08-24 07:48:24 +08:00
3ce7578183 fix: 收藏夹翻页 2023-08-24 07:25:55 +08:00
7b60cc2666 Update README.md 2023-08-22 22:46:05 +08:00
ead24e90fb Add files via upload 2023-08-22 22:44:43 +08:00
41 changed files with 809 additions and 238 deletions

View File

@ -11,10 +11,11 @@
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/174shots_so.png" width="32%" alt="home" />
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/850shots_so.png" width="32%" alt="home" />
<br/>
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/main_screen.png" width="96%" alt="home" />
<br/>
</div>
### 开发环境
## 开发环境
Xcode 13.4 不支持**auto_orientation**,请注释相关代码
```bash
@ -30,7 +31,7 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
<br/>
### 功能
## 功能
目前着重移动端(Android、iOS)暂时没有适配桌面端、Pad端、手表端等
@ -98,17 +99,18 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
- [x] 图片质量设定
- [x] 主题模式:亮色/暗色/跟随系统
- [x] 震动反馈(可选)
- [x] 高帧率
- [ ] 等等
<br/>
### 下载
## 下载
可以通过右侧release进行下载或拉取代码到本地进行编译
<br/>
### 声明
## 声明
此项目PiliPala是个人为了兴趣而开发, 仅用于学习和测试。
所用API皆从官方网站收集, 不提供任何破解内容。
@ -117,7 +119,13 @@ Xcode 13.4 不支持**auto_orientation**,请注释相关代码
<br/>
### 致谢
## 技术交流
Telegram https://t.me/+lm_oOVmF0RJiODk1
<br/>
## 致谢
- [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
- [flutter_meedu_videoplayer](https://github.com/zezo357/flutter_meedu_videoplayer)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

30
change_log/1.0.5.0826.md Normal file
View File

@ -0,0 +1,30 @@
## 1.0.5
主要是bug修复跟一部分小功能弹幕功能需要下一版。
问题反馈请前往QQ频道或提交issues。
感谢🙏酷友「无力感*」「斤斤计较呀」「Pseudopamine」
### 新功能
+ 高帧率支持
+ 默认评论排序设置
+ 默认动态类别设置
+ 动态合集查看
+ 同时观看人数
+ iOS路由切换效果
### 修复
+ 收藏夹翻页
+ 首页搜索框频繁点击消失
+ 评论排序切换空白
+ 快速返回首页
+ 重复进入个人中心页面数据未刷新
+ 动态goods数据异常
+ 大会员切换番剧
+ 高画质codes匹配
### 优化
+ 倍速选择
+ 播放器亮度记忆
+ 下载对应版本apk

View File

@ -288,4 +288,8 @@ class Api {
// github 获取最新版
static const String latestApp =
'https://api.github.com/repos/guozhigq/pilipala/releases/latest';
// 多少人在看
// https://api.bilibili.com/x/player/online/total?aid=913663681&cid=1203559746&bvid=BV1MM4y1s7NZ&ts=56427838
static const String onlineTotal = '/x/player/online/total';
}

View File

@ -22,10 +22,18 @@ class DynamicsHttp {
}
var res = await Request().get(Api.followDynamic, data: data);
if (res.data['code'] == 0) {
return {
'status': true,
'data': DynamicsDataModel.fromJson(res.data['data']),
};
try {
return {
'status': true,
'data': DynamicsDataModel.fromJson(res.data['data']),
};
} catch (err) {
return {
'status': false,
'data': [],
'msg': err.toString(),
};
}
} else {
return {
'status': false,

View File

@ -51,10 +51,17 @@ class UserHttp {
'up_mid': mid,
});
if (res.data['code'] == 0) {
FavFolderData data = FavFolderData.fromJson(res.data['data']);
return {'status': true, 'data': data};
late FavFolderData data;
if (res.data['data'] != null) {
data = FavFolderData.fromJson(res.data['data']);
return {'status': true, 'data': data};
}
} else {
return {'status': false, 'data': [], 'msg': '账号未登录'};
return {
'status': false,
'data': [],
'msg': res.data['message'] ?? '账号未登录'
};
}
}

View File

@ -399,4 +399,16 @@ class VideoHttp {
return {'status': false, 'msg': res.data['result']['toast']};
}
}
// 查看视频同时在看人数
static Future onlineTotal({int? aid, String? bvid, int? cid}) async {
var res = await Request().get(Api.onlineTotal, data: {
'aid': aid,
'bvid': bvid,
'cid': cid,
});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
}
}
}

View File

@ -28,6 +28,13 @@ void main() async {
await Request.setCookie();
await Data.init();
await GStrorage.lazyInit();
// 小白条、导航栏沉浸
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent,
systemNavigationBarDividerColor: Colors.transparent,
statusBarColor: Colors.transparent,
));
});
}

View File

@ -360,7 +360,7 @@ class GoodItem {
String? brief;
String? cover;
String? id;
dynamic id;
String? jumpDesc;
String? jumpUrl;
String? name;

View File

@ -93,26 +93,19 @@ extension AudioQualityDesc on AudioQuality {
}
enum VideoDecodeFormats {
DVH1,
AV1,
HEVC,
AVC,
}
extension VideoDecodeFormatsDesc on VideoDecodeFormats {
static final List<String> _descList = [
'AV1',
'HEVC',
'AVC',
];
static final List<String> _descList = ['DVH1', 'AV1', 'HEVC', 'AVC'];
get description => _descList[index];
}
extension VideoDecodeFormatsCode on VideoDecodeFormats {
static final List<String> _codeList = [
'av01',
'hev1',
'avc1',
];
static final List<String> _codeList = ['dvh1', 'av01', 'hev1', 'avc1'];
get code => _codeList[index];
static VideoDecodeFormats? fromCode(String code) {

View File

@ -47,6 +47,11 @@ class _AboutPageState extends State<AboutPage> {
'PiliPala',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 6),
Text(
'使用Flutter开发的哔哩哔哩第三方客户端',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
const SizedBox(height: 20),
Obx(
() => ListTile(
@ -82,16 +87,6 @@ class _AboutPageState extends State<AboutPage> {
height: 30,
color: Theme.of(context).colorScheme.onInverseSurface,
),
ListTile(
onTap: () {},
title: const Text('作者'),
trailing: Text('guozhigq', style: subTitleStyle),
),
ListTile(
onTap: () {},
title: const Text('酷安'),
trailing: Text('影若风', style: subTitleStyle),
),
ListTile(
onTap: () => _aboutController.githubUrl(),
title: const Text('Github'),
@ -123,6 +118,11 @@ class _AboutPageState extends State<AboutPage> {
title: const Text('TG频道'),
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
),
ListTile(
onTap: () => _aboutController.aPay(),
title: const Text('赞助'),
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
),
Divider(
thickness: 8,
height: 30,
@ -141,11 +141,12 @@ class AboutController extends GetxController {
late LatestDataModel remoteAppInfo;
RxBool isUpdate = true.obs;
RxBool isLoading = true.obs;
late LatestDataModel data;
@override
void onInit() {
super.onInit();
init();
// init();
// 获取当前版本
getCurrentApp();
// 获取最新的版本
@ -153,16 +154,16 @@ class AboutController extends GetxController {
}
// 获取设备信息
Future init() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
print(androidInfo.supportedAbis);
} else if (Platform.isIOS) {
IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
print(iosInfo);
}
}
// Future init() async {
// DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
// if (Platform.isAndroid) {
// AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
// print(androidInfo.supportedAbis);
// } else if (Platform.isIOS) {
// IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
// print(iosInfo);
// }
// }
// 获取当前版本
Future getCurrentApp() async {
@ -173,7 +174,7 @@ class AboutController extends GetxController {
// 获取远程版本
Future getRemoteApp() async {
var result = await Request().get(Api.latestApp);
LatestDataModel data = LatestDataModel.fromJson(result.data);
data = LatestDataModel.fromJson(result.data);
remoteAppInfo = data;
remoteVersion.value = data.tagName!;
isUpdate.value =
@ -183,15 +184,7 @@ class AboutController extends GetxController {
// 跳转下载/本地更新
Future onUpdate() async {
// final dir = await getApplicationSupportDirectory();
// final path = '${dir.path}/pilipala.apk';
// var result = await Request()
// .downloadFile(remoteAppInfo.assets!.first.downloadUrl, path);
// print(result);
launchUrl(
Uri.parse('https://github.com/guozhigq/pilipala/releases'),
mode: LaunchMode.externalApplication,
);
Utils.matchVersion(data);
}
// 跳转github
@ -242,4 +235,16 @@ class AboutController extends GetxController {
),
);
}
aPay() {
try {
launchUrl(
Uri.parse(
'alipayqr://platformapi/startapp?saId=10000007&qrcode=https://qr.alipay.com/fkx14623ddwl1ping3ddd73'),
mode: LaunchMode.externalApplication,
);
} catch (e) {
print(e);
}
}
}

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/bangumi/info.dart';
import 'package:pilipala/utils/storage.dart';
class BangumiPanel extends StatefulWidget {
final List<EpisodeItem> pages;
@ -24,12 +26,20 @@ class _BangumiPanelState extends State<BangumiPanel> {
late int currentIndex;
final ScrollController listViewScrollCtr = ScrollController();
final ScrollController listViewScrollCtr_2 = ScrollController();
Box userInfoCache = GStrorage.userInfo;
dynamic userInfo;
// 默认未开通
int vipStatus = 0;
@override
void initState() {
super.initState();
currentIndex = widget.pages.indexWhere((e) => e.cid == widget.cid!);
scrollToIndex();
userInfo = userInfoCache.get('userInfoCache');
if (userInfo != null) {
vipStatus = userInfo.vipStatus;
}
}
@override
@ -126,7 +136,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
}
void changeFucCall(item, i) async {
if (item.badge != null) {
if (item.badge != null && vipStatus != 1) {
SmartDialog.showToast('需要大会员');
return;
}

View File

@ -13,6 +13,7 @@ import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/models/dynamics/up.dart';
import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
@ -50,17 +51,21 @@ class DynamicsController extends GetxController {
},
];
bool flag = false;
RxInt initialValue = 1.obs;
RxInt initialValue = 0.obs;
Box userInfoCache = GStrorage.userInfo;
RxBool userLogin = false.obs;
var userInfo;
RxBool isLoadingDynamic = false.obs;
Box setting = GStrorage.setting;
@override
void onInit() {
userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null;
super.onInit();
initialValue.value =
setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0);
dynamicsType = DynamicsType.values[initialValue.value].obs;
}
Future queryFollowDynamic({type = 'init'}) async {
@ -99,7 +104,7 @@ class DynamicsController extends GetxController {
}
onSelectType(value) async {
dynamicsType.value = filterTypeList[value - 1]['value'];
dynamicsType.value = filterTypeList[value]['value'];
dynamicsList.value = [DynamicItemModel()];
page = 1;
initialValue.value = value;
@ -109,16 +114,21 @@ class DynamicsController extends GetxController {
pushDetail(item, floor, {action = 'all'}) async {
feedBack();
/// 点击评论action 直接查看评论
if (action == 'comment') {
Get.toNamed('/dynamicDetail',
arguments: {'item': item, 'floor': floor, 'action': action});
return false;
}
switch (item!.type) {
/// 转发的动态
case 'DYNAMIC_TYPE_FORWARD':
Get.toNamed('/dynamicDetail',
arguments: {'item': item, 'floor': floor});
break;
/// 图文动态查看
case 'DYNAMIC_TYPE_DRAW':
Get.toNamed('/dynamicDetail',
arguments: {'item': item, 'floor': floor});
@ -134,6 +144,8 @@ class DynamicsController extends GetxController {
SmartDialog.showToast(err.toString());
}
break;
/// 专栏文章查看
case 'DYNAMIC_TYPE_ARTICLE':
String title = item.modules.moduleDynamic.major.opus.title;
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
@ -144,7 +156,10 @@ class DynamicsController extends GetxController {
break;
case 'DYNAMIC_TYPE_PGC':
print('番剧');
SmartDialog.showToast('暂未支持的类型,请联系开发者');
break;
/// 纯文字动态查看
case 'DYNAMIC_TYPE_WORD':
print('纯文本');
Get.toNamed('/dynamicDetail',
@ -168,10 +183,19 @@ class DynamicsController extends GetxController {
});
break;
/// TODO
/// 合集查看
case 'DYNAMIC_TYPE_UGC_SEASON':
print('合集');
DynamicArchiveModel ugcSeason =
item.modules.moduleDynamic.major.ugcSeason;
int aid = ugcSeason.aid!;
String bvid = IdUtils.av2bv(aid);
String cover = ugcSeason.cover!;
int cid = await SearchHttp.ab2c(bvid: bvid);
Get.toNamed('/video?bvid=$bvid&cid=$cid',
arguments: {'pic': cover, 'heroTag': bvid});
break;
/// 番剧查看
case 'DYNAMIC_TYPE_PGC_UNION':
print('DYNAMIC_TYPE_PGC_UNION 番剧');
DynamicArchiveModel pgc = item.modules.moduleDynamic.major.pgc;
@ -247,7 +271,7 @@ class DynamicsController extends GetxController {
void resetSearch() {
mid.value = -1;
dynamicsType.value = DynamicsType.values[0];
initialValue.value = 1;
initialValue.value = 0;
SmartDialog.showToast('还原默认加载');
dynamicsList.value = [DynamicItemModel()];
queryFollowDynamic();

View File

@ -1,8 +1,10 @@
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/reply.dart';
import 'package:pilipala/models/common/reply_sort_type.dart';
import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
class DynamicDetailController extends GetxController {
DynamicDetailController(this.oid, this.type);
@ -16,9 +18,10 @@ class DynamicDetailController extends GetxController {
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
RxInt acount = 0.obs;
ReplySortType sortType = ReplySortType.time;
ReplySortType _sortType = ReplySortType.time;
RxString sortTypeTitle = ReplySortType.time.titles.obs;
RxString sortTypeLabel = ReplySortType.time.labels.obs;
Box setting = GStrorage.setting;
@override
void onInit() {
@ -29,6 +32,11 @@ class DynamicDetailController extends GetxController {
acount.value =
int.parse(item!.modules!.moduleStat!.comment!.count ?? '0');
}
int deaultReplySortIndex =
setting.get(SettingBoxKey.replySortType, defaultValue: 0);
_sortType = ReplySortType.values[deaultReplySortIndex];
sortTypeTitle.value = _sortType.titles;
sortTypeLabel.value = _sortType.labels;
}
Future queryReplyList({reqType = 'init'}) async {
@ -39,7 +47,7 @@ class DynamicDetailController extends GetxController {
oid: oid!,
pageNum: currentPage + 1,
type: type!,
sort: sortType.index,
sort: _sortType.index,
);
if (res['status']) {
List<ReplyItemModel> replies = res['data'].replies;
@ -76,20 +84,20 @@ class DynamicDetailController extends GetxController {
// 排序搜索评论
queryBySort() {
feedBack();
switch (sortType) {
switch (_sortType) {
case ReplySortType.time:
sortType = ReplySortType.like;
_sortType = ReplySortType.like;
break;
case ReplySortType.like:
sortType = ReplySortType.reply;
_sortType = ReplySortType.reply;
break;
case ReplySortType.reply:
sortType = ReplySortType.time;
_sortType = ReplySortType.time;
break;
default:
}
sortTypeTitle.value = sortType.titles;
sortTypeLabel.value = sortType.labels;
sortTypeTitle.value = _sortType.titles;
sortTypeLabel.value = _sortType.labels;
replyList.clear();
queryReplyList(reqType: 'init');
}

View File

@ -132,7 +132,7 @@ class _DynamicsPageState extends State<DynamicsPage>
initialValue:
_dynamicsController.initialValue.value,
children: {
1: Text(
0: Text(
'全部',
style: TextStyle(
fontSize: Theme.of(context)
@ -140,19 +140,19 @@ class _DynamicsPageState extends State<DynamicsPage>
.labelMedium!
.fontSize),
),
2: Text('投稿',
1: Text('投稿',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize)),
3: Text('番剧',
2: Text('番剧',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.labelMedium!
.fontSize)),
4: Text('专栏',
3: Text('专栏',
style: TextStyle(
fontSize: Theme.of(context)
.textTheme

View File

@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/user.dart';
@ -6,23 +8,45 @@ import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/utils/storage.dart';
class FavController extends GetxController {
final ScrollController scrollController = ScrollController();
Rx<FavFolderData> favFolderData = FavFolderData().obs;
Box userInfoCache = GStrorage.userInfo;
UserInfoData? userInfo;
int currentPage = 1;
int pageSize = 10;
RxBool hasMore = true.obs;
Future<dynamic> queryFavFolder() async {
Future<dynamic> queryFavFolder({type = 'init'}) async {
userInfo = userInfoCache.get('userInfoCache');
if (userInfo == null) {
return {'status': false, 'msg': '账号未登录'};
}
if (!hasMore.value) {
return;
}
var res = await await UserHttp.userfavFolder(
pn: 1,
ps: 10,
pn: currentPage,
ps: pageSize,
mid: userInfo!.mid!,
);
if (res['status']) {
favFolderData.value = res['data'];
if (type == 'init') {
favFolderData.value = res['data'];
} else {
if (res['data'].list.isNotEmpty) {
favFolderData.value.list!.addAll(res['data'].list);
favFolderData.update((val) {});
}
}
hasMore.value = res['data'].hasMore;
currentPage++;
} else {
SmartDialog.showToast(res['msg']);
}
return res;
}
Future onLoad() async {
queryFavFolder(type: 'onload');
}
}

View File

@ -1,3 +1,4 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart';
@ -14,11 +15,23 @@ class FavPage extends StatefulWidget {
class _FavPageState extends State<FavPage> {
final FavController _favController = Get.put(FavController());
late Future _futureBuilderFuture;
late ScrollController scrollController;
@override
void initState() {
super.initState();
_futureBuilderFuture = _favController.queryFavFolder();
scrollController = _favController.scrollController;
scrollController.addListener(
() {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 300) {
EasyThrottle.throttle('history', const Duration(seconds: 1), () {
_favController.onLoad();
});
}
},
);
}
@override
@ -40,6 +53,7 @@ class _FavPageState extends State<FavPage> {
if (data['status']) {
return Obx(
() => ListView.builder(
controller: scrollController,
itemCount: _favController.favFolderData.value.list!.length,
itemBuilder: (context, index) {
return FavItem(

View File

@ -142,7 +142,8 @@ class _ArchivePanelState extends State<ArchivePanel>
}
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
final ArchiveController _archiveController = Get.put(ArchiveController());
final ArchiveController _archiveController =
Get.put(ArchiveController(), tag: Get.arguments['heroTag']);
@override
Future<bool> loadData([bool isloadMoreAction = false]) async {

View File

@ -118,7 +118,8 @@ class _MemberDynamicPanelState extends State<MemberDynamicPanel>
}
class LoadMoreListSource extends LoadingMoreBase<DynamicItemModel> {
final _dynamicController = Get.put(MemberDynamicPanelController());
final _dynamicController =
Get.put(MemberDynamicPanelController(), tag: Get.arguments['heroTag']);
@override
Future<bool> loadData([bool isloadMoreAction = false]) async {

View File

@ -45,11 +45,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
return OpenContainer(
closedElevation: 0,
openElevation: 0,
onClosed: (_) async {
// 在 openBuilder 关闭时触发的回调函数
await Future.delayed(const Duration(milliseconds: 500));
_searchController.onClear();
},
onClosed: (_) => _searchController.onClear(),
openColor: Theme.of(context).colorScheme.background,
middleColor: Theme.of(context).colorScheme.background,
closedColor: Theme.of(context).colorScheme.background,

View File

@ -1,4 +1,7 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/common/dynamics_type.dart';
import 'package:pilipala/models/common/reply_sort_type.dart';
import 'package:pilipala/utils/storage.dart';
import 'widgets/switch_item.dart';
@ -11,8 +14,28 @@ class ExtraSetting extends StatefulWidget {
}
class _ExtraSettingState extends State<ExtraSetting> {
Box setting = GStrorage.setting;
late dynamic defaultReplySort;
late dynamic defaultDynamicType;
@override
void initState() {
super.initState();
// 默认优先显示最新评论
defaultReplySort =
setting.get(SettingBoxKey.replySortType, defaultValue: 0);
// 优先展示全部动态 all
defaultDynamicType =
setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0);
}
@override
Widget build(BuildContext context) {
TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!;
TextStyle subTitleStyle = Theme.of(context)
.textTheme
.labelMedium!
.copyWith(color: Theme.of(context).colorScheme.outline);
return Scaffold(
appBar: AppBar(
centerTitle: false,
@ -23,8 +46,58 @@ class _ExtraSettingState extends State<ExtraSetting> {
),
),
body: ListView(
children: const [
SetSwitchItem(
children: [
ListTile(
dense: false,
title: Text('评论展示', style: titleStyle),
subtitle: Text(
'当前优先展示「${ReplySortType.values[defaultReplySort].titles}',
style: subTitleStyle,
),
trailing: PopupMenuButton(
initialValue: defaultReplySort,
icon: const Icon(Icons.more_vert_outlined, size: 22),
onSelected: (item) {
defaultReplySort = item;
setting.put(SettingBoxKey.replySortType, item);
setState(() {});
},
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
for (var i in ReplySortType.values) ...[
PopupMenuItem(
value: i.index,
child: Text(i.titles),
),
]
],
),
),
ListTile(
dense: false,
title: Text('动态展示', style: titleStyle),
subtitle: Text(
'当前优先展示「${DynamicsType.values[defaultDynamicType].labels}',
style: subTitleStyle,
),
trailing: PopupMenuButton(
initialValue: defaultDynamicType,
icon: const Icon(Icons.more_vert_outlined, size: 22),
onSelected: (item) {
defaultDynamicType = item;
setting.put(SettingBoxKey.defaultDynamicType, item);
setState(() {});
},
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
for (var i in DynamicsType.values) ...[
PopupMenuItem(
value: i.index,
child: Text(i.labels),
),
]
],
),
),
const SetSwitchItem(
title: '检查更新',
subTitle: '每次启动时检查是否需要更新',
setKey: SettingBoxKey.autoUpdate,

View File

@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
class SetDiaplayMode extends StatefulWidget {
const SetDiaplayMode({super.key});
@override
State<SetDiaplayMode> createState() => _SetDiaplayModeState();
}
class _SetDiaplayModeState extends State<SetDiaplayMode> {
List<DisplayMode> modes = <DisplayMode>[];
DisplayMode? active;
DisplayMode? preferred;
final ValueNotifier<int> page = ValueNotifier<int>(0);
late final PageController controller = PageController()
..addListener(() {
page.value = controller.page!.round();
});
@override
void initState() {
super.initState();
init();
SchedulerBinding.instance.addPostFrameCallback((_) {
fetchAll();
});
}
Future<void> fetchAll() async {
preferred = await FlutterDisplayMode.preferred;
active = await FlutterDisplayMode.active;
// GStorage().setDisplayModeType(preferred!);
setState(() {});
}
Future<void> init() async {
try {
modes = await FlutterDisplayMode.supported;
} on PlatformException catch (e) {
print(e);
}
// var res = await GStorage().getDisplayModeType();
// preferred = modes.toList().firstWhere((el) => el == res);
FlutterDisplayMode.setPreferredMode(preferred!);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('屏幕帧率设置')),
body: SafeArea(
top: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (modes.isEmpty) const Text('Nothing here'),
Padding(
padding: const EdgeInsets.only(left: 25, top: 10, bottom: 5),
child: Text(
'没有生效重启app试试',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
Expanded(
child: ListView.builder(
itemCount: modes.length,
itemBuilder: (_, int i) {
final DisplayMode mode = modes[i];
return RadioListTile<DisplayMode>(
value: mode,
title: mode == DisplayMode.auto
? const Text('自动')
: Text('$mode${mode == active ? " [系统]" : ""}'),
groupValue: preferred,
onChanged: (DisplayMode? newMode) async {
await FlutterDisplayMode.setPreferredMode(newMode!);
await Future<dynamic>.delayed(
const Duration(milliseconds: 100),
);
await fetchAll();
},
);
},
),
),
],
),
),
);
}
}

View File

@ -66,6 +66,18 @@ class _PlaySettingState extends State<PlaySetting> {
setKey: SettingBoxKey.enableHA,
defaultVal: true,
),
const SetSwitchItem(
title: '观看人数',
subTitle: '展示同时在看人数',
setKey: SettingBoxKey.enableOnlineTotal,
defaultVal: false,
),
const SetSwitchItem(
title: '亮度记忆',
subTitle: '返回时自动调整视频亮度',
setKey: SettingBoxKey.enableAutoBrightness,
defaultVal: false,
),
ListTile(
dense: false,
title: Text('默认画质', style: titleStyle),

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
@ -5,6 +7,7 @@ import 'package:pilipala/models/common/theme_type.dart';
import 'package:pilipala/utils/storage.dart';
import 'controller.dart';
import 'widgets/switch_item.dart';
class StyleSetting extends StatefulWidget {
const StyleSetting({super.key});
@ -66,6 +69,12 @@ class _StyleSettingState extends State<StyleSetting> {
),
),
),
const SetSwitchItem(
title: 'iOS路由切换',
subTitle: 'iOS路由切换样式需重启',
setKey: SettingBoxKey.iosTransition,
defaultVal: false,
),
ListTile(
dense: false,
onTap: () {
@ -188,12 +197,21 @@ class _StyleSettingState extends State<StyleSetting> {
dense: false,
onTap: () => Get.toNamed('/colorSetting'),
title: Text('应用主题', style: titleStyle),
trailing: const Icon(Icons.arrow_forward_ios, size: 17),
),
ListTile(
dense: false,
onTap: () => Get.toNamed('/fontSizeSetting'),
title: Text('字体大小', style: titleStyle),
)
trailing: const Icon(Icons.arrow_forward_ios, size: 17),
),
if (Platform.isAndroid)
ListTile(
dense: false,
onTap: () => Get.toNamed('/displayModeSetting'),
title: Text('屏幕帧率', style: titleStyle),
trailing: const Icon(Icons.arrow_forward_ios, size: 17),
)
],
),
);

View File

@ -32,6 +32,15 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
val = Setting.get(widget.setKey, defaultValue: widget.defaultVal ?? false);
}
void switchChange(value) {
val = value ?? !val;
Setting.put(widget.setKey, val);
if (widget.setKey == SettingBoxKey.autoUpdate && value == true) {
Utils.checkUpdata();
}
setState(() {});
}
@override
Widget build(BuildContext context) {
TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!;
@ -41,9 +50,7 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
.copyWith(color: Theme.of(context).colorScheme.outline);
return ListTile(
enableFeedback: true,
onTap: () {
Setting.put(widget.setKey, !val);
},
onTap: () => switchChange(null),
title: Text(widget.title!, style: titleStyle),
subtitle: widget.subTitle != null
? Text(widget.subTitle!, style: subTitleStyle)
@ -51,22 +58,16 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
trailing: Transform.scale(
scale: 0.8,
child: Switch(
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
(Set<MaterialState> states) {
if (states.isNotEmpty && states.first == MaterialState.selected) {
return const Icon(Icons.done);
}
return null; // All other states will use the default thumbIcon.
}),
value: val,
onChanged: (value) {
val = value;
Setting.put(widget.setKey, value);
if (widget.setKey == SettingBoxKey.autoUpdate && value == true) {
Utils.checkUpdata();
}
setState(() {});
}),
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
(Set<MaterialState> states) {
if (states.isNotEmpty && states.first == MaterialState.selected) {
return const Icon(Icons.done);
}
return null; // All other states will use the default thumbIcon.
}),
value: val,
onChanged: (val) => switchChange(val),
),
),
);
}

View File

@ -14,6 +14,7 @@ import 'package:pilipala/pages/video/detail/replyReply/index.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:screen_brightness/screen_brightness.dart';
class VideoDetailController extends GetxController
with GetSingleTickerProviderStateMixin {
@ -68,6 +69,8 @@ class VideoDetailController extends GetxController
late String videoUrl;
late String audioUrl;
late Duration defaultST;
// 亮度
double? brightness;
// 默认记录历史记录
bool enableHeart = true;
var userInfo;
@ -137,8 +140,17 @@ class VideoDetailController extends GetxController
/// 根据currentVideoQa和currentDecodeFormats 重新设置videoUrl
List<VideoItem> videoList =
data.dash!.video!.where((i) => i.id == currentVideoQa.code).toList();
firstVideo = videoList
.firstWhere((i) => i.codecs!.startsWith(currentDecodeFormats.code));
try {
firstVideo = videoList
.firstWhere((i) => i.codecs!.startsWith(currentDecodeFormats.code));
} catch (_) {
// 当前格式不可用
currentDecodeFormats = VideoDecodeFormatsCode.fromString(setting.get(
SettingBoxKey.defaultDecode,
defaultValue: VideoDecodeFormats.values.last.code))!;
firstVideo = videoList
.firstWhere((i) => i.codecs!.startsWith(currentDecodeFormats.code));
}
videoUrl = firstVideo.baseUrl!;
/// 根据currentAudioQa 重新设置audioUrl
@ -152,6 +164,12 @@ class VideoDetailController extends GetxController
}
Future playerInit({video, audio, seekToTime, duration}) async {
/// 设置/恢复 屏幕亮度
if (brightness != null) {
ScreenBrightness().setScreenBrightness(brightness!);
} else {
ScreenBrightness().resetScreenBrightness();
}
await plPlayerController.setDataSource(
DataSource(
videoSource: video ?? videoUrl,
@ -234,17 +252,25 @@ class VideoDetailController extends GetxController
defaultValue: VideoDecodeFormats.values.last.code))!;
try {
// 当前视频没有对应格式返回第一个
currentDecodeFormats =
supportDecodeFormats.contains(supportDecodeFormats)
? supportDecodeFormats
: supportDecodeFormats.first;
} catch (_) {}
currentDecodeFormats = supportDecodeFormats
.contains(currentDecodeFormats)
? currentDecodeFormats
: VideoDecodeFormatsCode.fromString(supportDecodeFormats.first)!;
} catch (e) {
print(e);
}
/// 取出符合当前解码格式的videoItem
firstVideo = videosList
.firstWhere((e) => e.codecs!.startsWith(currentDecodeFormats.code));
try {
firstVideo = videosList.firstWhere(
(e) => e.codecs!.startsWith(currentDecodeFormats.code));
} catch (_) {
firstVideo = videosList.first;
}
videoUrl = firstVideo.baseUrl!;
} catch (_) {}
} catch (err) {
print(err);
}
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
late AudioItem firstAudio;

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@ -51,6 +53,12 @@ class VideoIntroController extends GetxController {
RxInt lastPlayCid = 0.obs;
var userInfo;
// 同时观看
bool isShowOnlineTotal = false;
RxInt totel = 1.obs;
Timer? timer;
bool isPaused = false;
@override
void onInit() {
super.onInit();
@ -78,6 +86,12 @@ class VideoIntroController extends GetxController {
}
userLogin = userInfo != null;
lastPlayCid.value = int.parse(Get.parameters['cid']!);
isShowOnlineTotal =
setting.get(SettingBoxKey.enableOnlineTotal, defaultValue: false);
if (isShowOnlineTotal) {
queryOnlineTotal();
startTimer(); // 在页面加载时启动定时器
}
}
// 获取视频简介&分p
@ -417,4 +431,33 @@ class VideoIntroController extends GetxController {
lastPlayCid.value = cid;
await queryVideoIntro();
}
void startTimer() {
const duration = Duration(seconds: 10); // 设置定时器间隔为10秒
timer = Timer.periodic(duration, (Timer timer) {
if (!isPaused) {
queryOnlineTotal(); // 定时器回调函数,发起请求
}
});
}
// 查看同时在看人数
Future queryOnlineTotal() async {
var result = await VideoHttp.onlineTotal(
aid: IdUtils.bv2av(bvid),
bvid: bvid,
cid: lastPlayCid.value,
);
if (result['status']) {
totel.value = int.parse(result['data']['total']);
}
}
@override
void onClose() {
if (timer != null) {
timer!.cancel(); // 销毁页面时取消定时器
}
super.onClose();
}
}

View File

@ -111,6 +111,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
late final dynamic owner;
late final dynamic follower;
late final dynamic followStatus;
late int mid;
late String memberHeroTag;
@override
void initState() {
@ -160,14 +162,15 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
// 用户主页
onPushMember() {
feedBack();
int mid = !loadingStatus
mid = !loadingStatus
? widget.videoDetail!.owner!.mid
: videoItem['owner'].mid;
memberHeroTag = Utils.makeHeroTag(mid);
String face = !loadingStatus
? widget.videoDetail!.owner!.face
: videoItem['owner'].face;
Get.toNamed('/member?mid=$mid',
arguments: {'face': face, 'heroTag': (mid + 99).toString()});
arguments: {'face': face, 'heroTag': memberHeroTag});
}
@override
@ -255,6 +258,17 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
color: t.colorScheme.outline,
),
),
const SizedBox(width: 10),
if (videoIntroController.isShowOnlineTotal)
Obx(
() => Text(
'${videoIntroController.totel.value}人在看',
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
),
],
),
),

View File

@ -28,7 +28,25 @@ class _SeasonPanelState extends State<SeasonPanel> {
@override
void initState() {
super.initState();
episodes = widget.ugcSeason.sections!.first.episodes!;
/// 根据 cid 找到对应集,找到对应 episodes
/// 有多个episodes时只显示其中一个
/// TODO 同时显示多个合集
List<SectionItem> sections = widget.ugcSeason.sections!;
for (int i = 0; i < sections.length; i++) {
List<EpisodeItem> episodesList = sections[i].episodes!;
for (int j = 0; j < episodesList.length; j++) {
if (episodesList[j].cid == widget.cid) {
episodes = episodesList;
continue;
}
}
}
/// 取对应 season_id 的 episodes
// episodes = widget.ugcSeason.sections!
// .firstWhere((e) => e.seasonId == widget.ugcSeason.id)
// .episodes!;
currentIndex = episodes.indexWhere((e) => e.cid == widget.cid);
}
@ -136,7 +154,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
),
const SizedBox(width: 10),
Text(
'${currentIndex + 1}/${widget.ugcSeason.epCount}',
'${currentIndex + 1}/${episodes.length}',
style: Theme.of(context).textTheme.labelMedium,
),
const SizedBox(width: 6),

View File

@ -1,10 +1,13 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/reply.dart';
import 'package:pilipala/models/common/reply_sort_type.dart';
import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
class VideoReplyController extends GetxController {
VideoReplyController(
@ -25,13 +28,26 @@ class VideoReplyController extends GetxController {
bool isLoadingMore = false;
RxString noMore = ''.obs;
int ps = 20;
RxInt count = 0.obs;
// 当前回复的回复
ReplyItemModel? currentReplyItem;
ReplySortType sortType = ReplySortType.time;
ReplySortType _sortType = ReplySortType.time;
RxString sortTypeTitle = ReplySortType.time.titles.obs;
RxString sortTypeLabel = ReplySortType.time.labels.obs;
Box setting = GStrorage.setting;
@override
void onInit() {
super.onInit();
int deaultReplySortIndex =
setting.get(SettingBoxKey.replySortType, defaultValue: 0);
_sortType = ReplySortType.values[deaultReplySortIndex];
sortTypeTitle.value = _sortType.titles;
sortTypeLabel.value = _sortType.labels;
}
Future queryReplyList({type = 'init'}) async {
isLoadingMore = true;
if (type == 'init') {
@ -45,7 +61,7 @@ class VideoReplyController extends GetxController {
pageNum: currentPage + 1,
ps: ps,
type: ReplyType.video.index,
sort: sortType.index,
sort: _sortType.index,
);
if (res['status']) {
List<ReplyItemModel> replies = res['data'].replies;
@ -81,6 +97,7 @@ class VideoReplyController extends GetxController {
replyList.addAll(replies);
}
}
count.value = res['data'].page.count;
isLoadingMore = false;
return res;
}
@ -92,23 +109,26 @@ class VideoReplyController extends GetxController {
// 排序搜索评论
queryBySort() {
feedBack();
switch (sortType) {
case ReplySortType.time:
sortType = ReplySortType.like;
break;
case ReplySortType.like:
sortType = ReplySortType.reply;
break;
case ReplySortType.reply:
sortType = ReplySortType.time;
break;
default:
}
sortTypeTitle.value = sortType.titles;
sortTypeLabel.value = sortType.labels;
currentPage = 0;
replyList.clear();
queryReplyList(type: 'init');
EasyThrottle.throttle('queryBySort', const Duration(seconds: 1), () {
feedBack();
switch (_sortType) {
case ReplySortType.time:
_sortType = ReplySortType.like;
break;
case ReplySortType.like:
_sortType = ReplySortType.reply;
break;
case ReplySortType.reply:
_sortType = ReplySortType.time;
break;
default:
}
sortTypeTitle.value = _sortType.titles;
sortTypeLabel.value = _sortType.labels;
currentPage = 0;
noMore.value = '';
replyList.clear();
queryReplyList(type: 'init');
});
}
}

View File

@ -160,9 +160,9 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
scale: animation, child: child);
},
child: Text(
_videoReplyController.sortTypeTitle.value,
key: ValueKey<String>(
_videoReplyController.sortTypeTitle.value),
'${_videoReplyController.count.value}条回复',
key: ValueKey<int>(
_videoReplyController.count.value),
),
),
),

View File

@ -591,7 +591,8 @@ InlineSpan buildContent(
if (content.jumpUrl.isNotEmpty && hasMatchMember) {
List urlKeys = content.jumpUrl.keys.toList();
matchUrl = matchMember.splitMapJoin(
RegExp("(?:${urlKeys.join("|")})"),
/// RegExp.escape() 转义特殊字符
RegExp(RegExp.escape(urlKeys.join("|"))),
onMatch: (Match match) {
String matchStr = match[0]!;
spanChilds.add(

View File

@ -37,12 +37,15 @@ class _VideoDetailPageState extends State<VideoDetailPage>
PlPlayerController? plPlayerController;
final ScrollController _extendNestCtr = ScrollController();
late StreamController<double> appbarStream;
final VideoIntroController videoIntroController =
Get.put(VideoIntroController(), tag: Get.arguments['heroTag']);
PlayerStatus playerStatus = PlayerStatus.playing;
// bool isShowCover = true;
double doubleOffset = 0;
Box localCache = GStrorage.localCache;
Box setting = GStrorage.setting;
late double statusBarHeight;
final videoHeight = Get.size.width * 9 / 16;
late Future _futureBuilderFuture;
@ -95,7 +98,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
@override
void dispose() {
plPlayerController!.pause();
plPlayerController!.dispose();
super.dispose();
}
@ -103,7 +105,12 @@ class _VideoDetailPageState extends State<VideoDetailPage>
@override
// 离开当前页面时
void didPushNext() async {
/// 开启
if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: false)) {
videoDetailController.brightness = plPlayerController!.brightness.value;
}
videoDetailController.defaultST = plPlayerController!.position.value;
videoIntroController.isPaused = true;
plPlayerController!.pause();
super.didPushNext();
}
@ -112,6 +119,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
// 返回当前页面时
void didPopNext() async {
videoDetailController.playerInit();
videoIntroController.isPaused = false;
if (_extendNestCtr.position.pixels == 0) {
await Future.delayed(const Duration(milliseconds: 300));
plPlayerController!.play();

View File

@ -387,9 +387,12 @@ class _HeaderControlState extends State<HeaderControl> {
// 当前选中的解码格式
VideoDecodeFormats currentDecodeFormats =
widget.videoDetailCtr!.currentDecodeFormats;
VideoItem firstVideo = widget.videoDetailCtr!.firstVideo;
// 当前视频可用的解码格式
List<FormatItem> videoFormat = videoInfo.supportFormats!;
List list = videoFormat.first.codecs!;
List list = videoFormat
.firstWhere((e) => e.quality == firstVideo.quality!.code)
.codecs!;
showModalBottomSheet(
context: context,
@ -483,10 +486,12 @@ class _HeaderControlState extends State<HeaderControl> {
size: 15,
color: Colors.white,
),
fuc: () {
fuc: () async {
// 销毁播放器实例
widget.controller!.dispose(type: 'all');
Get.offAll(const MainApp());
await widget.controller!.dispose(type: 'all');
if (mounted) {
Navigator.popUntil(context, (route) => route.isFirst);
}
},
),
const Spacer(),
@ -506,9 +511,7 @@ class _HeaderControlState extends State<HeaderControl> {
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
_.togglePlaybackSpeed();
},
onPressed: () => showSetSpeedSheet(),
child: Text(
'${_.playbackSpeed.toString()}X',
style: textStyle,

View File

@ -619,7 +619,7 @@ class PlPlayerController {
try {
brightness.value = brightnes;
ScreenBrightness().setScreenBrightness(brightnes);
setVideoBrightness();
// setVideoBrightness();
} catch (e) {
throw 'Failed to set brightness';
}
@ -662,27 +662,24 @@ class PlPlayerController {
}
/// 缓存fit
Future<void> setVideoFit() async {
videoStorage.put(VideoBoxKey.videoBrightness, _videoFit.value.name);
}
// Future<void> setVideoFit() async {
// videoStorage.put(VideoBoxKey.videoBrightness, _videoFit.value.name);
// }
/// 读取fit
Future<void> getVideoFit() async {
String fitValue =
videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 'contain');
_videoFit.value = videoFitType
.firstWhere((element) => element['attr'] == fitValue)['attr'];
}
/// 缓存亮度
Future<void> setVideoBrightness() async {}
// Future<void> getVideoFit() async {
// String fitValue =
// videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 'contain');
// _videoFit.value = videoFitType
// .firstWhere((element) => element['attr'] == fitValue)['attr'];
// }
/// 读取亮度
Future<void> getVideoBrightness() async {
double brightnessValue =
videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 0.5);
setBrightness(brightnessValue);
}
// Future<void> getVideoBrightness() async {
// double brightnessValue =
// videoStorage.get(VideoBoxKey.videoBrightness, defaultValue: 0.5);
// setBrightness(brightnessValue);
// }
set controls(bool visible) {
_showControls.value = visible;
@ -760,36 +757,41 @@ class PlPlayerController {
Future<void> dispose({String type = 'single'}) async {
// 每次减1最后销毁
if (type == 'single') {
if (type == 'single' && playerCount.value > 1) {
_playerCount.value -= 1;
_heartDuration = 0;
if (playerCount.value > 0) {
return;
}
pause();
return;
}
_playerCount.value = 0;
try {
_timer?.cancel();
_timerForVolume?.cancel();
_timerForGettingVolume?.cancel();
timerForTrackingMouse?.cancel();
_timerForSeek?.cancel();
videoFitChangedTimer?.cancel();
// _position.close();
_playerEventSubs?.cancel();
// _sliderPosition.close();
// _sliderTempPosition.close();
// _isSliderMoving.close();
// _duration.close();
// _buffered.close();
// _showControls.close();
// _controlsLock.close();
_timer?.cancel();
_timerForVolume?.cancel();
_timerForGettingVolume?.cancel();
timerForTrackingMouse?.cancel();
_timerForSeek?.cancel();
videoFitChangedTimer?.cancel();
_position.close();
_playerEventSubs?.cancel();
_sliderPosition.close();
_sliderTempPosition.close();
_isSliderMoving.close();
_duration.close();
_buffered.close();
_showControls.close();
_controlsLock.close();
// playerStatus.status.close();
// dataStatus.status.close();
playerStatus.status.close();
dataStatus.status.close();
removeListeners();
await _videoPlayerController?.dispose();
_videoPlayerController = null;
_instance = null;
removeListeners();
await _videoPlayerController?.dispose();
_videoPlayerController = null;
_instance = null;
// 关闭所有视频页面恢复亮度
resetBrightness();
} catch (err) {
print(err);
}
}
}

View File

@ -156,6 +156,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
});
}
});
widget.controller.brightness.value = value;
}
Future<void> triggerFullScreen() async {

View File

@ -1,4 +1,8 @@
// ignore_for_file: must_be_immutable
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/pages/about/index.dart';
import 'package:pilipala/pages/blacklist/index.dart';
import 'package:pilipala/pages/dynamics/deatil/index.dart';
@ -18,6 +22,7 @@ import 'package:pilipala/pages/search/index.dart';
import 'package:pilipala/pages/searchResult/index.dart';
import 'package:pilipala/pages/setting/extra_setting.dart';
import 'package:pilipala/pages/setting/pages/color_select.dart';
import 'package:pilipala/pages/setting/pages/display_mode.dart';
import 'package:pilipala/pages/setting/pages/font_size_select.dart';
import 'package:pilipala/pages/setting/play_setting.dart';
import 'package:pilipala/pages/setting/privacy_setting.dart';
@ -27,15 +32,20 @@ import 'package:pilipala/pages/video/detail/replyReply/index.dart';
import 'package:pilipala/pages/webview/index.dart';
import 'package:pilipala/pages/setting/index.dart';
import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/utils/storage.dart';
Box setting = GStrorage.setting;
bool iosTransition =
setting.get(SettingBoxKey.iosTransition, defaultValue: false);
class Routes {
static final List<GetPage> getPages = [
// 首页(推荐)
GetPage(name: '/', page: () => const HomePage()),
CustomGetPage(name: '/', page: () => const HomePage()),
// 热门
GetPage(name: '/hot', page: () => const HotPage()),
CustomGetPage(name: '/hot', page: () => const HotPage()),
// 视频详情
GetPage(name: '/video', page: () => const VideoDetailPage()),
CustomGetPage(name: '/video', page: () => const VideoDetailPage()),
// 图片预览
GetPage(
name: '/preview',
@ -45,51 +55,77 @@ class Routes {
showCupertinoParallax: false,
),
//
GetPage(name: '/webview', page: () => const WebviewPage()),
CustomGetPage(name: '/webview', page: () => const WebviewPage()),
// 设置
GetPage(name: '/setting', page: () => const SettingPage()),
CustomGetPage(name: '/setting', page: () => const SettingPage()),
//
GetPage(name: '/media', page: () => const MediaPage()),
CustomGetPage(name: '/media', page: () => const MediaPage()),
//
GetPage(name: '/fav', page: () => const FavPage()),
CustomGetPage(name: '/fav', page: () => const FavPage()),
//
GetPage(name: '/favDetail', page: () => const FavDetailPage()),
CustomGetPage(name: '/favDetail', page: () => const FavDetailPage()),
// 稍后再看
GetPage(name: '/later', page: () => const LaterPage()),
CustomGetPage(name: '/later', page: () => const LaterPage()),
// 历史记录
GetPage(name: '/history', page: () => const HistoryPage()),
CustomGetPage(name: '/history', page: () => const HistoryPage()),
// 搜索页面
GetPage(name: '/search', page: () => const SearchPage()),
CustomGetPage(name: '/search', page: () => const SearchPage()),
// 搜索结果
GetPage(name: '/searchResult', page: () => const SearchResultPage()),
CustomGetPage(name: '/searchResult', page: () => const SearchResultPage()),
// 动态
GetPage(name: '/dynamics', page: () => const DynamicsPage()),
CustomGetPage(name: '/dynamics', page: () => const DynamicsPage()),
// 动态详情
GetPage(name: '/dynamicDetail', page: () => const DynamicDetailPage()),
CustomGetPage(
name: '/dynamicDetail', page: () => const DynamicDetailPage()),
// 关注
GetPage(name: '/follow', page: () => const FollowPage()),
CustomGetPage(name: '/follow', page: () => const FollowPage()),
// 粉丝
GetPage(name: '/fan', page: () => const FansPage()),
CustomGetPage(name: '/fan', page: () => const FansPage()),
// 直播详情
GetPage(name: '/liveRoom', page: () => const LiveRoomPage()),
CustomGetPage(name: '/liveRoom', page: () => const LiveRoomPage()),
// 用户中心
GetPage(name: '/member', page: () => const MemberPage()),
CustomGetPage(name: '/member', page: () => const MemberPage()),
// 二级回复
GetPage(name: '/replyReply', page: () => const VideoReplyReplyPanel()),
CustomGetPage(
name: '/replyReply', page: () => const VideoReplyReplyPanel()),
// 播放设置
GetPage(name: '/playSetting', page: () => const PlaySetting()),
CustomGetPage(name: '/playSetting', page: () => const PlaySetting()),
// 外观设置
GetPage(name: '/styleSetting', page: () => const StyleSetting()),
CustomGetPage(name: '/styleSetting', page: () => const StyleSetting()),
// 隐私设置
GetPage(name: '/privacySetting', page: () => const PrivacySetting()),
CustomGetPage(name: '/privacySetting', page: () => const PrivacySetting()),
// 其他设置
GetPage(name: '/extraSetting', page: () => const ExtraSetting()),
CustomGetPage(name: '/extraSetting', page: () => const ExtraSetting()),
//
GetPage(name: '/blackListPage', page: () => const BlackListPage()),
GetPage(name: '/colorSetting', page: () => const ColorSelectPage()),
GetPage(name: '/fontSizeSetting', page: () => const FontSizeSelectPage()),
CustomGetPage(name: '/blackListPage', page: () => const BlackListPage()),
CustomGetPage(name: '/colorSetting', page: () => const ColorSelectPage()),
CustomGetPage(
name: '/fontSizeSetting', page: () => const FontSizeSelectPage()),
// 屏幕帧率
CustomGetPage(
name: '/displayModeSetting', page: () => const SetDiaplayMode()),
// 关于
GetPage(name: '/about', page: () => const AboutPage()),
CustomGetPage(name: '/about', page: () => const AboutPage()),
];
}
class CustomGetPage extends GetPage {
bool? fullscreen = false;
CustomGetPage({
name,
page,
this.fullscreen,
transitionDuration,
}) : super(
name: name,
page: page,
curve: Curves.linear,
transition: iosTransition ? Transition.cupertino : Transition.native,
showCupertinoParallax: false,
popGesture: false,
transitionDuration: transitionDuration,
fullscreenDialog: fullscreen != null && fullscreen,
);
}

View File

@ -82,6 +82,7 @@ class GStrorage {
}
class SettingBoxKey {
/// 播放器
static const String btmProgressBehavior = 'btmProgressBehavior';
static const String defaultVideoSpeed = 'defaultVideoSpeed';
static const String autoUpgradeEnable = 'autoUpgradeEnable';
@ -94,15 +95,23 @@ class SettingBoxKey {
static const String danmakuEnable = 'danmakuEnable';
static const String defaultPicQa = 'defaultPicQa';
static const String enableHA = 'enableHA';
static const String enableOnlineTotal = 'enableOnlineTotal';
static const String enableAutoBrightness = 'enableAutoBrightness';
/// 隐私
static const String blackMidsList = 'blackMidsList';
/// 其他
static const String autoUpdate = 'autoUpdate';
static const String replySortType = 'replySortType';
static const String defaultDynamicType = 'defaultDynamicType';
/// 外观
static const String themeMode = 'themeMode';
static const String defaultTextScale = 'textScale';
static const String dynamicColor = 'dynamicColor'; // bool
static const String customColor = 'customColor'; // 自定义主题色
static const String iosTransition = 'iosTransition'; // ios路由
}
class LocalCacheKey {

View File

@ -4,6 +4,7 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get_utils/get_utils.dart';
@ -210,6 +211,7 @@ class Utils {
bool isUpdate = Utils.needUpdate(currentInfo.version, data.tagName!);
if (isUpdate) {
SmartDialog.show(
animationType: SmartAnimationType.centerFade_otherSlide,
builder: (context) {
return AlertDialog(
title: const Text('🎉 发现新版本 '),
@ -228,22 +230,27 @@ class Utils {
),
actions: [
TextButton(
onPressed: () => SmartDialog.dismiss(),
child: Text(
'稍后',
style:
TextStyle(color: Theme.of(context).colorScheme.outline),
)),
onPressed: () => SmartDialog.dismiss(),
child: Text(
'稍后',
style:
TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
await SmartDialog.dismiss();
launchUrl(
Uri.parse(
'https://github.com/guozhigq/pilipala/releases'),
mode: LaunchMode.externalApplication,
);
},
child: const Text('去下载')),
onPressed: () async {
await SmartDialog.dismiss();
launchUrl(
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
mode: LaunchMode.externalApplication,
);
},
child: const Text('网盘下载'),
),
TextButton(
onPressed: () => matchVersion(data),
child: const Text('Github下载'),
),
],
);
},
@ -251,4 +258,27 @@ class Utils {
}
return true;
}
// 下载适用于当前系统的安装包
static Future matchVersion(data) async {
await SmartDialog.dismiss();
// 获取设备信息
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
if (Platform.isAndroid) {
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
// [arm64-v8a]
String abi = androidInfo.supportedAbis.first;
late String downloadUrl;
for (var i in data.assets) {
if (i.downloadUrl.contains(abi)) {
downloadUrl = i.downloadUrl;
}
}
// 应用外下载
launchUrl(
Uri.parse(downloadUrl),
mode: LaunchMode.externalApplication,
);
}
}
}

View File

@ -438,6 +438,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.3.1"
flutter_displaymode:
dependency: "direct main"
description:
name: flutter_displaymode
sha256: "42c5e9abd13d28ed74f701b60529d7f8416947e58256e6659c5550db719c57ef"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
flutter_launcher_icons:
dependency: "direct dev"
description:

View File

@ -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
# 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.
version: 1.0.4
version: 1.0.5
environment:
sdk: ">=2.19.6 <3.0.0"
@ -115,6 +115,8 @@ dependencies:
flutter_svg: ^2.0.7
# 防抖节流
easy_debounce: ^2.0.3
# 高帧率
flutter_displaymode: ^0.6.0
dev_dependencies:
flutter_test: