mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
97
.github/workflows/main.yml
vendored
97
.github/workflows/main.yml
vendored
@@ -1,15 +1,15 @@
|
||||
name: build_apk
|
||||
name: Pilipala Release
|
||||
|
||||
# action事件触发
|
||||
on:
|
||||
push:
|
||||
# push tag时触发
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
- "v*.*.*"
|
||||
|
||||
# 可以有多个jobs
|
||||
jobs:
|
||||
build_apk:
|
||||
android:
|
||||
# 运行环境 ubuntu-latest window-latest mac-latest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -48,13 +48,19 @@ jobs:
|
||||
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
|
||||
|
||||
- name: flutter build apk
|
||||
# 对应 android/app/build.gradle signingConfigs中的配置项
|
||||
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: flutter build apk
|
||||
run: flutter build apk --release
|
||||
env:
|
||||
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
|
||||
|
||||
- name: 获取版本号
|
||||
id: version
|
||||
run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
|
||||
@@ -63,22 +69,89 @@ jobs:
|
||||
# id: date
|
||||
# run: echo "date=$(date +'%m%d')" >>$GITHUB_OUTPUT
|
||||
|
||||
- name: 重命名应用 Pili-arm64-v8a-*.*.*.0101.apk
|
||||
- name: 重命名应用
|
||||
run: |
|
||||
# DATE=${{ steps.date.outputs.date }}
|
||||
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"
|
||||
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]}${{ steps.version.outputs.version }}.apk"
|
||||
mv "$file" "$new_file_name"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: 构建和发布release
|
||||
- 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:
|
||||
# release title
|
||||
name: v${{ steps.version.outputs.version }}
|
||||
artifacts: "build/app/outputs/flutter-apk/Pili-*.apk"
|
||||
bodyFile: "change_log/${{steps.version.outputs.version}}.md"
|
||||
token: ${{ secrets.GIT_TOKEN }}
|
||||
omitBodyDuringUpdate: true
|
||||
omitNameDuringUpdate: true
|
||||
omitPrereleaseDuringUpdate: true
|
||||
allowUpdates: true
|
||||
artifacts: Pilipala-Release/*
|
||||
|
||||
@@ -58,7 +58,6 @@ android {
|
||||
applicationId "com.guozhigq.pilipala"
|
||||
// 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.
|
||||
// minSdkVersion flutter.minSdkVersion
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
|
||||
16
change_log/1.0.18.0130.md
Normal file
16
change_log/1.0.18.0130.md
Normal file
@@ -0,0 +1,16 @@
|
||||
## 1.0.18
|
||||
|
||||
|
||||
### 功能
|
||||
|
||||
|
||||
### 修复
|
||||
|
||||
|
||||
### 优化
|
||||
|
||||
|
||||
|
||||
|
||||
更多更新日志可在Github上查看
|
||||
问题反馈、功能建议请查看「关于」页面。
|
||||
15
change_log/1.0.19.0131.md
Normal file
15
change_log/1.0.19.0131.md
Normal file
@@ -0,0 +1,15 @@
|
||||
## 1.0.19
|
||||
|
||||
|
||||
### 修复
|
||||
+ 视频404、评论加载错误
|
||||
+ bvav转换
|
||||
|
||||
### 优化
|
||||
+ 视频详情页内存占用
|
||||
|
||||
|
||||
|
||||
|
||||
更多更新日志可在Github上查看
|
||||
问题反馈、功能建议请查看「关于」页面。
|
||||
@@ -13,8 +13,13 @@ PODS:
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- Flutter (1.0.0)
|
||||
- flutter_mailer (0.0.1):
|
||||
- Flutter
|
||||
- flutter_volume_controller (0.0.1):
|
||||
- Flutter
|
||||
- fluttertoast (0.0.2):
|
||||
- Flutter
|
||||
- Toast
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
@@ -49,6 +54,7 @@ PODS:
|
||||
- Flutter
|
||||
- system_proxy (0.0.1):
|
||||
- Flutter
|
||||
- Toast (4.1.0)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- volume_controller (0.0.1):
|
||||
@@ -68,7 +74,9 @@ DEPENDENCIES:
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- Flutter (from `Flutter`)
|
||||
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/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`)
|
||||
- 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`)
|
||||
@@ -93,6 +101,7 @@ SPEC REPOS:
|
||||
- FMDB
|
||||
- GT3Captcha-iOS
|
||||
- ReachabilitySwift
|
||||
- Toast
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
appscheme:
|
||||
@@ -109,8 +118,12 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
Flutter:
|
||||
:path: Flutter
|
||||
flutter_mailer:
|
||||
:path: ".symlinks/plugins/flutter_mailer/ios"
|
||||
flutter_volume_controller:
|
||||
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
gt3_flutter_plugin:
|
||||
:path: ".symlinks/plugins/gt3_flutter_plugin/ios"
|
||||
media_kit_libs_ios_video:
|
||||
@@ -156,7 +169,9 @@ SPEC CHECKSUMS:
|
||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
|
||||
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
||||
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
|
||||
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
||||
@@ -173,6 +188,7 @@ SPEC CHECKSUMS:
|
||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
|
||||
system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44
|
||||
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
|
||||
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
|
||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||
|
||||
@@ -181,8 +181,14 @@ class Request {
|
||||
);
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
print('get error: $e');
|
||||
return Future.error(await ApiInterceptor.dioError(e));
|
||||
Response errResponse = Response(
|
||||
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}');
|
||||
return response;
|
||||
} on DioException catch (e) {
|
||||
print('post error: $e');
|
||||
return Future.error(await ApiInterceptor.dioError(e));
|
||||
Response errResponse = Response(
|
||||
data: {
|
||||
'message': await ApiInterceptor.dioError(e)
|
||||
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
|
||||
statusCode: 200,
|
||||
requestOptions: RequestOptions(),
|
||||
);
|
||||
return errResponse;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'package:dio/dio.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import '../utils/storage.dart';
|
||||
// import 'package:get/get.dart' hide Response;
|
||||
|
||||
class ApiInterceptor extends Interceptor {
|
||||
@override
|
||||
@@ -71,34 +70,27 @@ class ApiInterceptor extends Interceptor {
|
||||
return '发送请求超时,请检查网络设置';
|
||||
case DioExceptionType.unknown:
|
||||
final String res = await checkConnect();
|
||||
return '$res \n 网络异常,请稍后重试!';
|
||||
// default:
|
||||
// return 'Dio异常';
|
||||
return '$res,网络异常!';
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String> checkConnect() async {
|
||||
final ConnectivityResult connectivityResult =
|
||||
await Connectivity().checkConnectivity();
|
||||
if (connectivityResult == ConnectivityResult.mobile) {
|
||||
return 'connected with mobile network';
|
||||
} else if (connectivityResult == ConnectivityResult.wifi) {
|
||||
return 'connected with wifi network';
|
||||
} else if (connectivityResult == ConnectivityResult.ethernet) {
|
||||
// I am connected to a ethernet network.
|
||||
return '';
|
||||
} else if (connectivityResult == ConnectivityResult.vpn) {
|
||||
// I am connected to a vpn network.
|
||||
// Note for iOS and macOS:
|
||||
// There is no separate network interface type for [vpn].
|
||||
// It returns [other] on any device (also simulator)
|
||||
return '';
|
||||
} else if (connectivityResult == ConnectivityResult.other) {
|
||||
// I am connected to a network which is not in the above mentioned networks.
|
||||
return '';
|
||||
} else if (connectivityResult == ConnectivityResult.none) {
|
||||
return 'not connected to any network';
|
||||
} else {
|
||||
switch (connectivityResult) {
|
||||
case ConnectivityResult.mobile:
|
||||
return '正在使用移动流量';
|
||||
case ConnectivityResult.wifi:
|
||||
return '正在使用wifi';
|
||||
case ConnectivityResult.ethernet:
|
||||
return '正在使用局域网';
|
||||
case ConnectivityResult.vpn:
|
||||
return '正在使用代理网络';
|
||||
case ConnectivityResult.other:
|
||||
return '正在使用其他网络';
|
||||
case ConnectivityResult.none:
|
||||
return '未连接到任何网络';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ import 'package:pilipala/utils/data.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc.
|
||||
import 'package:pilipala/utils/recommend_filter.dart';
|
||||
import 'package:catcher_2/catcher_2.dart';
|
||||
import './services/loggeer.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -40,7 +42,32 @@ void main() async {
|
||||
Request();
|
||||
await Request.setCookie();
|
||||
RecommendFilter();
|
||||
|
||||
// 异常捕获 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.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
|
||||
|
||||
@@ -133,6 +133,11 @@ class _AboutPageState extends State<AboutPage> {
|
||||
title: const Text('赞助'),
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
// 日志
|
||||
logs() {
|
||||
Get.toNamed('/logs');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// 内容
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/badge.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
@@ -80,7 +81,7 @@ class _ContentState extends State<Content> {
|
||||
height: height,
|
||||
),
|
||||
),
|
||||
height > maxHeight
|
||||
height > Get.size.height * 0.9
|
||||
? const PBadge(
|
||||
text: '长图',
|
||||
right: 8,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/badge.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
@@ -87,7 +88,7 @@ Widget picWidget(item, context) {
|
||||
childAspectRatio: aspectRatio,
|
||||
children: list,
|
||||
),
|
||||
if (len == 1 && origAspectRatio < 0.4)
|
||||
if (len == 1 && height > Get.size.height * 0.9)
|
||||
const PBadge(
|
||||
text: '长图',
|
||||
top: null,
|
||||
|
||||
@@ -63,13 +63,16 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
void setTabConfig() async {
|
||||
defaultTabs = tabsConfig;
|
||||
defaultTabs = [...tabsConfig];
|
||||
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
|
||||
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
|
||||
.where((i) => tabbarSort.contains((i['type'] as TabType).id))
|
||||
.toList();
|
||||
tabs.value = defaultTabs;
|
||||
|
||||
if (tabbarSort.contains(TabType.rcmd.id)) {
|
||||
initialIndex.value = tabbarSort.indexOf(TabType.rcmd.id);
|
||||
|
||||
@@ -25,16 +25,17 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
double width = (boxConstraints.maxWidth -
|
||||
final double width = (boxConstraints.maxWidth -
|
||||
StyleString.cardSpace *
|
||||
6 /
|
||||
MediaQuery.textScalerOf(context).scale(2.0));
|
||||
MediaQuery.textScalerOf(context).scale(1.0)) /
|
||||
2;
|
||||
return Container(
|
||||
constraints: const BoxConstraints(minHeight: 88),
|
||||
height: width / StyleString.aspectRatio,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
children: <Widget>[
|
||||
if (list[index].imageUrls != null &&
|
||||
list[index].imageUrls.isNotEmpty)
|
||||
AspectRatio(
|
||||
|
||||
@@ -22,6 +22,17 @@ class _TabbarSetPageState extends State<TabbarSetPage> {
|
||||
defaultTabs = tabsConfig;
|
||||
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
|
||||
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() {
|
||||
|
||||
201
lib/pages/setting/pages/logs.dart
Normal file
201
lib/pages/setting/pages/logs.dart
Normal 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(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -256,7 +256,12 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
||||
// 请求错误
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_videoReplyController.queryReplyList();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -784,7 +784,7 @@ InlineSpan buildContent(
|
||||
height: height,
|
||||
),
|
||||
),
|
||||
height > maxHeight
|
||||
height > Get.size.height * 0.9
|
||||
? const PBadge(
|
||||
text: '长图',
|
||||
right: 8,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/msg.dart';
|
||||
|
||||
@@ -5,7 +5,6 @@ import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.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/utils/utils.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
@@ -53,12 +52,13 @@ class ChatItem extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
bool isOwner =
|
||||
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 isArticle = item.msgType == 12; // 专栏
|
||||
bool isRevoke = item.msgType == MsgType.revoke; // 撤回消息
|
||||
bool isShareV2 = item.msgType == MsgType.share_v2;
|
||||
bool isRevoke = item.msgType == MsgType.revoke.value; // 撤回消息
|
||||
bool isShareV2 = item.msgType == MsgType.share_v2.value;
|
||||
bool isSystem =
|
||||
item.msgType == 18 || item.msgType == 10 || item.msgType == 13;
|
||||
dynamic content = item.content ?? '';
|
||||
@@ -83,14 +83,18 @@ class ChatItem extends StatelessWidget {
|
||||
if (emojiMap.containsKey(emojiKey)) {
|
||||
children.add(WidgetSpan(
|
||||
child: NetworkImgLayer(
|
||||
width: 18, height: 18,
|
||||
src: emojiMap[emojiKey]!,),
|
||||
width: 18,
|
||||
height: 18,
|
||||
src: emojiMap[emojiKey]!,
|
||||
),
|
||||
));
|
||||
}
|
||||
return '';
|
||||
},
|
||||
onNonMatch: (String text) {
|
||||
children.add(TextSpan(text: text, style: TextStyle(
|
||||
children.add(TextSpan(
|
||||
text: text,
|
||||
style: TextStyle(
|
||||
color: textColor(context),
|
||||
letterSpacing: 0.6,
|
||||
height: 1.5,
|
||||
@@ -123,11 +127,13 @@ class ChatItem extends StatelessWidget {
|
||||
return SystemNotice2(item: item);
|
||||
case MsgType.notify_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(
|
||||
letterSpacing: 0.6,
|
||||
height: 5,
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.8)
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.8),
|
||||
),
|
||||
);
|
||||
case MsgType.text:
|
||||
@@ -168,7 +174,9 @@ class ChatItem extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
letterSpacing: 0.6,
|
||||
height: 1.5,
|
||||
color: textColor(context), fontWeight: FontWeight.bold),
|
||||
color: textColor(context),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 1),
|
||||
Text(
|
||||
@@ -188,7 +196,9 @@ class ChatItem extends StatelessWidget {
|
||||
style: TextStyle(
|
||||
letterSpacing: 0.6,
|
||||
height: 1.5,
|
||||
color: textColor(context), fontWeight: FontWeight.bold),
|
||||
color: textColor(context),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +278,8 @@ class PlPlayerController {
|
||||
danmakuDurationVal =
|
||||
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(
|
||||
(e) =>
|
||||
e.value ==
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/pages/setting/pages/logs.dart';
|
||||
|
||||
import '../pages/about/index.dart';
|
||||
import '../pages/blacklist/index.dart';
|
||||
@@ -153,6 +154,8 @@ class Routes {
|
||||
// 用户专栏
|
||||
CustomGetPage(
|
||||
name: '/memberSeasons', page: () => const MemberSeasonsPage()),
|
||||
// 日志
|
||||
CustomGetPage(name: '/logs', page: () => const LogsPage()),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
56
lib/services/loggeer.dart
Normal file
56
lib/services/loggeer.dart
Normal 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;
|
||||
}
|
||||
@@ -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:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class IdUtils {
|
||||
static const String TABLE =
|
||||
'fZodR9XQDSUm21yCkr6zBqiveYah8bt4xsWpHnJE7jL5VG3guMTKNPAwcF';
|
||||
static const List<int> S = [11, 10, 3, 8, 4, 6]; // 位置编码表
|
||||
static const int XOR = 177451812; // 固定异或值
|
||||
static const int ADD = 8728348608; // 固定加法值
|
||||
static const List<String> r = [
|
||||
static final XOR_CODE = BigInt.parse('23442827791579');
|
||||
static final MASK_CODE = BigInt.parse('2251799813685247');
|
||||
static final MAX_AID = BigInt.one << (BigInt.from(51)).toInt();
|
||||
static final BASE = BigInt.from(58);
|
||||
|
||||
static const data =
|
||||
'FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf';
|
||||
|
||||
/// av转bv
|
||||
static String av2bv(int aid) {
|
||||
List<String> bytes = [
|
||||
'B',
|
||||
'V',
|
||||
'1',
|
||||
'',
|
||||
'',
|
||||
'4',
|
||||
'',
|
||||
'1',
|
||||
'',
|
||||
'7',
|
||||
'',
|
||||
''
|
||||
'0',
|
||||
'0',
|
||||
'0',
|
||||
'0',
|
||||
'0',
|
||||
'0',
|
||||
'0',
|
||||
'0',
|
||||
'0'
|
||||
];
|
||||
|
||||
/// av转bv
|
||||
static String av2bv(int av) {
|
||||
int x_ = (av ^ XOR) + ADD;
|
||||
List<String> newR = [];
|
||||
newR.addAll(r);
|
||||
for (int i = 0; i < S.length; i++) {
|
||||
newR[S[i]] =
|
||||
TABLE.characters.elementAt((x_ / pow(58, i).toInt() % 58).toInt());
|
||||
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
|
||||
static int bv2av(String bv) {
|
||||
int r = 0;
|
||||
for (int i = 0; i < S.length; i++) {
|
||||
r += (TABLE.indexOf(bv.characters.elementAt(S[i])).toInt()) *
|
||||
pow(58, i).toInt();
|
||||
}
|
||||
return (r - ADD) ^ XOR;
|
||||
/// bv转av
|
||||
static int bv2av(String bvid) {
|
||||
List<String> bvidArr = bvid.split('');
|
||||
final tmpValue = bvidArr[3];
|
||||
bvidArr[3] = bvidArr[9];
|
||||
bvidArr[9] = tmpValue;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// 匹配
|
||||
|
||||
52
pubspec.lock
52
pubspec.lock
@@ -208,7 +208,15 @@ packages:
|
||||
sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -547,6 +555,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -589,6 +605,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -781,6 +805,14 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -789,6 +821,14 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -951,7 +991,7 @@ packages:
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
@@ -1214,6 +1254,14 @@ packages:
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.17+1017
|
||||
version: 1.0.19+1019
|
||||
|
||||
environment:
|
||||
sdk: ">=2.19.6 <3.0.0"
|
||||
@@ -134,7 +134,9 @@ dependencies:
|
||||
uuid: ^3.0.7
|
||||
scrollable_positioned_list: ^0.3.8
|
||||
nil: ^1.1.1
|
||||
|
||||
catcher_2: ^1.1.0
|
||||
logger: ^2.0.2+1
|
||||
path: 1.8.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
@@ -205,6 +207,5 @@ flutter:
|
||||
# fonts:
|
||||
# - asset: assets/fonts/HarmonyOS_Sans_SC_Regular.ttf
|
||||
|
||||
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
|
||||
Reference in New Issue
Block a user