Merge remote-tracking branch 'upstream/main'

This commit is contained in:
orz12
2024-02-01 21:04:31 +08:00
25 changed files with 698 additions and 184 deletions

View File

@@ -1,84 +1,157 @@
name: build_apk name: Pilipala Release
# action事件触发 # action事件触发
on: on:
push: push:
# push tag时触发 # push tag时触发
tags: tags:
- 'v*.*.*' - "v*.*.*"
# 可以有多个jobs # 可以有多个jobs
jobs: jobs:
build_apk: android:
# 运行环境 ubuntu-latest window-latest mac-latest # 运行环境 ubuntu-latest window-latest mac-latest
runs-on: ubuntu-latest runs-on: ubuntu-latest
# 每个jobs中可以有多个steps # 每个jobs中可以有多个steps
steps: steps:
- name: 代码迁出 - name: 代码迁出
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: 构建Java环境 - name: 构建Java环境
uses: actions/setup-java@v3 uses: actions/setup-java@v3
with: with:
distribution: "zulu" distribution: "zulu"
java-version: "17" java-version: "17"
token: ${{secrets.GIT_TOKEN}} token: ${{secrets.GIT_TOKEN}}
- name: 检查缓存 - name: 检查缓存
uses: actions/cache@v2 uses: actions/cache@v2
id: cache-flutter id: cache-flutter
with: with:
path: /root/flutter-sdk # Flutter SDK 的路径 path: /root/flutter-sdk # Flutter SDK 的路径
key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }} key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }}
- name: 安装Flutter - name: 安装Flutter
if: steps.cache-flutter.outputs.cache-hit != 'true' if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: 3.16.5 flutter-version: 3.16.5
channel: any channel: any
- name: 下载项目依赖 - name: 下载项目依赖
run: flutter pub get run: flutter pub get
- name: 解码生成 jks - name: 解码生成 jks
run: echo $KEYSTORE_BASE64 | base64 -di > android/app/vvex.jks run: echo $KEYSTORE_BASE64 | base64 -di > android/app/vvex.jks
env: env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
- name: flutter build apk - name: flutter build apk
# 对应 android/app/build.gradle signingConfigs中的配置项 run: flutter build apk --release --split-per-abi
run: flutter build apk --release --split-per-abi env:
env: KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }} KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
- name: 获取版本号 - name: flutter build apk
id: version run: flutter build apk --release
run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
# - name: 获取当前日期 - name: 获取版本号
# id: date id: version
# run: echo "date=$(date +'%m%d')" >>$GITHUB_OUTPUT run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
- name: 重命名应用 Pili-arm64-v8a-*.*.*.0101.apk # - name: 获取当前日期
run: | # id: date
# DATE=${{ steps.date.outputs.date }} # run: echo "date=$(date +'%m%d')" >>$GITHUB_OUTPUT
for file in build/app/outputs/flutter-apk/app-*-release.apk; do
if [[ $file =~ app-(.*)-release.apk ]]; then
new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}-${{ steps.version.outputs.version }}.apk"
mv "$file" "$new_file_name"
fi
done
- name: 构建和发布release - name: 重命名应用
uses: ncipollo/release-action@v1 run: |
with: # DATE=${{ steps.date.outputs.date }}
# release title for file in build/app/outputs/flutter-apk/app-*.apk; do
name: v${{ steps.version.outputs.version }} if [[ $file =~ app-(.?*)release.apk ]]; then
artifacts: "build/app/outputs/flutter-apk/Pili-*.apk" new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}${{ steps.version.outputs.version }}.apk"
bodyFile: "change_log/${{steps.version.outputs.version}}.md" mv "$file" "$new_file_name"
token: ${{ secrets.GIT_TOKEN }} fi
allowUpdates: true done
- name: 上传
uses: actions/upload-artifact@v3
with:
name: Pilipala-Release
path: |
build/app/outputs/flutter-apk/Pili-*.apk
iOS:
runs-on: macos-latest
steps:
- name: 代码迁出
uses: actions/checkout@v4
- 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: flutter build ipa
run: |
flutter build ios --release --no-codesign
ln -sf ./build/ios/iphoneos Payload
zip -r9 app.ipa Payload/runner.app
- name: 获取版本号
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
- name: 重命名应用
run: |
DATE=${{ steps.date.outputs.date }}
for file in app.ipa; do
new_file_name="build/Pili-${{ steps.version.outputs.version }}.ipa"
mv "$file" "$new_file_name"
done
- name: 上传
uses: actions/upload-artifact@v3
with:
if-no-files-found: error
name: Pilipala-Release
path: |
build/Pili-*.ipa
upload:
runs-on: ubuntu-latest
needs:
- android
- iOS
steps:
- uses: actions/download-artifact@v3
with:
name: Pilipala-Release
path: ./Pilipala-Release
- name: Install dependencies
run: sudo apt-get install tree -y
- name: Get version
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
- name: Upload Release
uses: ncipollo/release-action@v1
with:
name: v${{ steps.version.outputs.version }}
token: ${{ secrets.GIT_TOKEN }}
omitBodyDuringUpdate: true
omitNameDuringUpdate: true
omitPrereleaseDuringUpdate: true
allowUpdates: true
artifacts: Pilipala-Release/*

View File

@@ -58,7 +58,6 @@ android {
applicationId "com.guozhigq.pilipala" applicationId "com.guozhigq.pilipala"
// You can update the following values to match your application needs. // You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
// minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName

16
change_log/1.0.18.0130.md Normal file
View File

@@ -0,0 +1,16 @@
## 1.0.18
### 功能
### 修复
### 优化
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

15
change_log/1.0.19.0131.md Normal file
View File

@@ -0,0 +1,15 @@
## 1.0.19
### 修复
+ 视频404、评论加载错误
+ bvav转换
### 优化
+ 视频详情页内存占用
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@@ -13,8 +13,13 @@ PODS:
- device_info_plus (0.0.1): - device_info_plus (0.0.1):
- Flutter - Flutter
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_mailer (0.0.1):
- Flutter
- flutter_volume_controller (0.0.1): - flutter_volume_controller (0.0.1):
- Flutter - Flutter
- fluttertoast (0.0.2):
- Flutter
- Toast
- FMDB (2.7.5): - FMDB (2.7.5):
- FMDB/standard (= 2.7.5) - FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5) - FMDB/standard (2.7.5)
@@ -49,6 +54,7 @@ PODS:
- Flutter - Flutter
- system_proxy (0.0.1): - system_proxy (0.0.1):
- Flutter - Flutter
- Toast (4.1.0)
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
- volume_controller (0.0.1): - volume_controller (0.0.1):
@@ -68,7 +74,9 @@ DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`) - flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`) - gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`)
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`) - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
@@ -93,6 +101,7 @@ SPEC REPOS:
- FMDB - FMDB
- GT3Captcha-iOS - GT3Captcha-iOS
- ReachabilitySwift - ReachabilitySwift
- Toast
EXTERNAL SOURCES: EXTERNAL SOURCES:
appscheme: appscheme:
@@ -109,8 +118,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_info_plus/ios" :path: ".symlinks/plugins/device_info_plus/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_mailer:
:path: ".symlinks/plugins/flutter_mailer/ios"
flutter_volume_controller: flutter_volume_controller:
:path: ".symlinks/plugins/flutter_volume_controller/ios" :path: ".symlinks/plugins/flutter_volume_controller/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
gt3_flutter_plugin: gt3_flutter_plugin:
:path: ".symlinks/plugins/gt3_flutter_plugin/ios" :path: ".symlinks/plugins/gt3_flutter_plugin/ios"
media_kit_libs_ios_video: media_kit_libs_ios_video:
@@ -156,7 +169,9 @@ SPEC CHECKSUMS:
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529 flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23 gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6 GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
@@ -173,6 +188,7 @@ SPEC CHECKSUMS:
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446 status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44 system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9 volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47 wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47

View File

@@ -181,8 +181,14 @@ class Request {
); );
return response; return response;
} on DioException catch (e) { } on DioException catch (e) {
print('get error: $e'); Response errResponse = Response(
return Future.error(await ApiInterceptor.dioError(e)); data: {
'message': await ApiInterceptor.dioError(e)
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
statusCode: 200,
requestOptions: RequestOptions(),
);
return errResponse;
} }
} }
@@ -203,8 +209,14 @@ class Request {
// print('post success: ${response.data}'); // print('post success: ${response.data}');
return response; return response;
} on DioException catch (e) { } on DioException catch (e) {
print('post error: $e'); Response errResponse = Response(
return Future.error(await ApiInterceptor.dioError(e)); data: {
'message': await ApiInterceptor.dioError(e)
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
statusCode: 200,
requestOptions: RequestOptions(),
);
return errResponse;
} }
} }

View File

@@ -5,7 +5,6 @@ import 'package:dio/dio.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import '../utils/storage.dart'; import '../utils/storage.dart';
// import 'package:get/get.dart' hide Response;
class ApiInterceptor extends Interceptor { class ApiInterceptor extends Interceptor {
@override @override
@@ -71,35 +70,28 @@ class ApiInterceptor extends Interceptor {
return '发送请求超时,请检查网络设置'; return '发送请求超时,请检查网络设置';
case DioExceptionType.unknown: case DioExceptionType.unknown:
final String res = await checkConnect(); final String res = await checkConnect();
return '$res \n 网络异常,请稍后重试'; return '$res,网络异常';
// default:
// return 'Dio异常';
} }
} }
static Future<String> checkConnect() async { static Future<String> checkConnect() async {
final ConnectivityResult connectivityResult = final ConnectivityResult connectivityResult =
await Connectivity().checkConnectivity(); await Connectivity().checkConnectivity();
if (connectivityResult == ConnectivityResult.mobile) { switch (connectivityResult) {
return 'connected with mobile network'; case ConnectivityResult.mobile:
} else if (connectivityResult == ConnectivityResult.wifi) { return '正在使用移动流量';
return 'connected with wifi network'; case ConnectivityResult.wifi:
} else if (connectivityResult == ConnectivityResult.ethernet) { return '正在使用wifi';
// I am connected to a ethernet network. case ConnectivityResult.ethernet:
return ''; return '正在使用局域网';
} else if (connectivityResult == ConnectivityResult.vpn) { case ConnectivityResult.vpn:
// I am connected to a vpn network. return '正在使用代理网络';
// Note for iOS and macOS: case ConnectivityResult.other:
// There is no separate network interface type for [vpn]. return '正在使用其他网络';
// It returns [other] on any device (also simulator) case ConnectivityResult.none:
return ''; return '未连接到任何网络';
} else if (connectivityResult == ConnectivityResult.other) { default:
// I am connected to a network which is not in the above mentioned networks. return '';
return '';
} else if (connectivityResult == ConnectivityResult.none) {
return 'not connected to any network';
} else {
return '';
} }
} }
} }

View File

@@ -22,6 +22,8 @@ import 'package:pilipala/utils/data.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc. import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc.
import 'package:pilipala/utils/recommend_filter.dart'; import 'package:pilipala/utils/recommend_filter.dart';
import 'package:catcher_2/catcher_2.dart';
import './services/loggeer.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@@ -40,7 +42,32 @@ void main() async {
Request(); Request();
await Request.setCookie(); await Request.setCookie();
RecommendFilter(); RecommendFilter();
runApp(const MyApp());
// 异常捕获 logo记录
final Catcher2Options debugConfig = Catcher2Options(
SilentReportMode(),
[
FileHandler(await getLogsPath()),
ConsoleHandler(
enableDeviceParameters: false,
enableApplicationParameters: false,
)
],
);
final Catcher2Options releaseConfig = Catcher2Options(
SilentReportMode(),
[FileHandler(await getLogsPath())],
);
Catcher2(
debugConfig: debugConfig,
releaseConfig: releaseConfig,
runAppFunction: () {
runApp(const MyApp());
},
);
// 小白条、导航栏沉浸 // 小白条、导航栏沉浸
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(

View File

@@ -133,6 +133,11 @@ class _AboutPageState extends State<AboutPage> {
title: const Text('赞助'), title: const Text('赞助'),
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline), trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
), ),
ListTile(
onTap: () => _aboutController.logs(),
title: const Text('错误日志'),
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
),
], ],
), ),
), ),
@@ -260,4 +265,9 @@ class AboutController extends GetxController {
mode: LaunchMode.externalApplication, mode: LaunchMode.externalApplication,
); );
} }
// 日志
logs() {
Get.toNamed('/logs');
}
} }

View File

@@ -1,5 +1,6 @@
// 内容 // 内容
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/dynamics/result.dart'; import 'package:pilipala/models/dynamics/result.dart';
@@ -80,7 +81,7 @@ class _ContentState extends State<Content> {
height: height, height: height,
), ),
), ),
height > maxHeight height > Get.size.height * 0.9
? const PBadge( ? const PBadge(
text: '长图', text: '长图',
right: 8, right: 8,

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
@@ -87,7 +88,7 @@ Widget picWidget(item, context) {
childAspectRatio: aspectRatio, childAspectRatio: aspectRatio,
children: list, children: list,
), ),
if (len == 1 && origAspectRatio < 0.4) if (len == 1 && height > Get.size.height * 0.9)
const PBadge( const PBadge(
text: '长图', text: '长图',
top: null, top: null,

View File

@@ -63,13 +63,16 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
} }
void setTabConfig() async { void setTabConfig() async {
defaultTabs = tabsConfig; defaultTabs = [...tabsConfig];
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort, tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']); defaultValue: ['live', 'rcmd', 'hot', 'bangumi']);
defaultTabs.retainWhere(
(item) => tabbarSort.contains((item['type'] as TabType).id));
defaultTabs.sort((a, b) => tabbarSort
.indexOf((a['type'] as TabType).id)
.compareTo(tabbarSort.indexOf((b['type'] as TabType).id)));
tabs.value = defaultTabs tabs.value = defaultTabs;
.where((i) => tabbarSort.contains((i['type'] as TabType).id))
.toList();
if (tabbarSort.contains(TabType.rcmd.id)) { if (tabbarSort.contains(TabType.rcmd.id)) {
initialIndex.value = tabbarSort.indexOf(TabType.rcmd.id); initialIndex.value = tabbarSort.indexOf(TabType.rcmd.id);

View File

@@ -25,16 +25,17 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
padding: const EdgeInsets.fromLTRB( padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 5, StyleString.safeSpace, 5), StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder(builder: (context, boxConstraints) { child: LayoutBuilder(builder: (context, boxConstraints) {
double width = (boxConstraints.maxWidth - final double width = (boxConstraints.maxWidth -
StyleString.cardSpace * StyleString.cardSpace *
6 / 6 /
MediaQuery.textScalerOf(context).scale(2.0)); MediaQuery.textScalerOf(context).scale(1.0)) /
2;
return Container( return Container(
constraints: const BoxConstraints(minHeight: 88), constraints: const BoxConstraints(minHeight: 88),
height: width / StyleString.aspectRatio, height: width / StyleString.aspectRatio,
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: <Widget>[
if (list[index].imageUrls != null && if (list[index].imageUrls != null &&
list[index].imageUrls.isNotEmpty) list[index].imageUrls.isNotEmpty)
AspectRatio( AspectRatio(

View File

@@ -22,6 +22,17 @@ class _TabbarSetPageState extends State<TabbarSetPage> {
defaultTabs = tabsConfig; defaultTabs = tabsConfig;
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort, tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']); defaultValue: ['live', 'rcmd', 'hot', 'bangumi']);
// 对 tabData 进行排序
defaultTabs.sort((a, b) {
int indexA = tabbarSort.indexOf((a['type'] as TabType).id);
int indexB = tabbarSort.indexOf((b['type'] as TabType).id);
// 如果类型在 sortOrder 中不存在,则放在末尾
if (indexA == -1) indexA = tabbarSort.length;
if (indexB == -1) indexB = tabbarSort.length;
return indexA.compareTo(indexB);
});
} }
void saveEdit() { void saveEdit() {

View File

@@ -0,0 +1,201 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:pilipala/common/widgets/no_data.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../services/loggeer.dart';
class LogsPage extends StatefulWidget {
const LogsPage({super.key});
@override
State<LogsPage> createState() => _LogsPageState();
}
class _LogsPageState extends State<LogsPage> {
late File logsPath;
late String fileContent;
List logsContent = [];
@override
void initState() {
getPath();
super.initState();
}
void getPath() async {
logsPath = await getLogsPath();
fileContent = await logsPath.readAsString();
logsContent = await parseLogs(fileContent);
setState(() {});
}
Future<List<Map<String, dynamic>>> parseLogs(String fileContent) async {
const String splitToken =
'======================================================================';
List contentList = fileContent.split(splitToken).map((item) {
return item
.replaceAll(
'============================== CATCHER 2 LOG ==============================',
'Pilipala错误日志 \n ********************')
.replaceAll('DEVICE INFO', '设备信息')
.replaceAll('APP INFO', '应用信息')
.replaceAll('ERROR', '错误信息')
.replaceAll('STACK TRACE', '错误堆栈');
}).toList();
List<Map<String, dynamic>> result = [];
for (String i in contentList) {
DateTime? date;
String body = i
.split("\n")
.map((l) {
if (l.startsWith("Crash occurred on")) {
date = DateTime.parse(
l.split("Crash occurred on")[1].trim().split('.')[0],
);
return "";
}
return l;
})
.where((dynamic l) => l.replaceAll("\n", "").trim().isNotEmpty)
.join("\n");
if (date != null || body != '') {
result.add({'date': date, 'body': body, 'expand': false});
}
}
return result.reversed.toList();
}
void copyLogs() async {
await Clipboard.setData(ClipboardData(text: fileContent));
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('复制成功')),
);
}
}
void feedback() {
launchUrl(
Uri.parse('https://github.com/guozhigq/pilipala/issues'),
// 系统自带浏览器打开
mode: LaunchMode.externalApplication,
);
}
void clearLogsHandle() async {
if (await clearLogs()) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('已清空')),
);
logsContent = [];
setState(() {});
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
titleSpacing: 0,
title: Text('日志', style: Theme.of(context).textTheme.titleMedium),
actions: [
PopupMenuButton<String>(
onSelected: (String type) {
// 处理菜单项选择的逻辑
switch (type) {
case 'copy':
copyLogs();
break;
case 'feedback':
feedback();
break;
case 'clear':
clearLogsHandle();
break;
default:
}
},
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
const PopupMenuItem<String>(
value: 'copy',
child: Text('复制日志'),
),
const PopupMenuItem<String>(
value: 'feedback',
child: Text('错误反馈'),
),
const PopupMenuItem<String>(
value: 'clear',
child: Text('清空日志'),
),
],
),
const SizedBox(width: 6),
],
),
body: logsContent.isNotEmpty
? ListView.builder(
itemCount: logsContent.length,
itemBuilder: (context, index) {
final log = logsContent[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
log['date'].toString(),
style: Theme.of(context).textTheme.titleMedium,
),
),
TextButton.icon(
onPressed: () async {
await Clipboard.setData(
ClipboardData(text: log['body']),
);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'已将 ${log['date'].toString()} 复制至剪贴板',
),
),
);
}
},
icon: const Icon(Icons.copy_outlined, size: 16),
label: const Text('复制'),
)
],
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 1,
clipBehavior: Clip.antiAliasWithSaveLayer,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: SelectableText(log['body']),
),
),
),
const Divider(indent: 12, endIndent: 12),
],
);
},
)
: const CustomScrollView(
slivers: <Widget>[
NoData(),
],
),
);
}
}

View File

@@ -256,7 +256,12 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
// 请求错误 // 请求错误
return HttpError( return HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => setState(() {}), fn: () {
setState(() {
_futureBuilderFuture =
_videoReplyController.queryReplyList();
});
},
); );
} }
} else { } else {

View File

@@ -784,7 +784,7 @@ InlineSpan buildContent(
height: height, height: height,
), ),
), ),
height > maxHeight height > Get.size.height * 0.9
? const PBadge( ? const PBadge(
text: '长图', text: '长图',
right: 8, right: 8,

View File

@@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/msg.dart'; import 'package:pilipala/http/msg.dart';

View File

@@ -5,7 +5,6 @@ import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get/get_core/src/get_main.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
@@ -53,12 +52,13 @@ class ChatItem extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isOwner = bool isOwner =
item.senderUid == GStrorage.userInfo.get('userInfoCache').mid; item.senderUid == GStrorage.userInfo.get('userInfoCache').mid;
bool isPic = item.msgType == MsgType.pic; // 图片
bool isText = item.msgType == MsgType.text; // 文本 bool isPic = item.msgType == MsgType.pic.value; // 图片
bool isText = item.msgType == MsgType.text.value; // 文本
// bool isArchive = item.msgType == 11; // 投稿 // bool isArchive = item.msgType == 11; // 投稿
// bool isArticle = item.msgType == 12; // 专栏 // bool isArticle = item.msgType == 12; // 专栏
bool isRevoke = item.msgType == MsgType.revoke; // 撤回消息 bool isRevoke = item.msgType == MsgType.revoke.value; // 撤回消息
bool isShareV2 = item.msgType == MsgType.share_v2; bool isShareV2 = item.msgType == MsgType.share_v2.value;
bool isSystem = bool isSystem =
item.msgType == 18 || item.msgType == 10 || item.msgType == 13; item.msgType == 18 || item.msgType == 10 || item.msgType == 13;
dynamic content = item.content ?? ''; dynamic content = item.content ?? '';
@@ -72,7 +72,7 @@ class ChatItem extends StatelessWidget {
var text = content['content']; var text = content['content'];
if (e_infos != null) { if (e_infos != null) {
final List<InlineSpan> children = []; final List<InlineSpan> children = [];
Map<String,String> emojiMap = {}; Map<String, String> emojiMap = {};
for (var e in e_infos!) { for (var e in e_infos!) {
emojiMap[e['text']] = e['url']; emojiMap[e['text']] = e['url'];
} }
@@ -83,18 +83,22 @@ class ChatItem extends StatelessWidget {
if (emojiMap.containsKey(emojiKey)) { if (emojiMap.containsKey(emojiKey)) {
children.add(WidgetSpan( children.add(WidgetSpan(
child: NetworkImgLayer( child: NetworkImgLayer(
width: 18, height: 18, width: 18,
src: emojiMap[emojiKey]!,), height: 18,
src: emojiMap[emojiKey]!,
),
)); ));
} }
return ''; return '';
}, },
onNonMatch: (String text) { onNonMatch: (String text) {
children.add(TextSpan(text: text, style: TextStyle( children.add(TextSpan(
color: textColor(context), text: text,
letterSpacing: 0.6, style: TextStyle(
height: 1.5, color: textColor(context),
))); letterSpacing: 0.6,
height: 1.5,
)));
return ''; return '';
}, },
); );
@@ -123,11 +127,13 @@ class ChatItem extends StatelessWidget {
return SystemNotice2(item: item); return SystemNotice2(item: item);
case MsgType.notify_text: case MsgType.notify_text:
return Text( return Text(
jsonDecode(content['content']).map((m) => m['text'] as String).join("\n"), jsonDecode(content['content'])
.map((m) => m['text'] as String)
.join("\n"),
style: TextStyle( style: TextStyle(
letterSpacing: 0.6, letterSpacing: 0.6,
height: 5, height: 5,
color: Theme.of(context).colorScheme.outline.withOpacity(0.8) color: Theme.of(context).colorScheme.outline.withOpacity(0.8),
), ),
); );
case MsgType.text: case MsgType.text:
@@ -166,9 +172,11 @@ class ChatItem extends StatelessWidget {
Text( Text(
content['title'], content['title'],
style: TextStyle( style: TextStyle(
letterSpacing: 0.6, letterSpacing: 0.6,
height: 1.5, height: 1.5,
color: textColor(context), fontWeight: FontWeight.bold), color: textColor(context),
fontWeight: FontWeight.bold,
),
), ),
const SizedBox(height: 1), const SizedBox(height: 1),
Text( Text(
@@ -186,9 +194,11 @@ class ChatItem extends StatelessWidget {
return Text( return Text(
content['content'] ?? content.toString(), content['content'] ?? content.toString(),
style: TextStyle( style: TextStyle(
letterSpacing: 0.6, letterSpacing: 0.6,
height: 1.5, height: 1.5,
color: textColor(context), fontWeight: FontWeight.bold), color: textColor(context),
fontWeight: FontWeight.bold,
),
); );
} }
} }

View File

@@ -278,7 +278,8 @@ class PlPlayerController {
danmakuDurationVal = danmakuDurationVal =
localCache.get(LocalCacheKey.danmakuDuration, defaultValue: 4.0); localCache.get(LocalCacheKey.danmakuDuration, defaultValue: 4.0);
// 描边粗细 // 描边粗细
strokeWidth = localCache.get(LocalCacheKey.strokeWidth, defaultValue: 1.5); strokeWidth =
localCache.get(LocalCacheKey.strokeWidth, defaultValue: 1.5);
playRepeat = PlayRepeat.values.toList().firstWhere( playRepeat = PlayRepeat.values.toList().firstWhere(
(e) => (e) =>
e.value == e.value ==

View File

@@ -3,6 +3,7 @@
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:hive/hive.dart';
import 'package:pilipala/pages/setting/pages/logs.dart';
import '../pages/about/index.dart'; import '../pages/about/index.dart';
import '../pages/blacklist/index.dart'; import '../pages/blacklist/index.dart';
@@ -153,6 +154,8 @@ class Routes {
// 用户专栏 // 用户专栏
CustomGetPage( CustomGetPage(
name: '/memberSeasons', page: () => const MemberSeasonsPage()), name: '/memberSeasons', page: () => const MemberSeasonsPage()),
// 日志
CustomGetPage(name: '/logs', page: () => const LogsPage()),
]; ];
} }

56
lib/services/loggeer.dart Normal file
View File

@@ -0,0 +1,56 @@
// final _loggerFactory =
import 'dart:io';
import 'package:logger/logger.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
final _loggerFactory = PiliLogger();
PiliLogger getLogger<T>() {
return _loggerFactory;
}
class PiliLogger extends Logger {
PiliLogger() : super();
@override
void log(Level level, dynamic message,
{Object? error, StackTrace? stackTrace, DateTime? time}) async {
if (level == Level.error) {
String dir = (await getApplicationDocumentsDirectory()).path;
// 创建logo文件
final String filename = p.join(dir, ".pili_logs");
// 添加至文件末尾
await File(filename).writeAsString(
"**${DateTime.now()}** \n $message \n $stackTrace",
mode: FileMode.writeOnlyAppend,
);
}
super.log(level, "$message", error: error, stackTrace: stackTrace);
}
}
Future<File> getLogsPath() async {
String dir = (await getApplicationDocumentsDirectory()).path;
final String filename = p.join(dir, ".pili_logs");
final file = File(filename);
if (!await file.exists()) {
await file.create();
}
return file;
}
Future<bool> clearLogs() async {
String dir = (await getApplicationDocumentsDirectory()).path;
final String filename = p.join(dir, ".pili_logs");
final file = File(filename);
try {
await file.writeAsString('');
} catch (e) {
print('Error clearing file: $e');
return false;
}
return true;
}

View File

@@ -1,51 +1,65 @@
// ignore_for_file: constant_identifier_names // ignore_for_file: constant_identifier_names, non_constant_identifier_names
import 'dart:convert'; import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
class IdUtils { class IdUtils {
static const String TABLE = static final XOR_CODE = BigInt.parse('23442827791579');
'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF'; static final MASK_CODE = BigInt.parse('2251799813685247');
static const List<int> S = [11, 10, 3, 8, 4, 6]; // 位置编码表 static final MAX_AID = BigInt.one << (BigInt.from(51)).toInt();
static const int XOR = 177451812; // 固定异或值 static final BASE = BigInt.from(58);
static const int ADD = 8728348608; // 固定加法值
static const List<String> r = [ static const data =
'B', 'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf';
'V',
'1',
'',
'',
'4',
'',
'1',
'',
'7',
'',
''
];
/// av转bv /// av转bv
static String av2bv(int av) { static String av2bv(int aid) {
int x_ = (av ^ XOR) + ADD; List<String> bytes = [
List<String> newR = []; 'B',
newR.addAll(r); 'V',
for (int i = 0; i < S.length; i++) { '1',
newR[S[i]] = '0',
TABLE.characters.elementAt((x_ / pow(58, i).toInt() % 58).toInt()); '0',
'0',
'0',
'0',
'0',
'0',
'0',
'0'
];
int bvIndex = bytes.length - 1;
BigInt tmp = (MAX_AID | BigInt.from(aid)) ^ XOR_CODE;
while (tmp > BigInt.zero) {
bytes[bvIndex] = data[(tmp % BASE).toInt()];
tmp = tmp ~/ BASE;
bvIndex -= 1;
} }
return newR.join(); String tmpSwap = bytes[3];
bytes[3] = bytes[9];
bytes[9] = tmpSwap;
tmpSwap = bytes[4];
bytes[4] = bytes[7];
bytes[7] = tmpSwap;
return bytes.join();
} }
/// bv转bv /// bv转av
static int bv2av(String bv) { static int bv2av(String bvid) {
int r = 0; List<String> bvidArr = bvid.split('');
for (int i = 0; i < S.length; i++) { final tmpValue = bvidArr[3];
r += (TABLE.indexOf(bv.characters.elementAt(S[i])).toInt()) * bvidArr[3] = bvidArr[9];
pow(58, i).toInt(); bvidArr[9] = tmpValue;
}
return (r - ADD) ^ XOR; final tmpValue2 = bvidArr[4];
bvidArr[4] = bvidArr[7];
bvidArr[7] = tmpValue2;
bvidArr.removeRange(0, 3);
BigInt tmp = bvidArr.fold(BigInt.zero,
(pre, bvidChar) => pre * BASE + BigInt.from(data.indexOf(bvidChar)));
return ((tmp & MASK_CODE) ^ XOR_CODE).toInt();
} }
// 匹配 // 匹配

View File

@@ -208,7 +208,15 @@ packages:
sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316"
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.1.1" version: "1.1.0"
catcher_2:
dependency: "direct main"
description:
name: catcher_2
sha256: ca94d45ffb52bf4b16a425cdff6734ae8443d36d5f06c276f1c2a593120b11ed
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@@ -547,6 +555,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_mailer:
dependency: transitive
description:
name: flutter_mailer
sha256: "4fffaa35e911ff5ec2e5a4ebbca62c372e99a154eb3bb2c0bf79f09adf6ecf4c"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.2"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
@@ -589,6 +605,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
fluttertoast:
dependency: transitive
description:
name: fluttertoast
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1
url: "https://pub.flutter-io.cn"
source: hosted
version: "8.2.4"
font_awesome_flutter: font_awesome_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -781,6 +805,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "3.0.0" version: "3.0.0"
logger:
dependency: "direct main"
description:
name: logger
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.2+1"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@@ -789,6 +821,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
mailer:
dependency: transitive
description:
name: mailer
sha256: "57f6dd1496699999a7bfd0aa6be0645384f477f4823e16d4321c40a434346382"
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.0.1"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@@ -951,7 +991,7 @@ packages:
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
path: path:
dependency: transitive dependency: "direct main"
description: description:
name: path name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
@@ -1214,6 +1254,14 @@ packages:
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/" url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
source: hosted source: hosted
version: "0.3.8" version: "0.3.8"
sentry:
dependency: transitive
description:
name: sentry
sha256: "5686ed515bb620dc52b4ae99a6586fe720d443591183cf1f620ec5d1f0eec100"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.15.0"
share_plus: share_plus:
dependency: "direct main" dependency: "direct main"
description: 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 # 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.17+1017 version: 1.0.19+1019
environment: environment:
sdk: ">=2.19.6 <3.0.0" sdk: ">=2.19.6 <3.0.0"
@@ -85,8 +85,8 @@ dependencies:
encrypt: ^5.0.3 encrypt: ^5.0.3
# 视频播放器 # 视频播放器
media_kit: ^1.1.10 # Primary package. media_kit: ^1.1.10 # Primary package.
media_kit_video: ^1.2.4 # For video rendering. media_kit_video: ^1.2.4 # For video rendering.
media_kit_libs_video: ^1.0.4 media_kit_libs_video: ^1.0.4
# 媒体通知 # 媒体通知
@@ -134,7 +134,9 @@ dependencies:
uuid: ^3.0.7 uuid: ^3.0.7
scrollable_positioned_list: ^0.3.8 scrollable_positioned_list: ^0.3.8
nil: ^1.1.1 nil: ^1.1.1
catcher_2: ^1.1.0
logger: ^2.0.2+1
path: 1.8.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -205,6 +207,5 @@ flutter:
# fonts: # fonts:
# - asset: assets/fonts/HarmonyOS_Sans_SC_Regular.ttf # - asset: assets/fonts/HarmonyOS_Sans_SC_Regular.ttf
# For details regarding fonts from package dependencies, # For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages # see https://flutter.dev/custom-fonts/#from-packages