chore: 完成v1.2版本迭代优化与兼容修复

1. 基础适配升级:将iOS最低版本提升至15.5,更新Podfile与Xcode配置
2. 导航栈优化:使用push替代go保留路由栈,重构路由重定向逻辑
3. 多场景崩溃防护:
   - 新增目录获取降级逻辑,适配异常环境
   - 增加上下文挂载检查,防止PostFrame回调崩溃
   - 完善Hive初始化与Box访问的异常处理,实现空操作降级
4. 体验优化:
   - 修复摇一摇权限开关逻辑,更新个人中心文案
   - 新增请求超时保护与异常提示
   - 优化分享/保存相册的模拟器兼容处理
   - 重构引导页与初始化流程,修复数据丢失问题
5. 代码清理:移除重复路由注册,优化Widget预览页面生命周期
6. 细节优化:更新微信联系方式展示样式,完善日签与备份服务逻辑
This commit is contained in:
Developer
2026-06-04 10:26:42 +08:00
parent 67f26ff166
commit 33f59046b3
58 changed files with 872 additions and 3014 deletions

View File

@@ -1,298 +0,0 @@
/// ============================================================
/// 闲言APP — 字体下载URL验证脚本
/// 创建时间: 2026-05-22
/// 更新时间: 2026-05-22
/// 作用: 验证所有在线字体URL的可访问性和文件格式有效性
/// 上次更新: 初始创建验证8个字体+备用URL的下载和文件头校验
/// 运行方式: dart run Scripts/verify_fonts.dart
/// ============================================================
import 'dart:io';
import 'dart:typed_data';
/// 字体数据定义(与 font_models.dart 保持同步)
const onlineFontData = [
(
'霞鹜文楷',
'LXGWWenKai',
'https://raw.githubusercontent.com/lxgw/LxgwWenKai/main/fonts/LXGWWenKai-Regular.ttf',
'🖋️',
),
(
'阿里巴巴普惠体',
'AlibabaPuHuiTi',
'https://puhuiti.oss-cn-hangzhou.aliyuncs.com/AlibabaPuHuiTi-3/AlibabaPuHuiTi-3-55-Regular/AlibabaPuHuiTi-3-55-Regular.ttf',
'💼',
),
(
'站酷快乐体',
'ZCOOLKuaiLe',
'https://raw.githubusercontent.com/googlefonts/zcool-kuaile/main/fonts/ttf/ZCOOLKuaiLe-Regular.ttf',
'🎉',
),
(
'站酷小薇',
'ZCOOLXiaoWei',
'https://raw.githubusercontent.com/googlefonts/zcool-xiaowei/main/fonts/ZCOOLXiaoWei-Regular.ttf',
'🌸',
),
(
'思源黑体',
'NotoSansSC',
'https://raw.githubusercontent.com/notofonts/noto-cjk/main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf',
'📐',
),
(
'思源宋体',
'NotoSerifSC',
'https://raw.githubusercontent.com/notofonts/noto-cjk/main/Serif/OTF/SimplifiedChinese/NotoSerifCJKsc-Regular.otf',
'📜',
),
(
'更纱黑体',
'SarasaGothicSC',
'https://raw.githubusercontent.com/be5invis/Sarasa-Gothic/main/fonts/SarasaGothicSC-Regular.ttf',
'🎯',
),
(
'文泉驿微米黑',
'WenQuanYiMicroHei',
'https://raw.githubusercontent.com/niclas/wqy-microhei-font/master/wqy-microhei.ttc',
'✒️',
),
];
const fontFallbackUrls = <String, List<String>>{
'LXGWWenKai': [
'https://cdn.jsdelivr.net/gh/lxgw/LxgwWenKai@v1.501/fonts/LXGWWenKai-Regular.ttf',
'https://github.com/lxgw/LxgwWenKai/releases/download/v1.501/LXGWWenKai-Regular.ttf',
],
'AlibabaPuHuiTi': [
'https://fonts.alicdn.com/font/AlibabaPuHuiTi-3/AlibabaPuHuiTi-3-55-Regular.ttf',
],
'ZCOOLKuaiLe': [
'https://cdn.jsdelivr.net/gh/googlefonts/zcool-kuaile@main/fonts/ttf/ZCOOLKuaiLe-Regular.ttf',
],
'ZCOOLXiaoWei': [
'https://cdn.jsdelivr.net/gh/googlefonts/zcool-xiaowei@main/fonts/ZCOOLXiaoWei-Regular.ttf',
],
'NotoSansSC': [
'https://cdn.jsdelivr.net/gh/notofonts/noto-cjk@main/Sans/OTF/SimplifiedChinese/NotoSansCJKsc-Regular.otf',
],
'NotoSerifSC': [
'https://cdn.jsdelivr.net/gh/notofonts/noto-cjk@main/Serif/OTF/SimplifiedChinese/NotoSerifCJKsc-Regular.otf',
],
'SarasaGothicSC': [
'https://github.com/be5invis/Sarasa-Gothic/releases/download/v1.0.19/SarasaGothicSC-Regular.ttf',
],
'WenQuanYiMicroHei': [
'https://cdn.jsdelivr.net/gh/niclas/wqy-microhei-font@master/wqy-microhei.ttc',
],
};
/// 校验字体文件头是否为有效 TTF/OTF/TTC
bool isValidFontFile(Uint8List bytes) {
if (bytes.length < 4) return false;
final h = bytes;
final isTtf = (h[0] == 0x00 && h[1] == 0x01 && h[2] == 0x00 && h[3] == 0x00) ||
(h[0] == 0x74 && h[1] == 0x72 && h[2] == 0x75 && h[3] == 0x65);
final isOtf = h[0] == 0x4F && h[1] == 0x54 && h[2] == 0x54 && h[3] == 0x4F;
final isTtc = h[0] == 0x74 && h[1] == 0x74 && h[2] == 0x63 && h[3] == 0x66;
return isTtf || isOtf || isTtc;
}
/// 检测字体格式类型
String detectFontType(Uint8List bytes) {
if (bytes.length < 4) return '未知(数据不足)';
final h = bytes;
if (h[0] == 0x00 && h[1] == 0x01 && h[2] == 0x00 && h[3] == 0x00) return 'TTF';
if (h[0] == 0x74 && h[1] == 0x72 && h[2] == 0x75 && h[3] == 0x65) return 'TTF(true)';
if (h[0] == 0x4F && h[1] == 0x54 && h[2] == 0x54 && h[3] == 0x4F) return 'OTF';
if (h[0] == 0x74 && h[1] == 0x74 && h[2] == 0x63 && h[3] == 0x66) return 'TTC';
return '未知(0x${h[0].toRadixString(16)}${h[1].toRadixString(16)}${h[2].toRadixString(16)}${h[3].toRadixString(16)})';
}
/// 下载字体并验证
Future<_FontVerifyResult> verifyFontUrl(
String name,
String fontFamily,
String url,
HttpClient client,
) async {
final result = _FontVerifyResult(name: name, fontFamily: fontFamily, url: url);
try {
final request = await client.getUrl(Uri.parse(url));
final response = await request.close();
result.statusCode = response.statusCode;
if (response.statusCode != 200) {
result.success = false;
result.error = 'HTTP ${response.statusCode}';
return result;
}
final bytes = await consolidateBytes(response);
result.fileSize = bytes.length;
result.fontType = detectFontType(bytes);
result.validFormat = isValidFontFile(bytes);
result.success = result.validFormat;
if (!result.validFormat) {
result.error = '文件头非 TTF/OTF/TTC 格式,检测为: ${result.fontType}';
}
} catch (e) {
result.success = false;
result.error = e.toString();
}
return result;
}
/// 合并 HttpClientResponse 的字节流
Future<Uint8List> consolidateBytes(HttpClientResponse response) async {
final builder = BytesBuilder();
await for (final chunk in response) {
builder.add(chunk);
}
return builder.toBytes();
}
/// 验证结果
class _FontVerifyResult {
_FontVerifyResult({
required this.name,
required this.fontFamily,
required this.url,
});
final String name;
final String fontFamily;
final String url;
bool success = false;
int? statusCode;
int? fileSize;
String? fontType;
bool validFormat = false;
String? error;
String get fileSizeStr {
if (fileSize == null) return 'N/A';
if (fileSize! < 1024) return '$fileSize B';
if (fileSize! < 1024 * 1024) return '${(fileSize! / 1024).toStringAsFixed(1)} KB';
return '${(fileSize! / (1024 * 1024)).toStringAsFixed(1)} MB';
}
}
Future<void> main() async {
print('═══════════════════════════════════════════════════════════');
print(' 闲言APP — 字体下载URL验证工具');
print(' 运行时间: ${DateTime.now().toIso8601String()}');
print('═══════════════════════════════════════════════════════════');
print('');
final client = HttpClient();
client.connectionTimeout = const Duration(seconds: 30);
client.userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36';
final results = <_FontVerifyResult>[];
print('━━━ 阶段1: 主URL验证 ━━━');
print('');
for (final font in onlineFontData) {
final name = font.$1;
final fontFamily = font.$2;
final url = font.$3;
stdout.write(' $name ($fontFamily)... ');
final result = await verifyFontUrl(name, fontFamily, url, client);
results.add(result);
if (result.success) {
print('✅ 通过 [${result.fontType}] ${result.fileSizeStr}');
} else {
print('❌ 失败 [${result.error}]');
}
}
print('');
print('━━━ 阶段2: 备用URL验证 ━━━');
print('');
for (final entry in fontFallbackUrls.entries) {
final fontFamily = entry.key;
final fallbacks = entry.value;
final fontName = onlineFontData
.where((f) => f.$2 == fontFamily)
.map((f) => f.$1)
.firstOrNull ??
fontFamily;
for (int i = 0; i < fallbacks.length; i++) {
final url = fallbacks[i];
stdout.write(' $fontName 备用#${i + 1}... ');
final result = await verifyFontUrl(fontName, fontFamily, url, client);
results.add(result);
if (result.success) {
print('✅ 通过 [${result.fontType}] ${result.fileSizeStr}');
} else {
print('❌ 失败 [${result.error}]');
}
}
}
client.close();
print('');
print('═══════════════════════════════════════════════════════════');
print(' 验证结果汇总');
print('═══════════════════════════════════════════════════════════');
print('');
final primaryResults = results.sublist(0, onlineFontData.length);
final fallbackResults = results.sublist(onlineFontData.length);
print(' 主URL结果:');
for (final r in primaryResults) {
final icon = r.success ? '' : '';
print(' $icon ${r.name}${r.success ? '${r.fontType} ${r.fileSizeStr}' : r.error}');
}
print('');
print(' 备用URL结果:');
for (final r in fallbackResults) {
final icon = r.success ? '' : '';
print(' $icon ${r.name}${r.success ? '${r.fontType} ${r.fileSizeStr}' : r.error}');
}
final primaryPass = primaryResults.where((r) => r.success).length;
final primaryTotal = primaryResults.length;
final allPass = results.where((r) => r.success).length;
final allTotal = results.length;
print('');
print(' 主URL: $primaryPass/$primaryTotal 通过');
print(' 全部: $allPass/$allTotal 通过');
print('');
final failedPrimary = primaryResults.where((r) => !r.success).toList();
if (failedPrimary.isNotEmpty) {
print(' ⚠️ 以下字体主URL不可用需要更新:');
for (final r in failedPrimary) {
final hasFallback = fallbackResults.any(
(f) => f.fontFamily == r.fontFamily && f.success,
);
print(' - ${r.name}: ${r.error} ${hasFallback ? '(有可用备用URL)' : '(⚠️ 无可用备用URL!)'}');
}
} else {
print(' 🎉 所有字体主URL验证通过');
}
print('');
print('═══════════════════════════════════════════════════════════');
exit(failedPrimary.isNotEmpty ? 1 : 0);
}