Files
PiliPlus/lib/pages/about/view.dart
2026-03-14 17:44:10 +08:00

316 lines
11 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:PiliPlus/build_config.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/dialog/export_import.dart';
import 'package:PiliPlus/common/widgets/flutter/list_tile.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/services/logger.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/cache_manager.dart';
import 'package:PiliPlus/utils/date_utils.dart';
import 'package:PiliPlus/utils/extension/context_ext.dart';
import 'package:PiliPlus/utils/extension/num_ext.dart';
import 'package:PiliPlus/utils/login_utils.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/platform_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/update.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart' hide ListTile;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
class AboutPage extends StatefulWidget {
const AboutPage({super.key, this.showAppBar = true});
final bool showAppBar;
@override
State<AboutPage> createState() => _AboutPageState();
}
class _AboutPageState extends State<AboutPage> {
final currentVersion =
'${BuildConfig.versionName}+${BuildConfig.versionCode}';
RxString cacheSize = ''.obs;
late int _pressCount = 0;
@override
void initState() {
super.initState();
getCacheSize();
}
@override
void dispose() {
cacheSize.close();
super.dispose();
}
void getCacheSize() {
CacheManager.loadApplicationCache().then((res) {
if (mounted) {
cacheSize.value = CacheManager.formatSize(res);
}
});
}
void _showDialog() => showDialog(
context: context,
builder: (context) => AlertDialog(
constraints: StyleString.dialogFixedConstraints,
content: TextField(
autofocus: true,
onSubmitted: (value) {
Get.back();
if (value.isNotEmpty) {
PageUtils.handleWebview(value, inApp: true);
}
},
),
),
);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
const style = TextStyle(fontSize: 15);
final outline = theme.colorScheme.outline;
final subTitleStyle = TextStyle(fontSize: 13, color: outline);
final showAppBar = widget.showAppBar;
final padding = MediaQuery.viewPaddingOf(context);
return Scaffold(
appBar: showAppBar ? AppBar(title: const Text('关于')) : null,
resizeToAvoidBottomInset: false,
body: ListView(
padding: EdgeInsets.only(
left: showAppBar ? padding.left : 0,
right: showAppBar ? padding.right : 0,
bottom: padding.bottom + 100,
),
children: [
GestureDetector(
onTap: () {
if (++_pressCount == 5) {
_pressCount = 0;
_showDialog();
}
},
onSecondaryTap: PlatformUtils.isDesktop ? _showDialog : null,
child: Image.asset(
width: 150,
height: 150,
excludeFromSemantics: true,
cacheWidth: 150.cacheSize(context),
'assets/images/logo/logo.png',
),
),
ListTile(
title: Text(
Constants.appName,
textAlign: TextAlign.center,
style: theme.textTheme.titleMedium!.copyWith(height: 2),
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'使用Flutter开发的B站第三方客户端',
style: TextStyle(color: outline),
semanticsLabel: '与你一起,发现不一样的世界',
),
const Icon(
Icons.accessibility_new,
semanticLabel: "无障碍适配",
size: 18,
),
],
),
),
ListTile(
onTap: () => Update.checkUpdate(false),
onLongPress: () => Utils.copyText(currentVersion),
onSecondaryTap: PlatformUtils.isMobile
? null
: () => Utils.copyText(currentVersion),
title: const Text('当前版本'),
leading: const Icon(Icons.commit_outlined),
trailing: Text(
currentVersion,
style: subTitleStyle,
),
),
ListTile(
title: Text(
'''
Build Time: ${DateFormatUtils.format(BuildConfig.buildTime, format: DateFormatUtils.longFormatDs)}
Commit Hash: ${BuildConfig.commitHash}''',
style: const TextStyle(fontSize: 14),
),
leading: const Icon(Icons.info_outline),
onTap: () => PageUtils.launchURL(
'${Constants.sourceCodeUrl}/commit/${BuildConfig.commitHash}',
),
onLongPress: () => Utils.copyText(BuildConfig.commitHash),
onSecondaryTap: PlatformUtils.isMobile
? null
: () => Utils.copyText(BuildConfig.commitHash),
),
Divider(
thickness: 1,
height: 30,
color: theme.colorScheme.outlineVariant,
),
ListTile(
onTap: () => PageUtils.launchURL(Constants.sourceCodeUrl),
leading: const Icon(Icons.code),
title: const Text('Source Code'),
subtitle: Text(Constants.sourceCodeUrl, style: subTitleStyle),
),
if (Platform.isAndroid)
ListTile(
onTap: () => Utils.channel.invokeMethod('linkVerifySettings'),
leading: const Icon(MdiIcons.linkBoxOutline),
title: const Text('打开受支持的链接'),
trailing: Icon(
Icons.arrow_forward,
size: 16,
color: outline,
),
),
ListTile(
onTap: () =>
PageUtils.launchURL('${Constants.sourceCodeUrl}/issues'),
leading: const Icon(Icons.feedback_outlined),
title: const Text('问题反馈'),
trailing: Icon(
Icons.arrow_forward,
size: 16,
color: outline,
),
),
ListTile(
onTap: () => Get.toNamed('/logs'),
onLongPress: LoggerUtils.clearLogs,
onSecondaryTap: PlatformUtils.isMobile
? null
: LoggerUtils.clearLogs,
leading: const Icon(Icons.bug_report_outlined),
title: const Text('错误日志'),
subtitle: Text('长按清除日志', style: subTitleStyle),
trailing: Icon(Icons.arrow_forward, size: 16, color: outline),
),
ListTile(
onTap: () {
if (cacheSize.value.isNotEmpty) {
showConfirmDialog(
context: context,
title: '提示',
content: '该操作将清除图片及网络请求缓存数据,确认清除?',
onConfirm: () async {
SmartDialog.showLoading(msg: '正在清除...');
try {
await CacheManager.clearLibraryCache();
SmartDialog.showToast('清除成功');
} catch (err) {
SmartDialog.showToast(err.toString());
} finally {
SmartDialog.dismiss();
}
getCacheSize();
},
);
}
},
leading: const Icon(Icons.delete_outline),
title: const Text('清除缓存'),
subtitle: Obx(
() => Text(
'图片及网络缓存 ${cacheSize.value}',
style: subTitleStyle,
),
),
),
ListTile(
title: const Text('导入/导出登录信息'),
leading: const Icon(Icons.import_export_outlined),
onTap: () => showImportExportDialog<Map>(
context,
title: '登录信息',
localFileName: () => 'account',
onExport: () =>
Utils.jsonEncoder.convert(Accounts.account.toMap()),
onImport: (json) async {
final res = json.map(
(key, value) => MapEntry(key, LoginAccount.fromJson(value)),
);
await Accounts.account.putAll(res);
await Accounts.refresh();
MineController.anonymity.value = !Accounts.heartbeat.isLogin;
if (Accounts.main.isLogin) {
await LoginUtils.onLoginMain();
}
},
),
),
ListTile(
title: const Text('导入/导出设置'),
dense: false,
leading: const Icon(Icons.import_export_outlined),
onTap: () => showImportExportDialog<Map<String, dynamic>>(
context,
title: '设置',
localFileName: () => 'setting_${context.platformName}',
label: GStorage.setting.name,
onExport: GStorage.exportAllSettings,
onImport: GStorage.importAllJsonSettings,
),
),
ListTile(
title: const Text('重置所有设置'),
leading: const Icon(Icons.settings_backup_restore_outlined),
onTap: () => showDialog(
context: context,
builder: (context) {
return SimpleDialog(
clipBehavior: Clip.hardEdge,
title: const Text('是否重置所有设置?'),
children: [
ListTile(
dense: true,
onTap: () async {
Get.back();
await Future.wait([
GStorage.setting.clear(),
GStorage.video.clear(),
]);
SmartDialog.showToast('重置成功');
},
title: const Text('重置可导出的设置', style: style),
),
ListTile(
dense: true,
onTap: () async {
Get.back();
await GStorage.clear();
SmartDialog.showToast('重置成功');
},
title: const Text('重置所有数据(含登录信息)', style: style),
),
],
);
},
),
),
],
),
);
}
}