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:
@@ -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,35 +70,28 @@ 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 {
|
||||
return '';
|
||||
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();
|
||||
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.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 -
|
||||
StyleString.cardSpace *
|
||||
6 /
|
||||
MediaQuery.textScalerOf(context).scale(2.0));
|
||||
final double width = (boxConstraints.maxWidth -
|
||||
StyleString.cardSpace *
|
||||
6 /
|
||||
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 ?? '';
|
||||
@@ -72,7 +72,7 @@ class ChatItem extends StatelessWidget {
|
||||
var text = content['content'];
|
||||
if (e_infos != null) {
|
||||
final List<InlineSpan> children = [];
|
||||
Map<String,String> emojiMap = {};
|
||||
Map<String, String> emojiMap = {};
|
||||
for (var e in e_infos!) {
|
||||
emojiMap[e['text']] = e['url'];
|
||||
}
|
||||
@@ -83,18 +83,22 @@ 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(
|
||||
color: textColor(context),
|
||||
letterSpacing: 0.6,
|
||||
height: 1.5,
|
||||
)));
|
||||
children.add(TextSpan(
|
||||
text: text,
|
||||
style: TextStyle(
|
||||
color: textColor(context),
|
||||
letterSpacing: 0.6,
|
||||
height: 1.5,
|
||||
)));
|
||||
return '';
|
||||
},
|
||||
);
|
||||
@@ -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)
|
||||
letterSpacing: 0.6,
|
||||
height: 5,
|
||||
color: Theme.of(context).colorScheme.outline.withOpacity(0.8),
|
||||
),
|
||||
);
|
||||
case MsgType.text:
|
||||
@@ -166,9 +172,11 @@ class ChatItem extends StatelessWidget {
|
||||
Text(
|
||||
content['title'],
|
||||
style: TextStyle(
|
||||
letterSpacing: 0.6,
|
||||
height: 1.5,
|
||||
color: textColor(context), fontWeight: FontWeight.bold),
|
||||
letterSpacing: 0.6,
|
||||
height: 1.5,
|
||||
color: textColor(context),
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 1),
|
||||
Text(
|
||||
@@ -186,9 +194,11 @@ class ChatItem extends StatelessWidget {
|
||||
return Text(
|
||||
content['content'] ?? content.toString(),
|
||||
style: TextStyle(
|
||||
letterSpacing: 0.6,
|
||||
height: 1.5,
|
||||
color: textColor(context), fontWeight: FontWeight.bold),
|
||||
letterSpacing: 0.6,
|
||||
height: 1.5,
|
||||
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 = [
|
||||
'B',
|
||||
'V',
|
||||
'1',
|
||||
'',
|
||||
'',
|
||||
'4',
|
||||
'',
|
||||
'1',
|
||||
'',
|
||||
'7',
|
||||
'',
|
||||
''
|
||||
];
|
||||
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 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());
|
||||
static String av2bv(int aid) {
|
||||
List<String> bytes = [
|
||||
'B',
|
||||
'V',
|
||||
'1',
|
||||
'0',
|
||||
'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
|
||||
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();
|
||||
}
|
||||
|
||||
// 匹配
|
||||
|
||||
Reference in New Issue
Block a user