Compare commits

..

2 Commits

Author SHA1 Message Date
df2cb00112 Merge branch 'main' into alpha 2024-02-27 08:26:00 +08:00
b0166d6657 Merge branch 'feature-logger' into alpha 2024-01-28 15:56:02 +08:00
56 changed files with 535 additions and 1143 deletions

View File

@ -1,208 +0,0 @@
name: Pilipala Beta
on:
workflow_dispatch:
push:
branches:
- "main"
paths-ignore:
- "**.md"
- "**.txt"
- ".github/**"
- ".idea/**"
- "!.github/workflows/**"
jobs:
update_version:
name: Read and update version
runs-on: ubuntu-latest
outputs:
# 定义输出变量 version以便在其他job中引用
new_version: ${{ steps.version.outputs.new_version }}
last_commit: ${{ steps.get-last-commit.outputs.last_commit }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
fetch-depth: 0
- name: 获取first parent commit次数
id: get-first-parent-commit-count
run: |
version=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
recent_release_tag=$(git tag -l | grep $version | egrep -v "[-|+]" || true)
if [[ "x$recent_release_tag" == "x" ]]; then
echo "当前版本tag不存在请手动生成tag."
exit 1
fi
git log --oneline --first-parent $recent_release_tag..HEAD
first_parent_commit_count=$(git rev-list --first-parent --count $recent_release_tag..HEAD)
echo "count=$first_parent_commit_count" >> $GITHUB_OUTPUT
- name: 获取最后一次提交
id: get-last-commit
run: |
last_commit=$(git log -1 --pretty="%h %s" --first-parent)
echo "last_commit=$last_commit" >> $GITHUB_OUTPUT
- name: 更新版本号
id: version
run: |
# 读取版本号
VERSION=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
# 获取GitHub Actions的run_number
#RUN_NUMBER=${{ github.run_number }}
# 构建新版本号
NEW_VERSION=$VERSION-beta.${{ steps.get-first-parent-commit-count.outputs.count }}
# 输出新版本号
echo "New version: $NEW_VERSION"
# 设置新版本号为输出变量
echo "new_version=$NEW_VERSION" >>$GITHUB_OUTPUT
android:
name: Build CI (Android)
needs: update_version
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
- name: 构建Java环境
uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "17"
token: ${{secrets.GIT_TOKEN}}
- name: 检查缓存
uses: actions/cache@v2
id: cache-flutter
with:
path: /root/flutter-sdk
key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }}
- name: 安装Flutter
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2
with:
flutter-version: 3.16.5
channel: any
- name: 下载项目依赖
run: flutter pub get
- name: 解码生成 jks
run: echo $KEYSTORE_BASE64 | base64 -di > android/app/vvex.jks
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
- name: 更新版本号
id: version
run: |
# 更新pubspec.yaml文件中的版本号
sed -i "s/version: .*+/version: ${{ needs.update_version.outputs.new_version }}+/g" pubspec.yaml
- name: flutter build apk
run: flutter build apk --release --split-per-abi
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
- name: 重命名应用
run: |
for file in build/app/outputs/flutter-apk/app-*.apk; do
if [[ $file =~ app-(.?*)release.apk ]]; then
new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}v${{ needs.update_version.outputs.new_version }}.apk"
mv "$file" "$new_file_name"
fi
done
- name: 上传
uses: actions/upload-artifact@v3
with:
name: Pilipala-Beta
path: |
build/app/outputs/flutter-apk/Pili-*.apk
iOS:
name: Build CI (iOS)
needs: update_version
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
- name: 安装Flutter
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2.10.0
with:
cache: true
flutter-version: 3.16.5
- name: 更新版本号
id: version
run: |
# 更新pubspec.yaml文件中的版本号
sed -i "" "s/version: .*+/version: ${{ needs.update_version.outputs.new_version }}+/g" pubspec.yaml
- name: flutter build ipa
run: |
flutter build ios --release --no-codesign
ln -sf ./build/ios/iphoneos Payload
zip -r9 app.ipa Payload/runner.app
- name: 重命名应用
run: |
DATE=${{ steps.date.outputs.date }}
for file in app.ipa; do
new_file_name="build/Pili-v${{ needs.update_version.outputs.new_version }}.ipa"
mv "$file" "$new_file_name"
done
- name: 上传
uses: actions/upload-artifact@v3
with:
if-no-files-found: error
name: Pilipala-Beta
path: |
build/Pili-*.ipa
upload:
runs-on: ubuntu-latest
needs:
- update_version
- android
- iOS
steps:
- uses: actions/download-artifact@v3
with:
name: Pilipala-Beta
path: ./Pilipala-Beta
- name: 发送到Telegram频道
uses: xireiki/channel-post@v1.0.7
with:
bot_token: ${{ secrets.BOT_TOKEN }}
chat_id: ${{ secrets.CHAT_ID }}
large_file: true
api_id: ${{ secrets.TELEGRAM_API_ID }}
api_hash: ${{ secrets.TELEGRAM_API_HASH }}
method: sendFile
path: Pilipala-Beta/*
parse_mode: Markdown
context: "*Beta版本: v${{ needs.update_version.outputs.new_version }}*\n更新内容: [${{ needs.update_version.outputs.last_commit }}](${{ github.event.head_commit.url }})"

View File

@ -223,10 +223,6 @@
android:pathPattern="/mobile/video/.*" /> android:pathPattern="/mobile/video/.*" />
<data android:scheme="https" android:host="www.bilibili.com" <data android:scheme="https" android:host="www.bilibili.com"
android:pathPattern="/mobile/video/.*" /> android:pathPattern="/mobile/video/.*" />
<data android:scheme="https" android:host="b23.tv"
android:pathPattern="/*" />
<data android:scheme="https" android:host="space.bilibili.com"
android:pathPattern="/*" />
</intent-filter> </intent-filter>
</activity> </activity>

View File

@ -1,31 +0,0 @@
## 1.0.20
### 功能
+ 评论区增加表情
+ 首页渐变背景开关
+ 媒体库显示「我的订阅」
+ 评论区链接解析
+ 默认启动页设置
### 修复
+ 评论区内容重复
+ pip相关问题
+ 播放多p视频评论不刷新
+ 视频评论翻页重复
### 优化
+ url scheme优化
+ 图片预览放大
+ 图片加载速度
+ 视频评论区复制
+ 全屏显示视频标题
+ 网络异常处理
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -231,7 +231,6 @@ class VideoContent extends StatelessWidget {
const SizedBox(height: 2), const SizedBox(height: 2),
VideoStat( VideoStat(
videoItem: videoItem, videoItem: videoItem,
crossAxisCount: crossAxisCount,
), ),
], ],
if (crossAxisCount == 1) const SizedBox(height: 4), if (crossAxisCount == 1) const SizedBox(height: 4),
@ -295,7 +294,6 @@ class VideoContent extends StatelessWidget {
), ),
VideoStat( VideoStat(
videoItem: videoItem, videoItem: videoItem,
crossAxisCount: crossAxisCount,
), ),
const Spacer(), const Spacer(),
], ],
@ -319,12 +317,10 @@ class VideoContent extends StatelessWidget {
class VideoStat extends StatelessWidget { class VideoStat extends StatelessWidget {
final dynamic videoItem; final dynamic videoItem;
final int crossAxisCount;
const VideoStat({ const VideoStat({
Key? key, Key? key,
required this.videoItem, required this.videoItem,
required this.crossAxisCount,
}) : super(key: key); }) : super(key: key);
@override @override
@ -341,7 +337,7 @@ class VideoStat extends StatelessWidget {
danmu: videoItem.stat.danmu, danmu: videoItem.stat.danmu,
), ),
if (videoItem is RecVideoItemModel) ...<Widget>[ if (videoItem is RecVideoItemModel) ...<Widget>[
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8), const Spacer(),
RichText( RichText(
maxLines: 1, maxLines: 1,
text: TextSpan( text: TextSpan(

View File

@ -1,12 +0,0 @@
enum FullScreenGestureMode {
/// 从上滑到下
fromToptoBottom,
/// 从下滑到上
fromBottomtoTop,
}
extension FullScreenGestureModeExtension on FullScreenGestureMode {
String get values => ['fromToptoBottom', 'fromBottomtoTop'][index];
String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏'][index];
}

View File

@ -1,4 +0,0 @@
library commonn_model;
export './business_type.dart';
export './gesture_mode.dart';

View File

@ -1,43 +0,0 @@
import 'package:flutter/material.dart';
const defaultNavigationBars = [
{
'id': 0,
'icon': Icon(
Icons.home_outlined,
size: 21,
),
'selectIcon': Icon(
Icons.home,
size: 21,
),
'label': "首页",
'count': 0,
},
{
'id': 1,
'icon': Icon(
Icons.motion_photos_on_outlined,
size: 21,
),
'selectIcon': Icon(
Icons.motion_photos_on,
size: 21,
),
'label': "动态",
'count': 0,
},
{
'id': 2,
'icon': Icon(
Icons.video_collection_outlined,
size: 20,
),
'selectIcon': Icon(
Icons.video_collection,
size: 21,
),
'label': "媒体库",
'count': 0,
}
];

View File

@ -2,28 +2,18 @@ class FollowUpModel {
FollowUpModel({ FollowUpModel({
this.liveUsers, this.liveUsers,
this.upList, this.upList,
this.liveList,
this.myInfo,
}); });
LiveUsers? liveUsers; LiveUsers? liveUsers;
List<UpItem>? upList; List<UpItem>? upList;
List<LiveUserItem>? liveList;
MyInfo? myInfo;
FollowUpModel.fromJson(Map<String, dynamic> json) { FollowUpModel.fromJson(Map<String, dynamic> json) {
liveUsers = json['live_users'] != null liveUsers = json['live_users'] != null
? LiveUsers.fromJson(json['live_users']) ? LiveUsers.fromJson(json['live_users'])
: null; : null;
liveList = json['live_users'] != null
? json['live_users']['items']
.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
.toList()
: [];
upList = json['up_list'] != null upList = json['up_list'] != null
? json['up_list'].map<UpItem>((e) => UpItem.fromJson(e)).toList() ? json['up_list'].map<UpItem>((e) => UpItem.fromJson(e)).toList()
: []; : [];
myInfo = json['my_info'] != null ? MyInfo.fromJson(json['my_info']) : null;
} }
} }
@ -103,21 +93,3 @@ class UpItem {
uname = json['uname']; uname = json['uname'];
} }
} }
class MyInfo {
MyInfo({
this.face,
this.mid,
this.name,
});
String? face;
int? mid;
String? name;
MyInfo.fromJson(Map<String, dynamic> json) {
face = json['face'];
mid = json['mid'];
name = json['name'];
}
}

View File

@ -15,7 +15,7 @@ class FavFolderData {
? json['list'] ? json['list']
.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e)) .map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
.toList() .toList()
: <FavFolderItemData>[]; : [FavFolderItemData()];
hasMore = json['has_more']; hasMore = json['has_more'];
} }
} }

View File

@ -7,8 +7,8 @@ import 'package:pilipala/utils/storage.dart';
class BangumiController extends GetxController { class BangumiController extends GetxController {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
RxList<BangumiListItemModel> bangumiList = <BangumiListItemModel>[].obs; RxList<BangumiListItemModel> bangumiList = [BangumiListItemModel()].obs;
RxList<BangumiListItemModel> bangumiFollowList = <BangumiListItemModel>[].obs; RxList<BangumiListItemModel> bangumiFollowList = [BangumiListItemModel()].obs;
int _currentPage = 1; int _currentPage = 1;
bool isLoadingMore = true; bool isLoadingMore = true;
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;

View File

@ -65,45 +65,6 @@ class _BangumiPanelState extends State<BangumiPanel> {
super.dispose(); super.dispose();
} }
Widget buildPageListItem(
EpisodeItem page,
int index,
bool isCurrentIndex,
) {
Color primary = Theme.of(context).colorScheme.primary;
return ListTile(
onTap: () {
Get.back();
setState(() {
changeFucCall(page, index);
});
},
dense: false,
leading: isCurrentIndex
? Image.asset(
'assets/images/live.gif',
color: primary,
height: 12,
)
: null,
title: Text(
'${index + 1}${page.longTitle!}',
style: TextStyle(
fontSize: 14,
color: isCurrentIndex
? primary
: Theme.of(context).colorScheme.onSurface,
),
),
trailing: page.badge != null
? Image.asset(
'assets/images/big-vip.png',
height: 20,
)
: const SizedBox(),
);
}
void showBangumiPanel() { void showBangumiPanel() {
showBottomSheet( showBottomSheet(
context: context, context: context,
@ -145,21 +106,37 @@ class _BangumiPanelState extends State<BangumiPanel> {
child: Material( child: Material(
child: ScrollablePositionedList.builder( child: ScrollablePositionedList.builder(
itemCount: widget.pages.length, itemCount: widget.pages.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) =>
bool isLastItem = index == widget.pages.length - 1; ListTile(
bool isCurrentIndex = currentIndex == index; onTap: () {
return isLastItem setState(() {
? SizedBox( changeFucCall(widget.pages[index], index);
height: });
MediaQuery.of(context).padding.bottom + },
20, dense: false,
leading: index == currentIndex
? Image.asset(
'assets/images/live.gif',
color: Theme.of(context).colorScheme.primary,
height: 12,
) )
: buildPageListItem( : null,
widget.pages[index], title: Text(
index, '${index + 1}${widget.pages[index].longTitle!}',
isCurrentIndex, style: TextStyle(
); fontSize: 14,
}, color: index == currentIndex
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
),
),
trailing: widget.pages[index].badge != null
? Image.asset(
'assets/images/big-vip.png',
height: 20,
)
: const SizedBox(),
),
itemScrollController: itemScrollController, itemScrollController: itemScrollController,
), ),
), ),

View File

@ -139,7 +139,7 @@ class BlackListController extends GetxController {
int currentPage = 1; int currentPage = 1;
int pageSize = 50; int pageSize = 50;
RxInt total = 0.obs; RxInt total = 0.obs;
RxList<BlackListItem> blackList = <BlackListItem>[].obs; RxList<BlackListItem> blackList = [BlackListItem()].obs;
Future queryBlacklist({type = 'init'}) async { Future queryBlacklist({type = 'init'}) async {
if (type == 'init') { if (type == 'init') {

View File

@ -20,7 +20,7 @@ import 'package:pilipala/utils/utils.dart';
class DynamicsController extends GetxController { class DynamicsController extends GetxController {
int page = 1; int page = 1;
String? offset = ''; String? offset = '';
RxList<DynamicItemModel> dynamicsList = <DynamicItemModel>[].obs; RxList<DynamicItemModel> dynamicsList = [DynamicItemModel()].obs;
Rx<DynamicsType> dynamicsType = DynamicsType.values[0].obs; Rx<DynamicsType> dynamicsType = DynamicsType.values[0].obs;
RxString dynamicsTypeLabel = '全部'.obs; RxString dynamicsTypeLabel = '全部'.obs;
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
@ -105,7 +105,7 @@ class DynamicsController extends GetxController {
onSelectType(value) async { onSelectType(value) async {
dynamicsType.value = filterTypeList[value]['value']; dynamicsType.value = filterTypeList[value]['value'];
dynamicsList.value = <DynamicItemModel>[]; dynamicsList.value = [DynamicItemModel()];
page = 1; page = 1;
initialValue.value = value; initialValue.value = value;
await queryFollowDynamic(); await queryFollowDynamic();
@ -249,8 +249,8 @@ class DynamicsController extends GetxController {
return {'status': false, 'msg': '账号未登录'}; return {'status': false, 'msg': '账号未登录'};
} }
if (type == 'init') { if (type == 'init') {
upData.value.upList = <UpItem>[]; upData.value.upList = [];
upData.value.liveList = <LiveUserItem>[]; upData.value.liveUsers = LiveUsers();
} }
var res = await DynamicsHttp.followUp(); var res = await DynamicsHttp.followUp();
if (res['status']) { if (res['status']) {
@ -258,23 +258,20 @@ class DynamicsController extends GetxController {
if (upData.value.upList!.isEmpty) { if (upData.value.upList!.isEmpty) {
mid.value = -1; mid.value = -1;
} }
upData.value.upList!.insertAll(0, [
UpItem(face: '', uname: '全部动态', mid: -1),
UpItem(face: userInfo.face, uname: '', mid: userInfo.mid),
]);
} }
return res; return res;
} }
onSelectUp(mid) async { onSelectUp(mid) async {
dynamicsType.value = DynamicsType.values[0]; dynamicsType.value = DynamicsType.values[0];
dynamicsList.value = <DynamicItemModel>[]; dynamicsList.value = [DynamicItemModel()];
page = 1; page = 1;
queryFollowDynamic(); queryFollowDynamic();
} }
onRefresh() async { onRefresh() async {
page = 1; page = 1;
print('onRefresh');
await queryFollowUp(); await queryFollowUp();
await queryFollowDynamic(); await queryFollowDynamic();
} }
@ -296,7 +293,7 @@ class DynamicsController extends GetxController {
dynamicsType.value = DynamicsType.values[0]; dynamicsType.value = DynamicsType.values[0];
initialValue.value = 0; initialValue.value = 0;
SmartDialog.showToast('还原默认加载'); SmartDialog.showToast('还原默认加载');
dynamicsList.value = <DynamicItemModel>[]; dynamicsList.value = [DynamicItemModel()];
queryFollowDynamic(); queryFollowDynamic();
} }
} }

View File

@ -17,7 +17,7 @@ class DynamicDetailController extends GetxController {
int currentPage = 0; int currentPage = 0;
bool isLoadingMore = false; bool isLoadingMore = false;
RxString noMore = ''.obs; RxString noMore = ''.obs;
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs; RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
RxInt acount = 0.obs; RxInt acount = 0.obs;
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();

View File

@ -34,25 +34,25 @@ Widget articlePanel(item, context, {floor = 1}) {
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
], ],
// Text( Text(
// item.modules.moduleDynamic.major.opus.title, item.modules.moduleDynamic.major.opus.title,
// style: Theme.of(context) style: Theme.of(context)
// .textTheme .textTheme
// .titleMedium! .titleMedium!
// .copyWith(fontWeight: FontWeight.bold), .copyWith(fontWeight: FontWeight.bold),
// ), ),
// const SizedBox(height: 2), const SizedBox(height: 2),
// if (item.modules.moduleDynamic.major.opus.summary.text != if (item.modules.moduleDynamic.major.opus.summary.text !=
// 'undefined') ...[ 'undefined') ...[
// Text( Text(
// item.modules.moduleDynamic.major.opus.summary.richTextNodes.first item.modules.moduleDynamic.major.opus.summary.richTextNodes.first
// .text, .text,
// maxLines: 4, maxLines: 4,
// style: const TextStyle(height: 1.55), style: const TextStyle(height: 1.55),
// overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
// ), ),
// const SizedBox(height: 2), const SizedBox(height: 2),
// ], ],
picWidget(item, context) picWidget(item, context)
], ],
), ),

View File

@ -45,9 +45,7 @@ class _ContentState extends State<Content> {
if (len == 1) { if (len == 1) {
OpusPicsModel pictureItem = pics.first; OpusPicsModel pictureItem = pics.first;
picList.add(pictureItem.url!); picList.add(pictureItem.url!);
spanChilds.add(const TextSpan(text: '\n'));
/// 图片上方的空白间隔
// spanChilds.add(const TextSpan(text: '\n'));
spanChilds.add( spanChilds.add(
WidgetSpan( WidgetSpan(
child: LayoutBuilder( child: LayoutBuilder(

View File

@ -19,17 +19,6 @@ InlineSpan richNode(item, context) {
// 动态页面 richTextNodes 层级可能与主页动态层级不同 // 动态页面 richTextNodes 层级可能与主页动态层级不同
richTextNodes = richTextNodes =
item.modules.moduleDynamic.major.opus.summary.richTextNodes; item.modules.moduleDynamic.major.opus.summary.richTextNodes;
if (item.modules.moduleDynamic.major.opus.title != null) {
spanChilds.add(
TextSpan(
text: item.modules.moduleDynamic.major.opus.title + '\n',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
),
);
}
} }
if (richTextNodes == null || richTextNodes.isEmpty) { if (richTextNodes == null || richTextNodes.isEmpty) {
return spacer; return spacer;

View File

@ -1,14 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/dynamics/up.dart'; import 'package:pilipala/models/dynamics/up.dart';
import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/pages/dynamics/controller.dart'; import 'package:pilipala/pages/dynamics/controller.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/utils.dart'; import 'package:pilipala/utils/utils.dart';
class UpPanel extends StatefulWidget { class UpPanel extends StatefulWidget {
final FollowUpModel upData; final FollowUpModel? upData;
const UpPanel(this.upData, {Key? key}) : super(key: key); const UpPanel(this.upData, {Key? key}) : super(key: key);
@override @override
@ -22,22 +24,38 @@ class _UpPanelState extends State<UpPanel> {
List<UpItem> upList = []; List<UpItem> upList = [];
List<LiveUserItem> liveList = []; List<LiveUserItem> liveList = [];
static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0); static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0);
late MyInfo userInfo; Box userInfoCache = GStrorage.userInfo;
var userInfo;
void listFormat() { @override
userInfo = widget.upData.myInfo!; void initState() {
upList = widget.upData.upList!; super.initState();
liveList = widget.upData.liveList!; upList = widget.upData!.upList!;
if (widget.upData!.liveUsers != null) {
liveList = widget.upData!.liveUsers!.items!;
}
upList.insert(
0,
UpItem(face: '', uname: '全部动态', mid: -1),
);
userInfo = userInfoCache.get('userInfoCache');
upList.insert(
1,
UpItem(
face: userInfo.face,
uname: '',
mid: userInfo.mid,
),
);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
listFormat();
return SliverPersistentHeader( return SliverPersistentHeader(
floating: true, floating: true,
pinned: false, pinned: false,
delegate: _SliverHeaderDelegate( delegate: _SliverHeaderDelegate(
height: liveList.isNotEmpty || upList.isNotEmpty ? 126 : 0, height: 126,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -72,7 +90,7 @@ class _UpPanelState extends State<UpPanel> {
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
child: Row( child: Row(
children: [ children: [
Flexible( Expanded(
child: ListView( child: ListView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
controller: scrollController, controller: scrollController,

View File

@ -10,7 +10,7 @@ class FansController extends GetxController {
int pn = 1; int pn = 1;
int ps = 20; int ps = 20;
int total = 0; int total = 0;
RxList<FansItemModel> fansList = <FansItemModel>[].obs; RxList<FansItemModel> fansList = [FansItemModel()].obs;
late int mid; late int mid;
late String name; late String name;
var userInfo; var userInfo;

View File

@ -91,21 +91,19 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
vsync: this, vsync: this,
); );
// 监听 tabController 切换 // 监听 tabController 切换
if (enableGradientBg) { tabController.animation!.addListener(() {
tabController.animation!.addListener(() { if (tabController.indexIsChanging) {
if (tabController.indexIsChanging) { if (initialIndex.value != tabController.index) {
if (initialIndex.value != tabController.index) { 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;
}
} }
}); } else {
} final int temp = tabController.animation!.value.round();
if (initialIndex.value != temp) {
initialIndex.value = temp;
tabController.index = initialIndex.value;
}
}
});
} }
void searchDefault() async { void searchDefault() async {

View File

@ -58,9 +58,6 @@ class _HomePageState extends State<HomePage>
return Scaffold( return Scaffold(
extendBody: true, extendBody: true,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
appBar: _homeController.enableGradientBg
? null
: AppBar(toolbarHeight: 0, elevation: 0),
body: Stack( body: Stack(
children: [ children: [
// gradient background // gradient background
@ -415,16 +412,13 @@ class SearchBar extends StatelessWidget {
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Obx( Obx(
() => Expanded( () => Text(
child: Text( ctr!.defaultSearch.value,
ctr!.defaultSearch.value, maxLines: 1,
maxLines: 1, overflow: TextOverflow.ellipsis,
overflow: TextOverflow.ellipsis, style: TextStyle(color: colorScheme.outline),
style: TextStyle(color: colorScheme.outline),
),
), ),
), ),
const SizedBox(width: 15),
], ],
), ),
), ),

View File

@ -7,7 +7,7 @@ class HotController extends GetxController {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
final int _count = 20; final int _count = 20;
int _currentPage = 1; int _currentPage = 1;
RxList<HotVideoItemModel> videoList = <HotVideoItemModel>[].obs; RxList<HotVideoItemModel> videoList = [HotVideoItemModel()].obs;
bool isLoadingMore = false; bool isLoadingMore = false;
bool flag = false; bool flag = false;
OverlayEntry? popupDialog; OverlayEntry? popupDialog;

View File

@ -12,7 +12,6 @@ import 'package:pilipala/pages/media/index.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 '../../models/common/dynamic_badge_mode.dart'; import '../../models/common/dynamic_badge_mode.dart';
import '../../models/common/nav_bar_config.dart';
class MainController extends GetxController { class MainController extends GetxController {
List<Widget> pages = <Widget>[ List<Widget> pages = <Widget>[
@ -20,7 +19,44 @@ class MainController extends GetxController {
const DynamicsPage(), const DynamicsPage(),
const MediaPage(), const MediaPage(),
]; ];
RxList navigationBars = defaultNavigationBars.obs; RxList navigationBars = [
{
'icon': const Icon(
Icons.home_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.home,
size: 21,
),
'label': "首页",
'count': 0,
},
{
'icon': const Icon(
Icons.motion_photos_on_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.motion_photos_on,
size: 21,
),
'label': "动态",
'count': 0,
},
{
'icon': const Icon(
Icons.video_collection_outlined,
size: 20,
),
'selectIcon': const Icon(
Icons.video_collection,
size: 21,
),
'label': "媒体库",
'count': 0,
}
].obs;
final StreamController<bool> bottomBarStream = final StreamController<bool> bottomBarStream =
StreamController<bool>.broadcast(); StreamController<bool>.broadcast();
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
@ -39,10 +75,6 @@ class MainController extends GetxController {
Utils.checkUpdata(); Utils.checkUpdata();
} }
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true); hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
int defaultHomePage =
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int;
selectedIndex = defaultNavigationBars
.indexWhere((item) => item['id'] == defaultHomePage);
var userInfo = userInfoCache.get('userInfoCache'); var userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null; userLogin.value = userInfo != null;
dynamicBadgeType.value = DynamicBadgeMode.values[setting.get( dynamicBadgeType.value = DynamicBadgeMode.values[setting.get(

View File

@ -20,7 +20,7 @@ class MemberController extends GetxController {
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
late int ownerMid; late int ownerMid;
// 投稿列表 // 投稿列表
RxList<VListItemModel>? archiveList = <VListItemModel>[].obs; RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
dynamic userInfo; dynamic userInfo;
RxInt attribute = (-1).obs; RxInt attribute = (-1).obs;
RxString attributeText = '关注'.obs; RxString attributeText = '关注'.obs;

View File

@ -135,103 +135,115 @@ class _ImagePreviewState extends State<ImagePreview>
), ),
body: Stack( body: Stack(
children: [ children: [
GestureDetector( DismissiblePage(
onLongPress: () => onOpenMenu(), backgroundColor: Colors.transparent,
child: ExtendedImageGesturePageView.builder( onDismissed: () {
controller: ExtendedPageController( Navigator.of(context).pop();
initialPage: _previewController.initialPage.value, },
pageSpacing: 0, // Note that scrollable widget inside DismissiblePage might limit the functionality
), // If scroll direction matches DismissiblePage direction
onPageChanged: (int index) => _previewController.onChange(index), direction: DismissiblePageDismissDirection.down,
canScrollPage: (GestureDetails? gestureDetails) => disabled: _dismissDisabled,
gestureDetails!.totalScale! <= 1.0, isFullScreen: true,
itemCount: widget.imgList!.length, child: GestureDetector(
itemBuilder: (BuildContext context, int index) { onLongPress: () => onOpenMenu(),
return ExtendedImage.network( child: ExtendedImageGesturePageView.builder(
widget.imgList![index], controller: ExtendedPageController(
fit: BoxFit.contain, initialPage: _previewController.initialPage.value,
mode: ExtendedImageMode.gesture, pageSpacing: 0,
onDoubleTap: (ExtendedImageGestureState state) { ),
final Offset? pointerDownPosition = onPageChanged: (int index) =>
state.pointerDownPosition; _previewController.onChange(index),
final double? begin = state.gestureDetails!.totalScale; canScrollPage: (GestureDetails? gestureDetails) =>
double end; gestureDetails!.totalScale! <= 1.0,
itemCount: widget.imgList!.length,
itemBuilder: (BuildContext context, int index) {
return ExtendedImage.network(
widget.imgList![index],
fit: BoxFit.contain,
mode: ExtendedImageMode.gesture,
onDoubleTap: (ExtendedImageGestureState state) {
final Offset? pointerDownPosition =
state.pointerDownPosition;
final double? begin = state.gestureDetails!.totalScale;
double end;
//remove old //remove old
_doubleClickAnimation _doubleClickAnimation
?.removeListener(_doubleClickAnimationListener); ?.removeListener(_doubleClickAnimationListener);
//stop pre //stop pre
_doubleClickAnimationController.stop(); _doubleClickAnimationController.stop();
//reset to use //reset to use
_doubleClickAnimationController.reset(); _doubleClickAnimationController.reset();
if (begin == doubleTapScales[0]) { if (begin == doubleTapScales[0]) {
setState(() { setState(() {
_dismissDisabled = true; _dismissDisabled = true;
}); });
end = doubleTapScales[1]; end = doubleTapScales[1];
} else { } else {
setState(() { setState(() {
_dismissDisabled = false; _dismissDisabled = false;
}); });
end = doubleTapScales[0]; end = doubleTapScales[0];
} }
_doubleClickAnimationListener = () { _doubleClickAnimationListener = () {
state.handleDoubleTap( state.handleDoubleTap(
scale: _doubleClickAnimation!.value, scale: _doubleClickAnimation!.value,
doubleTapPosition: pointerDownPosition); doubleTapPosition: pointerDownPosition);
}; };
_doubleClickAnimation = _doubleClickAnimationController _doubleClickAnimation = _doubleClickAnimationController
.drive(Tween<double>(begin: begin, end: end)); .drive(Tween<double>(begin: begin, end: end));
_doubleClickAnimation! _doubleClickAnimation!
.addListener(_doubleClickAnimationListener); .addListener(_doubleClickAnimationListener);
_doubleClickAnimationController.forward(); _doubleClickAnimationController.forward();
}, },
// ignore: body_might_complete_normally_nullable // ignore: body_might_complete_normally_nullable
loadStateChanged: (ExtendedImageState state) { loadStateChanged: (ExtendedImageState state) {
if (state.extendedImageLoadState == LoadState.loading) { if (state.extendedImageLoadState == LoadState.loading) {
final ImageChunkEvent? loadingProgress = final ImageChunkEvent? loadingProgress =
state.loadingProgress; state.loadingProgress;
final double? progress = final double? progress =
loadingProgress?.expectedTotalBytes != null loadingProgress?.expectedTotalBytes != null
? loadingProgress!.cumulativeBytesLoaded / ? loadingProgress!.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes! loadingProgress.expectedTotalBytes!
: null; : null;
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
SizedBox( SizedBox(
width: 150.0, width: 150.0,
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: progress, value: progress,
color: Colors.white, color: Colors.white,
),
), ),
), // const SizedBox(height: 10.0),
// const SizedBox(height: 10.0), // Text('${((progress ?? 0.0) * 100).toInt()}%',),
// Text('${((progress ?? 0.0) * 100).toInt()}%',), ],
], ),
), );
}
},
initGestureConfigHandler: (ExtendedImageState state) {
return GestureConfig(
inPageView: true,
initialScale: 1.0,
maxScale: 5.0,
animationMaxScale: 6.0,
initialAlignment: InitialAlignment.center,
); );
} },
}, );
initGestureConfigHandler: (ExtendedImageState state) { },
return GestureConfig( ),
inPageView: true,
initialScale: 1.0,
maxScale: 5.0,
animationMaxScale: 6.0,
initialAlignment: InitialAlignment.center,
);
},
);
},
), ),
), ),
Positioned( Positioned(
@ -239,49 +251,33 @@ class _ImagePreviewState extends State<ImagePreview>
right: 0, right: 0,
bottom: 0, bottom: 0,
child: Container( child: Container(
padding: EdgeInsets.only( padding: EdgeInsets.only(
left: 20, bottom: MediaQuery.of(context).padding.bottom + 30),
right: 20, decoration: const BoxDecoration(
bottom: MediaQuery.of(context).padding.bottom + 30), gradient: LinearGradient(
decoration: const BoxDecoration( begin: Alignment.topCenter,
gradient: LinearGradient( end: Alignment.bottomCenter,
begin: Alignment.topCenter, colors: <Color>[
end: Alignment.bottomCenter, Colors.transparent,
colors: <Color>[ Colors.black87,
Colors.transparent,
Colors.black87,
],
tileMode: TileMode.mirror,
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
widget.imgList!.length > 1
? Obx(
() => Text.rich(
textAlign: TextAlign.center,
TextSpan(
style: const TextStyle(
color: Colors.white, fontSize: 16),
children: [
TextSpan(
text: _previewController.currentPage
.toString()),
const TextSpan(text: ' / '),
TextSpan(
text:
widget.imgList!.length.toString()),
]),
),
)
: const SizedBox(),
IconButton(
onPressed: () => Get.back(),
icon: const Icon(Icons.close, color: Colors.white),
),
], ],
)), tileMode: TileMode.mirror,
),
),
child: Obx(
() => Text.rich(
textAlign: TextAlign.center,
TextSpan(
style: const TextStyle(color: Colors.white, fontSize: 15),
children: [
TextSpan(
text: _previewController.currentPage.toString()),
const TextSpan(text: ' / '),
TextSpan(text: widget.imgList!.length.toString()),
]),
),
),
),
), ),
], ],
), ),

View File

@ -15,7 +15,7 @@ class SSearchController extends GetxController {
Box histiryWord = GStrorage.historyword; Box histiryWord = GStrorage.historyword;
List historyCacheList = []; List historyCacheList = [];
RxList historyList = [].obs; RxList historyList = [].obs;
RxList<SearchSuggestItem> searchSuggestList = <SearchSuggestItem>[].obs; RxList<SearchSuggestItem> searchSuggestList = [SearchSuggestItem()].obs;
final _debouncer = final _debouncer =
Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间 Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间
String hintText = '搜索'; String hintText = '搜索';

View File

@ -8,7 +8,6 @@ import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/login.dart'; import 'package:pilipala/utils/login.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import '../../models/common/dynamic_badge_mode.dart'; import '../../models/common/dynamic_badge_mode.dart';
import '../../models/common/nav_bar_config.dart';
import '../main/index.dart'; import '../main/index.dart';
import 'widgets/select_dialog.dart'; import 'widgets/select_dialog.dart';
@ -24,7 +23,6 @@ class SettingController extends GetxController {
Rx<ThemeType> themeType = ThemeType.system.obs; Rx<ThemeType> themeType = ThemeType.system.obs;
var userInfo; var userInfo;
Rx<DynamicBadgeMode> dynamicBadgeType = DynamicBadgeMode.number.obs; Rx<DynamicBadgeMode> dynamicBadgeType = DynamicBadgeMode.number.obs;
RxInt defaultHomePage = 0.obs;
@override @override
void onInit() { void onInit() {
@ -42,8 +40,6 @@ class SettingController extends GetxController {
dynamicBadgeType.value = DynamicBadgeMode.values[setting.get( dynamicBadgeType.value = DynamicBadgeMode.values[setting.get(
SettingBoxKey.dynamicBadgeMode, SettingBoxKey.dynamicBadgeMode,
defaultValue: DynamicBadgeMode.number.code)]; defaultValue: DynamicBadgeMode.number.code)];
defaultHomePage.value =
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0);
} }
loginOut() async { loginOut() async {
@ -114,24 +110,4 @@ class SettingController extends GetxController {
SmartDialog.showToast('设置成功'); SmartDialog.showToast('设置成功');
} }
} }
// 设置默认启动页
seteDefaultHomePage(BuildContext context) async {
int? result = await showDialog(
context: context,
builder: (context) {
return SelectDialog<int>(
title: '首页启动页',
value: defaultHomePage.value,
values: defaultNavigationBars.map((e) {
return {'title': e['label'], 'value': e['id']};
}).toList());
},
);
if (result != null) {
defaultHomePage.value = result;
setting.put(SettingBoxKey.defaultHomePage, result);
SmartDialog.showToast('设置成功,重启生效');
}
}
} }

View File

@ -40,6 +40,10 @@ class _TabbarSetPageState extends State<TabbarSetPage> {
.where((i) => tabbarSort.contains((i['type'] as TabType).id)) .where((i) => tabbarSort.contains((i['type'] as TabType).id))
.map<String>((i) => (i['type'] as TabType).id) .map<String>((i) => (i['type'] as TabType).id)
.toList(); .toList();
if (sortedTabbar.isEmpty) {
SmartDialog.showToast('请至少设置一项!');
return;
}
settingStorage.put(SettingBoxKey.tabbarSort, sortedTabbar); settingStorage.put(SettingBoxKey.tabbarSort, sortedTabbar);
SmartDialog.showToast('保存成功,下次启动时生效'); SmartDialog.showToast('保存成功,下次启动时生效');
} }

View File

@ -1,88 +0,0 @@
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/utils/global_data.dart';
import '../../../models/common/gesture_mode.dart';
import '../../../utils/storage.dart';
import '../widgets/select_dialog.dart';
import '../widgets/switch_item.dart';
class PlayGesturePage extends StatefulWidget {
const PlayGesturePage({super.key});
@override
State<PlayGesturePage> createState() => _PlayGesturePageState();
}
class _PlayGesturePageState extends State<PlayGesturePage> {
Box setting = GStrorage.setting;
late int fullScreenGestureMode;
@override
void initState() {
super.initState();
fullScreenGestureMode = setting.get(SettingBoxKey.fullScreenGestureMode,
defaultValue: FullScreenGestureMode.values.last.index);
}
@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,
titleSpacing: 0,
title: Text(
'手势设置',
style: Theme.of(context).textTheme.titleMedium,
),
),
body: ListView(
children: [
ListTile(
dense: false,
title: Text('全屏手势', style: titleStyle),
subtitle: Text(
'通过手势快速进入全屏',
style: subTitleStyle,
),
onTap: () async {
String? result = await showDialog(
context: context,
builder: (context) {
return SelectDialog<String>(
title: '全屏手势',
value: FullScreenGestureMode
.values[fullScreenGestureMode].values,
values: FullScreenGestureMode.values.map((e) {
return {'title': e.labels, 'value': e.values};
}).toList());
},
);
if (result != null) {
GlobalData().fullScreenGestureMode = FullScreenGestureMode
.values
.firstWhere((element) => element.values == result);
fullScreenGestureMode =
GlobalData().fullScreenGestureMode.index;
setting.put(
SettingBoxKey.fullScreenGestureMode, fullScreenGestureMode);
setState(() {});
}
},
),
const SetSwitchItem(
title: '双击快退/快进',
subTitle: '左侧双击快退,右侧双击快进',
setKey: SettingBoxKey.enableQuickDouble,
defaultVal: true,
),
],
),
);
}
}

View File

@ -7,7 +7,6 @@ import 'package:pilipala/models/video/play/quality.dart';
import 'package:pilipala/pages/setting/widgets/select_dialog.dart'; import 'package:pilipala/pages/setting/widgets/select_dialog.dart';
import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/services/service_locator.dart'; import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'widgets/switch_item.dart'; import 'widgets/switch_item.dart';
@ -74,12 +73,6 @@ class _PlaySettingState extends State<PlaySetting> {
title: Text('倍速设置', style: titleStyle), title: Text('倍速设置', style: titleStyle),
subtitle: Text('设置视频播放速度', style: subTitleStyle), subtitle: Text('设置视频播放速度', style: subTitleStyle),
), ),
ListTile(
dense: false,
onTap: () => Get.toNamed('/playerGestureSet'),
title: Text('手势设置', style: titleStyle),
subtitle: Text('设置播放器手势', style: subTitleStyle),
),
const SetSwitchItem( const SetSwitchItem(
title: '开启1080P', title: '开启1080P',
subTitle: '免登录查看1080P视频', subTitle: '免登录查看1080P视频',
@ -141,20 +134,18 @@ class _PlaySettingState extends State<PlaySetting> {
setKey: SettingBoxKey.enableAutoBrightness, setKey: SettingBoxKey.enableAutoBrightness,
defaultVal: false, defaultVal: false,
), ),
const SetSwitchItem(
title: '双击快退/快进',
subTitle: '左侧双击快退,右侧双击快进',
setKey: SettingBoxKey.enableQuickDouble,
defaultVal: true,
),
const SetSwitchItem( const SetSwitchItem(
title: '弹幕开关', title: '弹幕开关',
subTitle: '展示弹幕', subTitle: '展示弹幕',
setKey: SettingBoxKey.enableShowDanmaku, setKey: SettingBoxKey.enableShowDanmaku,
defaultVal: false, defaultVal: false,
), ),
SetSwitchItem(
title: '控制栏动画',
subTitle: '播放器控制栏显示动画效果',
setKey: SettingBoxKey.enablePlayerControlAnimation,
defaultVal: true,
callFn: (bool val) {
GlobalData().enablePlayerControlAnimation = val;
}),
ListTile( ListTile(
dense: false, dense: false,
title: Text('默认画质', style: titleStyle), title: Text('默认画质', style: titleStyle),

View File

@ -12,7 +12,6 @@ import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import '../../models/common/dynamic_badge_mode.dart'; import '../../models/common/dynamic_badge_mode.dart';
import '../../models/common/nav_bar_config.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/switch_item.dart'; import 'widgets/switch_item.dart';
@ -30,6 +29,7 @@ class _StyleSettingState extends State<StyleSetting> {
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
late int picQuality; late int picQuality;
late double toastOpacity;
late ThemeType _tempThemeValue; late ThemeType _tempThemeValue;
late dynamic defaultCustomRows; late dynamic defaultCustomRows;
@ -37,6 +37,7 @@ class _StyleSettingState extends State<StyleSetting> {
void initState() { void initState() {
super.initState(); super.initState();
picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10); picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
toastOpacity = setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0);
_tempThemeValue = settingController.themeType.value; _tempThemeValue = settingController.themeType.value;
defaultCustomRows = setting.get(SettingBoxKey.customRows, defaultValue: 2); defaultCustomRows = setting.get(SettingBoxKey.customRows, defaultValue: 2);
} }
@ -266,14 +267,6 @@ class _StyleSettingState extends State<StyleSetting> {
'当前主题:${colorSelectController.type.value == 0 ? '动态取色' : '指定颜色'}', '当前主题:${colorSelectController.type.value == 0 ? '动态取色' : '指定颜色'}',
style: subTitleStyle)), style: subTitleStyle)),
), ),
ListTile(
dense: false,
onTap: () => settingController.seteDefaultHomePage(context),
title: Text('默认启动页', style: titleStyle),
subtitle: Obx(() => Text(
'当前启动页:${defaultNavigationBars.firstWhere((e) => e['id'] == settingController.defaultHomePage.value)['label']}',
style: subTitleStyle)),
),
ListTile( ListTile(
dense: false, dense: false,
onTap: () => Get.toNamed('/fontSizeSetting'), onTap: () => Get.toNamed('/fontSizeSetting'),

View File

@ -128,8 +128,6 @@ class VideoDetailController extends GetxController
controller: plPlayerController, controller: plPlayerController,
videoDetailCtr: this, videoDetailCtr: this,
floating: floating, floating: floating,
bvid: bvid,
videoType: videoType,
); );
// CDN优化 // CDN优化
enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true); enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true);

View File

@ -22,9 +22,8 @@ import '../related/index.dart';
import 'widgets/group_panel.dart'; import 'widgets/group_panel.dart';
class VideoIntroController extends GetxController { class VideoIntroController extends GetxController {
VideoIntroController({required this.bvid});
// 视频bvid // 视频bvid
String bvid; String bvid = Get.parameters['bvid']!;
// 是否预渲染 骨架屏 // 是否预渲染 骨架屏
bool preRender = false; bool preRender = false;

View File

@ -24,10 +24,7 @@ import 'widgets/page.dart';
import 'widgets/season.dart'; import 'widgets/season.dart';
class VideoIntroPanel extends StatefulWidget { class VideoIntroPanel extends StatefulWidget {
final String bvid; const VideoIntroPanel({super.key});
final String? cid;
const VideoIntroPanel({super.key, required this.bvid, this.cid});
@override @override
State<VideoIntroPanel> createState() => _VideoIntroPanelState(); State<VideoIntroPanel> createState() => _VideoIntroPanelState();
@ -50,8 +47,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
/// fix 全屏时参数丢失 /// fix 全屏时参数丢失
heroTag = Get.arguments['heroTag']; heroTag = Get.arguments['heroTag'];
videoIntroController = videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
Get.put(VideoIntroController(bvid: widget.bvid), tag: heroTag);
_futureBuilderFuture = videoIntroController.queryVideoIntro(); _futureBuilderFuture = videoIntroController.queryVideoIntro();
videoIntroController.videoDetail.listen((value) { videoIntroController.videoDetail.listen((value) {
videoDetail = value; videoDetail = value;
@ -81,7 +77,6 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
loadingStatus: false, loadingStatus: false,
videoDetail: videoIntroController.videoDetail.value, videoDetail: videoIntroController.videoDetail.value,
heroTag: heroTag, heroTag: heroTag,
bvid: widget.bvid,
), ),
); );
} else { } else {
@ -100,7 +95,6 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
loadingStatus: true, loadingStatus: true,
videoDetail: videoDetail, videoDetail: videoDetail,
heroTag: heroTag, heroTag: heroTag,
bvid: widget.bvid,
); );
} }
}, },
@ -112,15 +106,10 @@ class VideoInfo extends StatefulWidget {
final bool loadingStatus; final bool loadingStatus;
final VideoDetailData? videoDetail; final VideoDetailData? videoDetail;
final String? heroTag; final String? heroTag;
final String bvid;
const VideoInfo({ const VideoInfo(
Key? key, {Key? key, this.loadingStatus = false, this.videoDetail, this.heroTag})
this.loadingStatus = false, : super(key: key);
this.videoDetail,
this.heroTag,
required this.bvid,
}) : super(key: key);
@override @override
State<VideoInfo> createState() => _VideoInfoState(); State<VideoInfo> createState() => _VideoInfoState();
@ -160,8 +149,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
void initState() { void initState() {
super.initState(); super.initState();
heroTag = widget.heroTag!; heroTag = widget.heroTag!;
videoIntroController = videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
Get.put(VideoIntroController(bvid: widget.bvid), tag: heroTag);
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag); videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
videoItem = videoIntroController.videoItem!; videoItem = videoIntroController.videoItem!;
sheetHeight = localCache.get('sheetHeight'); sheetHeight = localCache.get('sheetHeight');

View File

@ -56,37 +56,6 @@ class _PagesPanelState extends State<PagesPanel> {
super.dispose(); super.dispose();
} }
Widget buildEpisodeListItem(
Part episode,
int index,
bool isCurrentIndex,
) {
Color primary = Theme.of(context).colorScheme.primary;
return ListTile(
onTap: () {
changeFucCall(episode, index);
Get.back();
},
dense: false,
leading: isCurrentIndex
? Image.asset(
'assets/images/live.gif',
color: primary,
height: 12,
)
: null,
title: Text(
episode.pagePart!,
style: TextStyle(
fontSize: 14,
color: isCurrentIndex
? primary
: Theme.of(context).colorScheme.onSurface,
),
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
@ -162,25 +131,39 @@ class _PagesPanelState extends State<PagesPanel> {
child: Material( child: Material(
child: ListView.builder( child: ListView.builder(
controller: _scrollController, controller: _scrollController,
itemCount: episodes.length + 1, itemCount: episodes.length,
itemBuilder: itemBuilder:
(BuildContext context, int index) { (BuildContext context, int index) {
bool isLastItem = return ListTile(
index == episodes.length; onTap: () {
bool isCurrentIndex = changeFucCall(
currentIndex == index; episodes[index], index);
return isLastItem Get.back();
? SizedBox( },
height: MediaQuery.of(context) dense: false,
.padding leading: index == currentIndex
.bottom + ? Image.asset(
20, 'assets/images/live.gif',
) color: Theme.of(context)
: buildEpisodeListItem( .colorScheme
episodes[index], .primary,
index, height: 12,
isCurrentIndex, )
); : null,
title: Text(
episodes[index].pagePart!,
style: TextStyle(
fontSize: 14,
color: index == currentIndex
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.onSurface,
),
),
);
}, },
), ),
), ),
@ -209,7 +192,6 @@ class _PagesPanelState extends State<PagesPanel> {
itemCount: widget.pages.length, itemCount: widget.pages.length,
itemExtent: 150, itemExtent: 150,
itemBuilder: (BuildContext context, int i) { itemBuilder: (BuildContext context, int i) {
bool isCurrentIndex = currentIndex == i;
return Container( return Container(
width: 150, width: 150,
margin: const EdgeInsets.only(right: 10), margin: const EdgeInsets.only(right: 10),
@ -224,7 +206,7 @@ class _PagesPanelState extends State<PagesPanel> {
vertical: 8, horizontal: 8), vertical: 8, horizontal: 8),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
if (isCurrentIndex) ...<Widget>[ if (i == currentIndex) ...<Widget>[
Image.asset( Image.asset(
'assets/images/live.gif', 'assets/images/live.gif',
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
@ -238,7 +220,7 @@ class _PagesPanelState extends State<PagesPanel> {
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
color: isCurrentIndex color: i == currentIndex
? Theme.of(context).colorScheme.primary ? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface), : Theme.of(context).colorScheme.onSurface),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,

View File

@ -80,34 +80,6 @@ class _SeasonPanelState extends State<SeasonPanel> {
super.dispose(); super.dispose();
} }
Widget buildEpisodeListItem(
EpisodeItem episode,
int index,
bool isCurrentIndex,
) {
Color primary = Theme.of(context).colorScheme.primary;
return ListTile(
onTap: () => changeFucCall(episode, index),
dense: false,
leading: isCurrentIndex
? Image.asset(
'assets/images/live.gif',
color: primary,
height: 12,
)
: null,
title: Text(
episode.title!,
style: TextStyle(
fontSize: 14,
color: isCurrentIndex
? primary
: Theme.of(context).colorScheme.onSurface,
),
),
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Builder(builder: (BuildContext context) { return Builder(builder: (BuildContext context) {
@ -162,22 +134,32 @@ class _SeasonPanelState extends State<SeasonPanel> {
child: Material( child: Material(
child: ScrollablePositionedList.builder( child: ScrollablePositionedList.builder(
itemCount: episodes.length, itemCount: episodes.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) =>
bool isLastItem = index == episodes.length - 1; ListTile(
bool isCurrentIndex = currentIndex == index; onTap: () =>
return isLastItem changeFucCall(episodes[index], index),
? SizedBox( dense: false,
height: MediaQuery.of(context) leading: index == currentIndex
.padding ? Image.asset(
.bottom + 'assets/images/live.gif',
20, color: Theme.of(context)
.colorScheme
.primary,
height: 12,
) )
: buildEpisodeListItem( : null,
episodes[index], title: Text(
index, episodes[index].title!,
isCurrentIndex, style: TextStyle(
); fontSize: 14,
}, color: index == currentIndex
? Theme.of(context).colorScheme.primary
: Theme.of(context)
.colorScheme
.onSurface,
),
),
),
itemScrollController: itemScrollController, itemScrollController: itemScrollController,
), ),
), ),

View File

@ -22,7 +22,7 @@ class VideoReplyController extends GetxController {
String? replyLevel; String? replyLevel;
// rpid 请求楼中楼回复 // rpid 请求楼中楼回复
String? rpid; String? rpid;
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs; RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
// 当前页 // 当前页
int currentPage = 0; int currentPage = 0;
bool isLoadingMore = false; bool isLoadingMore = false;
@ -62,7 +62,6 @@ class VideoReplyController extends GetxController {
noMore.value = ''; noMore.value = '';
} }
if (noMore.value == '没有更多了') { if (noMore.value == '没有更多了') {
isLoadingMore = false;
return; return;
} }
final res = await ReplyHttp.replyList( final res = await ReplyHttp.replyList(

View File

@ -134,13 +134,13 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
super.build(context); super.build(context);
return RefreshIndicator( return RefreshIndicator(
onRefresh: () async { onRefresh: () async {
return await _videoReplyController.queryReplyList(type: 'init'); _videoReplyController.currentPage = 0;
return await _videoReplyController.queryReplyList();
}, },
child: Stack( child: Stack(
children: [ children: [
CustomScrollView( CustomScrollView(
controller: scrollController, controller: scrollController,
physics: const AlwaysScrollableScrollPhysics(),
key: const PageStorageKey<String>('评论'), key: const PageStorageKey<String>('评论'),
slivers: <Widget>[ slivers: <Widget>[
SliverPersistentHeader( SliverPersistentHeader(

View File

@ -462,9 +462,6 @@ class ReplyItemRow extends StatelessWidget {
InlineSpan buildContent( InlineSpan buildContent(
BuildContext context, replyItem, replyReply, fReplyItem) { BuildContext context, replyItem, replyReply, fReplyItem) {
final String routePath = Get.currentRoute;
bool isVideoPage = routePath.startsWith('/video');
// replyItem 当前回复内容 // replyItem 当前回复内容
// replyReply 查看二楼回复(回复详情)回调 // replyReply 查看二楼回复(回复详情)回调
// fReplyItem 父级回复内容,用作二楼回复(回复详情)展示 // fReplyItem 父级回复内容,用作二楼回复(回复详情)展示
@ -506,25 +503,21 @@ InlineSpan buildContent(
.replaceAll('&quot;', '"') .replaceAll('&quot;', '"')
.replaceAll('&apos;', "'") .replaceAll('&apos;', "'")
.replaceAll('&nbsp;', ' '); .replaceAll('&nbsp;', ' ');
// print("content.jumpUrl.keys:" + content.jumpUrl.keys.toString());
// 构建正则表达式 // 构建正则表达式
final List<String> specialTokens = [ final List<String> specialTokens = [
...content.emote.keys, ...content.emote.keys,
...content.topicsMeta?.keys?.map((e) => '#$e#') ?? [], ...content.topicsMeta?.keys?.map((e) => '#$e#') ?? [],
...content.atNameToMid.keys.map((e) => '@$e'), ...content.atNameToMid.keys.map((e) => '@$e'),
...content.jumpUrl.keys.map((e) =>
e.replaceAll('?', '\\?').replaceAll('+', '\\+').replaceAll('*', '\\*')),
]; ];
List<dynamic> jumpUrlKeysList = content.jumpUrl.keys.map((e) {
return e.replaceAllMapped(
RegExp(r'[?+*]'), (match) => '\\${match.group(0)}');
}).toList();
String patternStr = specialTokens.map(RegExp.escape).join('|'); String patternStr = specialTokens.map(RegExp.escape).join('|');
if (patternStr.isNotEmpty) { if (patternStr.isNotEmpty) {
patternStr += "|"; patternStr += "|";
} }
patternStr += r'(\b(?:\d+[:])?[0-5]?[0-9][:][0-5]?[0-9]\b)'; patternStr += r'(\b(?:\d+[:])?[0-5]?[0-9][:][0-5]?[0-9]\b)';
if (jumpUrlKeysList.isNotEmpty) {
patternStr += '|${jumpUrlKeysList.join('|')}';
}
final RegExp pattern = RegExp(patternStr); final RegExp pattern = RegExp(patternStr);
List<String> matchedStrs = []; List<String> matchedStrs = [];
void addPlainTextSpan(str) { void addPlainTextSpan(str) {
@ -578,31 +571,27 @@ InlineSpan buildContent(
spanChilds.add( spanChilds.add(
TextSpan( TextSpan(
text: ' $matchStr ', text: ' $matchStr ',
style: isVideoPage style: TextStyle(
? TextStyle( color: Theme.of(context).colorScheme.primary,
color: Theme.of(context).colorScheme.primary, ),
)
: null,
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
// 跳转到指定位置 // 跳转到指定位置
if (isVideoPage) { try {
try { SmartDialog.showToast('跳转至:$matchStr');
SmartDialog.showToast('跳转至:$matchStr'); Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
Get.find<VideoDetailController>( .plPlayerController
tag: Get.arguments['heroTag']) .seekTo(
.plPlayerController Duration(seconds: Utils.duration(matchStr)),
.seekTo( );
Duration(seconds: Utils.duration(matchStr)), } catch (e) {
); SmartDialog.showToast('跳转失败: $e');
} catch (e) {
SmartDialog.showToast('跳转失败: $e');
}
} }
}, },
), ),
); );
} else { } else {
print("matchStr=$matchStr");
String appUrlSchema = ''; String appUrlSchema = '';
final bool enableWordRe = setting.get(SettingBoxKey.enableWordRe, final bool enableWordRe = setting.get(SettingBoxKey.enableWordRe,
defaultValue: false) as bool; defaultValue: false) as bool;

View File

@ -109,26 +109,21 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
@override @override
void didChangeMetrics() { void didChangeMetrics() {
super.didChangeMetrics(); super.didChangeMetrics();
final String routePath = Get.currentRoute; WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && // 键盘高度
(routePath.startsWith('/video') || final viewInsets = EdgeInsets.fromViewPadding(
routePath.startsWith('/dynamicDetail'))) { View.of(context).viewInsets, View.of(context).devicePixelRatio);
WidgetsBinding.instance.addPostFrameCallback((_) { _debouncer.run(() {
// 键盘高度 if (mounted) {
final viewInsets = EdgeInsets.fromViewPadding( if (keyboardHeight == 0 && emoteHeight == 0) {
View.of(context).viewInsets, View.of(context).devicePixelRatio); setState(() {
_debouncer.run(() { emoteHeight = keyboardHeight =
if (mounted) { keyboardHeight == 0.0 ? viewInsets.bottom : keyboardHeight;
if (keyboardHeight == 0 && emoteHeight == 0) { });
setState(() {
emoteHeight = keyboardHeight =
keyboardHeight == 0.0 ? viewInsets.bottom : keyboardHeight;
});
}
} }
}); }
}); });
} });
} }
@override @override
@ -136,15 +131,11 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
_replyContentController.dispose(); _replyContentController.dispose();
replyContentFocusNode.removeListener(() {}); replyContentFocusNode.removeListener(() {});
replyContentFocusNode.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
double keyboardHeight = EdgeInsets.fromViewPadding(
View.of(context).viewInsets, View.of(context).devicePixelRatio)
.bottom;
return Container( return Container(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: BoxDecoration( decoration: BoxDecoration(

View File

@ -12,7 +12,7 @@ class VideoReplyReplyController extends GetxController {
// rpid 请求楼中楼回复 // rpid 请求楼中楼回复
String? rpid; String? rpid;
ReplyType replyType = ReplyType.video; ReplyType replyType = ReplyType.video;
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs; RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
// 当前页 // 当前页
int currentPage = 0; int currentPage = 0;
bool isLoadingMore = false; bool isLoadingMore = false;

View File

@ -5,7 +5,6 @@ import 'dart:ui';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:floating/floating.dart'; import 'package:floating/floating.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:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -24,6 +23,7 @@ import 'package:pilipala/plugin/pl_player/models/play_repeat.dart';
import 'package:pilipala/services/service_locator.dart'; import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/plugin/pl_player/utils/fullscreen.dart';
import '../../../services/shutdown_timer_service.dart'; import '../../../services/shutdown_timer_service.dart';
import 'widgets/header_control.dart'; import 'widgets/header_control.dart';
@ -58,7 +58,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
late bool autoExitFullcreen; late bool autoExitFullcreen;
late bool autoPlayEnable; late bool autoPlayEnable;
late bool autoPiP; late bool autoPiP;
late Floating floating; final Floating floating = Floating();
// 生命周期监听
late final AppLifecycleListener _lifecycleListener;
bool isShowing = true; bool isShowing = true;
@override @override
@ -66,9 +68,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
super.initState(); super.initState();
heroTag = Get.arguments['heroTag']; heroTag = Get.arguments['heroTag'];
videoDetailController = Get.put(VideoDetailController(), tag: heroTag); videoDetailController = Get.put(VideoDetailController(), tag: heroTag);
videoIntroController = Get.put( videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
VideoIntroController(bvid: Get.parameters['bvid']!),
tag: heroTag);
videoIntroController.videoDetail.listen((value) { videoIntroController.videoDetail.listen((value) {
videoPlayerServiceHandler.onVideoDetailChange( videoPlayerServiceHandler.onVideoDetailChange(
value, videoDetailController.cid.value); value, videoDetailController.cid.value);
@ -91,11 +91,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
videoSourceInit(); videoSourceInit();
appbarStreamListen(); appbarStreamListen();
lifecycleListener();
fullScreenStatusListener(); fullScreenStatusListener();
if (Platform.isAndroid) {
floating = videoDetailController.floating!;
autoEnterPip();
}
} }
// 获取视频资源,初始化播放器 // 获取视频资源,初始化播放器
@ -153,10 +150,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
} }
} catch (_) {} } catch (_) {}
} }
if (Platform.isAndroid) {
floating.toggleAutoPip(
autoEnter: status == PlayerStatus.playing && autoPiP);
}
} }
// 继续播放或重新播放 // 继续播放或重新播放
@ -175,6 +168,27 @@ class _VideoDetailPageState extends State<VideoDetailPage>
videoDetailController.isShowCover.value = false; videoDetailController.isShowCover.value = false;
} }
// 生命周期监听
void lifecycleListener() {
_lifecycleListener = AppLifecycleListener(
onResume: () => _handleTransition('resume'),
// 后台
onInactive: () => _handleTransition('inactive'),
// 在Android和iOS端不生效
onHide: () => _handleTransition('hide'),
onShow: () => _handleTransition('show'),
onPause: () => _handleTransition('pause'),
onRestart: () => _handleTransition('restart'),
onDetach: () => _handleTransition('detach'),
// 只作用于桌面端
onExitRequested: () {
ScaffoldMessenger.maybeOf(context)
?.showSnackBar(const SnackBar(content: Text("拦截应用退出")));
return Future.value(AppExitResponse.cancel);
},
);
}
void fullScreenStatusListener() { void fullScreenStatusListener() {
plPlayerController?.isFullScreen.listen((bool isFullScreen) { plPlayerController?.isFullScreen.listen((bool isFullScreen) {
if (isFullScreen) { if (isFullScreen) {
@ -194,10 +208,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
videoDetailController.floating!.dispose(); videoDetailController.floating!.dispose();
} }
videoPlayerServiceHandler.onVideoDetailDispose(); videoPlayerServiceHandler.onVideoDetailDispose();
if (Platform.isAndroid) { floating.dispose();
floating.toggleAutoPip(autoEnter: false); _lifecycleListener.dispose();
floating.dispose();
}
super.dispose(); super.dispose();
} }
@ -250,10 +262,29 @@ class _VideoDetailPageState extends State<VideoDetailPage>
.subscribe(this, ModalRoute.of(context)! as PageRoute); .subscribe(this, ModalRoute.of(context)! as PageRoute);
} }
void _handleTransition(String name) {
switch (name) {
case 'inactive':
if (plPlayerController != null &&
playerStatus == PlayerStatus.playing) {
autoEnterPip();
}
break;
}
}
void autoEnterPip() { void autoEnterPip() {
final String routePath = Get.currentRoute; final String routePath = Get.currentRoute;
if (autoPiP && routePath.startsWith('/video')) { final bool isPortrait =
floating.toggleAutoPip(autoEnter: autoPiP); MediaQuery.of(context).orientation == Orientation.portrait;
/// TODO 横屏全屏状态下误触pip
if (autoPiP && routePath.startsWith('/video') && isPortrait) {
floating.enable(
aspectRatio: Rational(
videoDetailController.data.dash!.video!.first.width!,
videoDetailController.data.dash!.video!.first.height!,
));
} }
} }
@ -373,7 +404,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
), ),
); );
} else { } else {
return buildCustomAppBar(); return const SizedBox();
} }
}, },
), ),
@ -416,7 +447,33 @@ class _VideoDetailPageState extends State<VideoDetailPage>
top: 0, top: 0,
left: 0, left: 0,
right: 0, right: 0,
child: buildCustomAppBar(), child: AppBar(
primary: false,
foregroundColor:
Colors.white,
elevation: 0,
scrolledUnderElevation: 0,
backgroundColor:
Colors.transparent,
actions: [
IconButton(
tooltip: '稍后再看',
onPressed: () async {
var res = await UserHttp
.toViewLater(
bvid: videoDetailController
.bvid);
SmartDialog
.showToast(
res['msg']);
},
icon: const Icon(Icons
.history_outlined),
),
const SizedBox(
width: 14)
],
),
), ),
Positioned( Positioned(
right: 12, right: 12,
@ -493,8 +550,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
slivers: <Widget>[ slivers: <Widget>[
if (videoDetailController.videoType == if (videoDetailController.videoType ==
SearchType.video) ...[ SearchType.video) ...[
VideoIntroPanel( const VideoIntroPanel(),
bvid: videoDetailController.bvid),
] else if (videoDetailController.videoType == ] else if (videoDetailController.videoType ==
SearchType.media_bangumi) ...[ SearchType.media_bangumi) ...[
Obx(() => BangumiIntroPanel( Obx(() => BangumiIntroPanel(
@ -521,7 +577,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
.withOpacity(0.06), .withOpacity(0.06),
), ),
), ),
const RelatedVideoPanel(), RelatedVideoPanel(),
], ],
); );
}, },
@ -571,8 +627,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
headerControl: HeaderControl( headerControl: HeaderControl(
controller: plPlayerController, controller: plPlayerController,
videoDetailCtr: videoDetailController, videoDetailCtr: videoDetailController,
bvid: videoDetailController.bvid,
videoType: videoDetailController.videoType,
), ),
danmuWidget: Obx( danmuWidget: Obx(
() => PlDanmaku( () => PlDanmaku(
@ -599,48 +653,4 @@ class _VideoDetailPageState extends State<VideoDetailPage>
return childWhenDisabled; return childWhenDisabled;
} }
} }
Widget buildCustomAppBar() {
return AppBar(
backgroundColor: Colors.transparent, // 使背景透明
foregroundColor: Colors.white,
elevation: 0,
scrolledUnderElevation: 0,
primary: false,
centerTitle: false,
automaticallyImplyLeading: false,
titleSpacing: 0,
title: Container(
height: kToolbarHeight,
padding: const EdgeInsets.symmetric(horizontal: 14),
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
colors: <Color>[
Colors.transparent,
Colors.black54,
],
tileMode: TileMode.mirror,
)),
child: Row(
children: [
ComBtn(
icon: const Icon(FontAwesomeIcons.arrowLeft, size: 15),
fuc: () => Get.back(),
),
const Spacer(),
ComBtn(
icon: const Icon(Icons.history_outlined, size: 22),
fuc: () async {
var res = await UserHttp.toViewLater(
bvid: videoDetailController.bvid);
SmartDialog.showToast(res['msg']);
},
),
],
),
),
);
}
} }

View File

@ -19,7 +19,6 @@ 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/http/danmaku.dart'; import 'package:pilipala/http/danmaku.dart';
import 'package:pilipala/services/shutdown_timer_service.dart'; import 'package:pilipala/services/shutdown_timer_service.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';
@ -28,15 +27,11 @@ class HeaderControl extends StatefulWidget implements PreferredSizeWidget {
this.controller, this.controller,
this.videoDetailCtr, this.videoDetailCtr,
this.floating, this.floating,
this.bvid,
this.videoType,
super.key, super.key,
}); });
final PlPlayerController? controller; final PlPlayerController? controller;
final VideoDetailController? videoDetailCtr; final VideoDetailController? videoDetailCtr;
final Floating? floating; final Floating? floating;
final String? bvid;
final SearchType? videoType;
@override @override
State<HeaderControl> createState() => _HeaderControlState(); State<HeaderControl> createState() => _HeaderControlState();
@ -67,8 +62,7 @@ class _HeaderControlState extends State<HeaderControl> {
speedsList = widget.controller!.speedsList; speedsList = widget.controller!.speedsList;
fullScreenStatusListener(); fullScreenStatusListener();
heroTag = Get.arguments['heroTag']; heroTag = Get.arguments['heroTag'];
videoIntroController = videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
Get.put(VideoIntroController(bvid: widget.bvid!), tag: heroTag);
} }
void fullScreenStatusListener() { void fullScreenStatusListener() {
@ -1110,16 +1104,14 @@ class _HeaderControlState extends State<HeaderControl> {
}, },
), ),
SizedBox(width: buttonSpace), SizedBox(width: buttonSpace),
if (showTitle && if (showTitle && isLandscape) ...[
isLandscape &&
widget.videoType == SearchType.video) ...[
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 200), constraints: BoxConstraints(maxWidth: 200),
child: Text( child: Text(
videoIntroController.videoDetail.value.title ?? '', videoIntroController.videoDetail.value.title!,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 16, fontSize: 16,

View File

@ -131,13 +131,13 @@ class WebviewController extends GetxController {
Get.back(); Get.back();
} else { } else {
// 获取用户信息失败 // 获取用户信息失败
SmartDialog.showToast(result['msg']); SmartDialog.showToast(result.msg);
Clipboard.setData(ClipboardData(text: result['msg'])); Clipboard.setData(ClipboardData(text: result.msg.toString()));
} }
} catch (e) { } catch (e) {
SmartDialog.showNotify(msg: e.toString(), notifyType: NotifyType.warning); SmartDialog.showNotify(msg: e.toString(), notifyType: NotifyType.warning);
content = content + e.toString(); content = content + e.toString();
Clipboard.setData(ClipboardData(text: content));
} }
Clipboard.setData(ClipboardData(text: content));
} }
} }

View File

@ -51,31 +51,27 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
@override @override
void didChangeMetrics() { void didChangeMetrics() {
super.didChangeMetrics(); super.didChangeMetrics();
final String routePath = Get.currentRoute; WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && routePath.startsWith('/whisper_detail')) { // 键盘高度
WidgetsBinding.instance.addPostFrameCallback((_) { final viewInsets = EdgeInsets.fromViewPadding(
// 键盘高度 View.of(context).viewInsets, View.of(context).devicePixelRatio);
final viewInsets = EdgeInsets.fromViewPadding( _debouncer.run(() {
View.of(context).viewInsets, View.of(context).devicePixelRatio); if (mounted) {
_debouncer.run(() { if (keyboardHeight == 0) {
if (mounted) { setState(() {
if (keyboardHeight == 0) { emoteHeight = keyboardHeight =
setState(() { keyboardHeight == 0.0 ? viewInsets.bottom : keyboardHeight;
emoteHeight = keyboardHeight = });
keyboardHeight == 0.0 ? viewInsets.bottom : keyboardHeight;
});
}
} }
}); }
}); });
} });
} }
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
replyContentFocusNode.removeListener(() {}); replyContentFocusNode.removeListener(() {});
replyContentFocusNode.dispose();
super.dispose(); super.dispose();
} }

View File

@ -9,7 +9,6 @@ import 'package:hive/hive.dart';
import 'package:media_kit/media_kit.dart'; import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart'; import 'package:media_kit_video/media_kit_video.dart';
import 'package:nil/nil.dart'; import 'package:nil/nil.dart';
import 'package:pilipala/models/common/gesture_mode.dart';
import 'package:pilipala/plugin/pl_player/controller.dart'; import 'package:pilipala/plugin/pl_player/controller.dart';
import 'package:pilipala/plugin/pl_player/models/duration.dart'; import 'package:pilipala/plugin/pl_player/models/duration.dart';
import 'package:pilipala/plugin/pl_player/models/fullscreen_mode.dart'; import 'package:pilipala/plugin/pl_player/models/fullscreen_mode.dart';
@ -18,7 +17,6 @@ import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:screen_brightness/screen_brightness.dart'; import 'package:screen_brightness/screen_brightness.dart';
import '../../utils/global_data.dart';
import 'models/bottom_progress_behavior.dart'; import 'models/bottom_progress_behavior.dart';
import 'widgets/app_bar_ani.dart'; import 'widgets/app_bar_ani.dart';
import 'widgets/backward_seek.dart'; import 'widgets/backward_seek.dart';
@ -75,8 +73,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
late bool enableQuickDouble; late bool enableQuickDouble;
late bool enableBackgroundPlay; late bool enableBackgroundPlay;
late double screenWidth; late double screenWidth;
final FullScreenGestureMode fullScreenGestureMode =
GlobalData().fullScreenGestureMode;
// 用于记录上一次全屏切换手势触发时间,避免误触 // 用于记录上一次全屏切换手势触发时间,避免误触
DateTime? lastFullScreenToggleTime; DateTime? lastFullScreenToggleTime;
@ -120,11 +116,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
super.initState(); super.initState();
screenWidth = Get.size.width; screenWidth = Get.size.width;
animationController = AnimationController( animationController = AnimationController(
vsync: this, vsync: this, duration: const Duration(milliseconds: 300));
duration: GlobalData().enablePlayerControlAnimation
? const Duration(milliseconds: 150)
: const Duration(milliseconds: 10),
);
videoController = widget.controller.videoController!; videoController = widget.controller.videoController!;
widget.controller.headerControl = widget.headerControl; widget.controller.headerControl = widget.headerControl;
widget.controller.bottomControl = widget.bottomControl; widget.controller.bottomControl = widget.bottomControl;
@ -528,20 +520,18 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
// 全屏 // 全屏
final double dy = details.delta.dy; final double dy = details.delta.dy;
const double threshold = 7.0; // 滑动阈值 const double threshold = 7.0; // 滑动阈值
final bool flag =
fullScreenGestureMode != FullScreenGestureMode.values.last;
if (dy > _distance && dy > threshold) { if (dy > _distance && dy > threshold) {
if (_.isFullScreen.value ^ flag) { if (_.isFullScreen.value) {
lastFullScreenToggleTime = DateTime.now(); lastFullScreenToggleTime = DateTime.now();
// 下滑退出全屏 // 下滑退出全屏
await widget.controller.triggerFullScreen(status: flag); await widget.controller.triggerFullScreen(status: false);
} }
_distance = 0.0; _distance = 0.0;
} else if (dy < _distance && dy < -threshold) { } else if (dy < _distance && dy < -threshold) {
if (!_.isFullScreen.value ^ flag) { if (!_.isFullScreen.value) {
lastFullScreenToggleTime = DateTime.now(); lastFullScreenToggleTime = DateTime.now();
// 上滑进入全屏 // 上滑进入全屏
await widget.controller.triggerFullScreen(status: !flag); await widget.controller.triggerFullScreen();
} }
_distance = 0.0; _distance = 0.0;
} }

View File

@ -19,11 +19,11 @@ class AppBarAni extends StatelessWidget implements PreferredSizeWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
visible ? controller.forward() : controller.reverse(); visible ? controller.reverse() : controller.forward();
return SlideTransition( return SlideTransition(
position: Tween<Offset>( position: Tween<Offset>(
begin: Offset(0, position! == 'top' ? -1 : 1), begin: Offset.zero,
end: Offset.zero, end: Offset(0, position! == 'top' ? -1 : 1),
).animate(CurvedAnimation( ).animate(CurvedAnimation(
parent: controller, parent: controller,
curve: Curves.linear, curve: Curves.linear,

View File

@ -39,7 +39,6 @@ import '../pages/setting/pages/color_select.dart';
import '../pages/setting/pages/display_mode.dart'; import '../pages/setting/pages/display_mode.dart';
import '../pages/setting/pages/font_size_select.dart'; import '../pages/setting/pages/font_size_select.dart';
import '../pages/setting/pages/home_tabbar_set.dart'; import '../pages/setting/pages/home_tabbar_set.dart';
import '../pages/setting/pages/play_gesture_set.dart';
import '../pages/setting/pages/play_speed_set.dart'; import '../pages/setting/pages/play_speed_set.dart';
import '../pages/setting/recommend_setting.dart'; import '../pages/setting/recommend_setting.dart';
import '../pages/setting/play_setting.dart'; import '../pages/setting/play_setting.dart';
@ -167,9 +166,6 @@ class Routes {
CustomGetPage(name: '/subscription', page: () => const SubPage()), CustomGetPage(name: '/subscription', page: () => const SubPage()),
// 订阅详情 // 订阅详情
CustomGetPage(name: '/subDetail', page: () => const SubDetailPage()), CustomGetPage(name: '/subDetail', page: () => const SubDetailPage()),
// 播放器手势
CustomGetPage(
name: '/playerGestureSet', page: () => const PlayGesturePage()),
]; ];
} }

View File

@ -149,8 +149,6 @@ class VideoPlayerServiceHandler extends BaseAudioHandler with SeekHandler {
)); ));
if (_item.isNotEmpty) { if (_item.isNotEmpty) {
_item.removeLast(); _item.removeLast();
}
if (_item.isNotEmpty) {
setMediaItem(_item.last); setMediaItem(_item.last);
} }
if (_item.isEmpty) { if (_item.isEmpty) {

View File

@ -5,7 +5,6 @@ import 'package:get/get.dart';
import '../http/search.dart'; import '../http/search.dart';
import '../models/common/search_type.dart'; import '../models/common/search_type.dart';
import 'id_utils.dart'; import 'id_utils.dart';
import 'url_utils.dart';
import 'utils.dart'; import 'utils.dart';
class PiliSchame { class PiliSchame {
@ -39,16 +38,23 @@ class PiliSchame {
final String path = value.path; final String path = value.path;
if (scheme == 'bilibili') { if (scheme == 'bilibili') {
// bilibili://root
if (host == 'root') { if (host == 'root') {
Navigator.popUntil( Navigator.popUntil(
Get.context!, (Route<dynamic> route) => route.isFirst); Get.context!, (Route<dynamic> route) => route.isFirst);
} else if (host == 'space') { }
// bilibili://space/{uid}
else if (host == 'space') {
final String mid = path.split('/').last; final String mid = path.split('/').last;
Get.toNamed<dynamic>( Get.toNamed<dynamic>(
'/member?mid=$mid', '/member?mid=$mid',
arguments: <String, dynamic>{'face': null}, arguments: <String, dynamic>{'face': null},
); );
} else if (host == 'video') { }
// bilibili://video/{aid}
else if (host == 'video') {
String pathQuery = path.split('/').last; String pathQuery = path.split('/').last;
final numericRegex = RegExp(r'^[0-9]+$'); final numericRegex = RegExp(r'^[0-9]+$');
if (numericRegex.hasMatch(pathQuery)) { if (numericRegex.hasMatch(pathQuery)) {
@ -62,16 +68,24 @@ class PiliSchame {
} else { } else {
SmartDialog.showToast('投稿匹配失败'); SmartDialog.showToast('投稿匹配失败');
} }
} else if (host == 'live') { }
// bilibili://live/{roomid}
else if (host == 'live') {
final String roomId = path.split('/').last; final String roomId = path.split('/').last;
Get.toNamed<dynamic>('/liveRoom?roomid=$roomId', Get.toNamed<dynamic>('/liveRoom?roomid=$roomId',
arguments: <String, String?>{'liveItem': null, 'heroTag': roomId}); arguments: <String, String?>{'liveItem': null, 'heroTag': roomId});
} else if (host == 'bangumi') { }
// bilibili://bangumi/season/${ssid}
else if (host == 'bangumi') {
if (path.startsWith('/season')) { if (path.startsWith('/season')) {
final String seasonId = path.split('/').last; final String seasonId = path.split('/').last;
_bangumiPush(int.parse(seasonId), null); _bangumiPush(int.parse(seasonId));
} }
} else if (host == 'opus') { }
// 专栏 bilibili://opus/detail/883089655985078289
else if (host == 'opus') {
if (path.startsWith('/detail')) { if (path.startsWith('/detail')) {
var opusId = path.split('/').last; var opusId = path.split('/').last;
Get.toNamed( Get.toNamed(
@ -87,9 +101,6 @@ class PiliSchame {
Get.toNamed('/searchResult', parameters: {'keyword': ''}); Get.toNamed('/searchResult', parameters: {'keyword': ''});
} }
} }
if (scheme == 'https') {
_fullPathPush(value);
}
} }
// 投稿跳转 // 投稿跳转
@ -120,10 +131,10 @@ class PiliSchame {
} }
// 番剧跳转 // 番剧跳转
static Future<void> _bangumiPush(int? seasonId, int? epId) async { static Future<void> _bangumiPush(int seasonId) async {
SmartDialog.showLoading<dynamic>(msg: '获取中...'); SmartDialog.showLoading<dynamic>(msg: '获取中...');
try { try {
var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId); var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: null);
if (result['status']) { if (result['status']) {
var bangumiDetail = result['data']; var bangumiDetail = result['data'];
final int cid = bangumiDetail.episodes!.first.cid; final int cid = bangumiDetail.episodes!.first.cid;
@ -140,8 +151,6 @@ class PiliSchame {
}, },
), ),
); );
} else {
SmartDialog.showToast(result['msg']);
} }
} catch (e) { } catch (e) {
SmartDialog.showToast('番剧获取失败:$e'); SmartDialog.showToast('番剧获取失败:$e');
@ -154,67 +163,29 @@ class PiliSchame {
// final String scheme = value.scheme!; // final String scheme = value.scheme!;
final String host = value.host!; final String host = value.host!;
final String? path = value.path; final String? path = value.path;
Map<String, String>? query = value.query; // Map<String, String> query = value.query!;
RegExp regExp = RegExp(r'^(www\.)?m?\.(bilibili\.com)$'); if (host.startsWith('live.bilibili')) {
if (regExp.hasMatch(host)) {
print('bilibili.com');
} else if (host.contains('live')) {
int roomId = int.parse(path!.split('/').last); int roomId = int.parse(path!.split('/').last);
Get.toNamed( // print('直播');
'/liveRoom?roomid=$roomId', Get.toNamed('/liveRoom?roomid=$roomId',
arguments: {'liveItem': null, 'heroTag': roomId.toString()}, arguments: {'liveItem': null, 'heroTag': roomId.toString()});
); return;
} else if (host.contains('space')) { }
var mid = path!.split('/').last; if (host.startsWith('space.bilibili')) {
Get.toNamed('/member?mid=$mid', arguments: {'face': ''}); print('个人空间');
return; return;
} else if (host == 'b23.tv') {
final String fullPath = 'https://$host$path';
final String redirectUrl = await UrlUtils.parseRedirectUrl(fullPath);
final String pathSegment = Uri.parse(redirectUrl).path;
final String lastPathSegment = pathSegment.split('/').last;
final RegExp avRegex = RegExp(r'^[aA][vV]\d+', caseSensitive: false);
if (avRegex.hasMatch(lastPathSegment)) {
final Map<String, dynamic> map =
IdUtils.matchAvorBv(input: lastPathSegment);
if (map.containsKey('AV')) {
_videoPush(map['AV']! as int, null);
} else if (map.containsKey('BV')) {
_videoPush(null, map['BV'] as String);
} else {
SmartDialog.showToast('投稿匹配失败');
}
} else if (lastPathSegment.startsWith('ep')) {
_handleEpisodePath(lastPathSegment, redirectUrl);
} else if (lastPathSegment.startsWith('ss')) {
_handleSeasonPath(lastPathSegment, redirectUrl);
} else if (lastPathSegment.startsWith('BV')) {
UrlUtils.matchUrlPush(
lastPathSegment,
'',
redirectUrl,
);
} else {
Get.toNamed(
'/webview',
parameters: {'url': redirectUrl, 'type': 'url', 'pageTitle': ''},
);
}
} }
if (path != null) { if (path != null) {
final String area = path.split('/').last; final String area = path.split('/')[1];
switch (area) { switch (area) {
case 'bangumi': case 'bangumi':
print('番剧'); // print('番剧');
if (area.startsWith('ep')) { final String seasonId = path.split('/').last;
_bangumiPush(null, matchNum(area).first); _bangumiPush(matchNum(seasonId).first);
} else if (area.startsWith('ss')) {
_bangumiPush(matchNum(area).first, null);
}
break; break;
case 'video': case 'video':
print('投稿'); // print('投稿');
final Map<String, dynamic> map = IdUtils.matchAvorBv(input: path); final Map<String, dynamic> map = IdUtils.matchAvorBv(input: path);
if (map.containsKey('AV')) { if (map.containsKey('AV')) {
_videoPush(map['AV']! as int, null); _videoPush(map['AV']! as int, null);
@ -229,7 +200,6 @@ class PiliSchame {
break; break;
case 'space': case 'space':
print('个人空间'); print('个人空间');
Get.toNamed('/member?mid=$area', arguments: {'face': ''});
break; break;
} }
} }
@ -241,18 +211,4 @@ class PiliSchame {
return matches.map((Match match) => int.parse(match.group(0)!)).toList(); return matches.map((Match match) => int.parse(match.group(0)!)).toList();
} }
static void _handleEpisodePath(String lastPathSegment, String redirectUrl) {
final String seasonId = _extractIdFromPath(lastPathSegment);
_bangumiPush(null, matchNum(seasonId).first);
}
static void _handleSeasonPath(String lastPathSegment, String redirectUrl) {
final String seasonId = _extractIdFromPath(lastPathSegment);
_bangumiPush(matchNum(seasonId).first, null);
}
static String _extractIdFromPath(String lastPathSegment) {
return lastPathSegment.split('/').last;
}
} }

View File

@ -1,10 +1,5 @@
import '../models/common/index.dart';
class GlobalData { class GlobalData {
int imgQuality = 10; int imgQuality = 10;
FullScreenGestureMode fullScreenGestureMode =
FullScreenGestureMode.values.last;
bool enablePlayerControlAnimation = true;
// 私有构造函数 // 私有构造函数
GlobalData._(); GlobalData._();

View File

@ -68,9 +68,8 @@ class IdUtils {
if (input == null || input.isEmpty) { if (input == null || input.isEmpty) {
return result; return result;
} }
final RegExp bvRegex = final RegExp bvRegex = RegExp(r'BV[0-9A-Za-z]{10}', caseSensitive: false);
RegExp(r'[bB][vV][0-9A-Za-z]{10}', caseSensitive: false); final RegExp avRegex = RegExp(r'AV\d+', caseSensitive: false);
final RegExp avRegex = RegExp(r'[aA][vV]\d+', caseSensitive: false);
final Iterable<Match> bvMatches = bvRegex.allMatches(input); final Iterable<Match> bvMatches = bvRegex.allMatches(input);
final Iterable<Match> avMatches = avRegex.allMatches(input); final Iterable<Match> avMatches = avRegex.allMatches(input);

View File

@ -4,7 +4,6 @@ import 'package:path_provider/path_provider.dart';
import 'package:pilipala/models/model_owner.dart'; import 'package:pilipala/models/model_owner.dart';
import 'package:pilipala/models/search/hot.dart'; import 'package:pilipala/models/search/hot.dart';
import 'package:pilipala/models/user/info.dart'; import 'package:pilipala/models/user/info.dart';
import '../models/common/gesture_mode.dart';
import 'global_data.dart'; import 'global_data.dart';
class GStrorage { class GStrorage {
@ -46,11 +45,6 @@ class GStrorage {
video = await Hive.openBox('video'); video = await Hive.openBox('video');
GlobalData().imgQuality = GlobalData().imgQuality =
setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10); // 设置全局变量 setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10); // 设置全局变量
GlobalData().fullScreenGestureMode = FullScreenGestureMode.values[
setting.get(SettingBoxKey.fullScreenGestureMode,
defaultValue: FullScreenGestureMode.values.last.index) as int];
GlobalData().enablePlayerControlAnimation = setting
.get(SettingBoxKey.enablePlayerControlAnimation, defaultValue: true);
} }
static void regAdapter() { static void regAdapter() {
@ -100,13 +94,11 @@ class SettingBoxKey {
enableCDN = 'enableCDN', enableCDN = 'enableCDN',
autoPiP = 'autoPiP', autoPiP = 'autoPiP',
enableAutoLongPressSpeed = 'enableAutoLongPressSpeed', enableAutoLongPressSpeed = 'enableAutoLongPressSpeed',
enablePlayerControlAnimation = 'enablePlayerControlAnimation',
// youtube 双击快进快退 // youtube 双击快进快退
enableQuickDouble = 'enableQuickDouble', enableQuickDouble = 'enableQuickDouble',
enableShowDanmaku = 'enableShowDanmaku', enableShowDanmaku = 'enableShowDanmaku',
enableBackgroundPlay = 'enableBackgroundPlay', enableBackgroundPlay = 'enableBackgroundPlay',
fullScreenGestureMode = 'fullScreenGestureMode',
/// 隐私 /// 隐私
blackMidsList = 'blackMidsList', blackMidsList = 'blackMidsList',
@ -130,8 +122,7 @@ class SettingBoxKey {
enableWordRe = 'enableWordRe', enableWordRe = 'enableWordRe',
enableSearchWord = 'enableSearchWord', enableSearchWord = 'enableSearchWord',
enableSystemProxy = 'enableSystemProxy', enableSystemProxy = 'enableSystemProxy',
enableAi = 'enableAi', enableAi = 'enableAi';
defaultHomePage = 'defaultHomePage';
/// 外观 /// 外观
static const String themeMode = 'themeMode', static const String themeMode = 'themeMode',

View File

@ -502,7 +502,7 @@ packages:
description: description:
path: "." path: "."
ref: main ref: main
resolved-ref: "8e89669eb9341f9980265306e24ef96fdbd3fd08" resolved-ref: d2d8421c4d80f6113f832404109853684721e11a
url: "https://github.com/guozhigq/floating.git" url: "https://github.com/guozhigq/floating.git"
source: git source: git
version: "2.0.1" version: "2.0.1"

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 # 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.20+1020 version: 1.0.19+1019
environment: environment:
sdk: ">=2.19.6 <3.0.0" sdk: ">=2.19.6 <3.0.0"