鸿蒙端提交
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -59,3 +59,4 @@ app.*.map.json
|
||||
|
||||
# Trae IDE
|
||||
.trae/
|
||||
docs/toolsapi/thinkphp
|
||||
|
||||
86
CHANGELOG.md
86
CHANGELOG.md
@@ -2,7 +2,45 @@
|
||||
|
||||
所有重要变更均记录于此文件。格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/)。
|
||||
|
||||
> 保留最近 10 个版本(v6.79.0 ~ v6.88.0)。更早版本已归档至软件特性功能文档。
|
||||
> 保留最近 10 个版本(v6.80.0 ~ v6.89.0)。更早版本已归档至软件特性功能文档。
|
||||
|
||||
***
|
||||
|
||||
## [v6.89.0] - 2026-06-17
|
||||
|
||||
### 🛠 评分弹窗商店名称多语言 + Beta问卷按钮隐藏跨平台修复
|
||||
|
||||
#### 1. "给个好评"弹窗商店名称由硬编码"Google Play"改为多语言"应用商店"
|
||||
- **需求**: Android 端点击"给个好评"后,跳转确认弹窗显示硬编码英文"Google Play",未接入多语言系统,风格不统一
|
||||
- **实现**:
|
||||
- `t_about.dart` 新增 `appStore` 翻译字段(构造函数 + 字段定义 + toMap + fromMap)
|
||||
- 14 种语言文件全部补充 `appStore` 翻译:
|
||||
- zh_cn: 应用商店 / zh_tw: 應用商店 / en: App Store / ru: Магазин приложений
|
||||
- pt: Loja de aplicativos / ko: 앱 스토어 / ja: アプリストア / it: App Store
|
||||
- hi: ऐप स्टोर / fr: App Store / es: Tienda de aplicaciones / de: App Store
|
||||
- bn: অ্যাপ স্টোর / ar: متجر التطبيقات
|
||||
- `app_store_service.dart` 的 `getStoreName` 方法:Android 分支由 `return 'Google Play'` 改为 `return t.about.appStore`
|
||||
- **影响**: `profile_page.dart` 和 `about_page.dart` 的"给个好评"/"评价应用"入口弹窗文案自动跟随系统语言
|
||||
|
||||
#### 2. Beta 页面"填写问卷"按钮提交后隐藏逻辑跨平台生效
|
||||
- **问题**: 问卷提交后按钮只在 iOS 端隐藏,Android 等端不隐藏
|
||||
- **根因**:
|
||||
1. `_QuestionnaireSheet` 不符合条件关闭时调用 `_markQuestionnaireSubmitted()` 未 `await`,`Navigator.pop` 可能在 SharedPreferences 写入完成前执行
|
||||
2. Sheet 关闭后仅依赖 `_loadQuestionnaireSubmitted()` 异步重读 SharedPreferences 更新 UI,跨平台存在时序差异(iOS NSUserDefaults 内存同步快,Android SharedPreferences.apply 异步落盘)
|
||||
- **修复**:
|
||||
- `_QuestionnaireSheet` 的两处关闭按钮改为 `Navigator.pop(context, true)` 返回已提交标记
|
||||
- `_showQuestionnaire` 接收 Sheet 返回值,`submitted == true` 时立即 `setState` 更新 `_questionnaireSubmitted`,不再依赖 SharedPreferences 时序
|
||||
- 不符合条件关闭分支改为 `async` + `await _markQuestionnaireSubmitted()`,并在 await 前获取 `Navigator.of(context)` 避免 `use_build_context_synchronously`
|
||||
- 保留 `_loadQuestionnaireSubmitted()` 作为兜底,确保与持久化状态最终一致
|
||||
- **效果**: 所有平台(iOS/Android/Windows/macOS)问卷提交后按钮立即隐藏
|
||||
|
||||
#### 修改文件
|
||||
| 文件 | 变更 |
|
||||
|---|---|
|
||||
| `l10n/types/t_about.dart` | 新增 `appStore` 字段(构造/定义/toMap/fromMap) |
|
||||
| `l10n/languages/*.dart` (14 个) | 补充 `appStore` 多语言翻译 |
|
||||
| `core/services/app_store_service.dart` | Android 端商店名称改用 `t.about.appStore` |
|
||||
| `features/settings/presentation/experimental_features_page.dart` | 问卷 Sheet 返回值即时更新 UI + await 修复时序 |
|
||||
|
||||
***
|
||||
|
||||
@@ -312,49 +350,3 @@
|
||||
| `l10n/languages/en.dart` | 新增英文翻译:Remember Account |
|
||||
| `features/auth/presentation/login_form_sections.dart` | `PasswordFormSection` 新增 `isRemembered`/`onToggleRemember` 参数,将忘记密码按钮改为 Row 布局(左:记住账户复选框,右:忘记密码) |
|
||||
| `features/auth/presentation/login_page.dart` | 新增 `_rememberAccount` 状态、`_saveRememberAccount()` 方法;`_loadLastLoginAccount()` 读取 `remember_account` 偏好;`_handlePasswordLogin()` 登录成功后保存/清除账户 |
|
||||
|
||||
***
|
||||
|
||||
## [v6.79.0] - 2026-06-17
|
||||
|
||||
### 🔒 隐私合规 — 修复安卓端自启动问题(androidx.glance.appwidget)
|
||||
|
||||
#### 问题描述
|
||||
1. 应用商店审核发现:`androidx.glance.appwidget` SDK 在应用退出后触发自启动(1次/秒),无隐私文本覆盖
|
||||
2. 审核依据:《个人信息保护法》要求 APP 未向用户明示且未经用户同意,不得存在频繁自启动行为
|
||||
3. 根因:`home_widget` 包引入了 `androidx.glance:glance-appwidget:1.1.1` 依赖,项目未使用 Glance Widget 但该库被打包进 APK,其内部 `GlanceAppWidgetReceiver`/`GlanceAppWidgetService` 被系统广播触发导致自启动
|
||||
4. 次要问题:8个桌面小部件 Provider 在系统定时 `APPWIDGET_UPDATE` 广播触发时,未检查隐私协议状态即执行数据读取
|
||||
|
||||
#### 修复内容
|
||||
|
||||
**1. 彻底移除 androidx.glance.appwidget 依赖**
|
||||
| 文件 | 变更 |
|
||||
|---|---|
|
||||
| `packages/home_widget/android/build.gradle` | 移除 `implementation "androidx.glance:glance-appwidget:1.1.1"` 依赖 |
|
||||
| `HomeWidgetGlanceWidgetReceiver.kt` | 删除未使用的 Glance Receiver 源文件 |
|
||||
| `HomeWidgetGlanceState.kt` | 删除未使用的 Glance State 源文件 |
|
||||
|
||||
**2. AndroidManifest 排除 Glance 组件合并**
|
||||
| 文件 | 变更 |
|
||||
|---|---|
|
||||
| `AndroidManifest.xml` | 新增 `tools:node="remove"` 移除 `GlanceAppWidgetReceiver` 和 `GlanceAppWidgetService`,防止残留库通过 manifest merge 注入 |
|
||||
|
||||
**3. 所有 Widget Provider 增加隐私协议守门**
|
||||
| 文件 | 变更 |
|
||||
|---|---|
|
||||
| `PrivacyAwareHomeWidgetProvider.kt` | 新建基类,在 `onUpdate` 中检查 `agreement_accepted` 标志,未同意时显示占位视图不读取任何业务数据 |
|
||||
| `widget_privacy_placeholder.xml` | 新建隐私占位布局,提示"请先同意隐私政策" |
|
||||
| `DailySentenceProvider.kt` | 改为继承 `PrivacyAwareHomeWidgetProvider`,`onUpdate` → `onUpdateWithAgreement` |
|
||||
| `ReadlaterProvider.kt` | 同上 |
|
||||
| `DailyCardProvider.kt` | 同上 |
|
||||
| `FortuneProvider.kt` | 同上 |
|
||||
| `CountdownProvider.kt` | 同上 |
|
||||
| `PomodoroProvider.kt` | 同上 |
|
||||
| `SolarTermProvider.kt` | 同上 |
|
||||
| `CheckinProvider.kt` | 同上 |
|
||||
| `CtcLatestNoteProvider.kt` | 同上 |
|
||||
|
||||
#### 合规影响
|
||||
- `androidx.glance.appwidget` 自启动行为彻底消除(依赖移除 + Manifest 排除 + 源文件删除三重防护)
|
||||
- Widget Provider 在用户未同意隐私政策前不执行任何数据操作,仅显示占位提示
|
||||
- 与现有 `SplashActivity` 协议守门机制形成完整闭环
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// 创建时间: 2026-06-17
|
||||
/// 更新时间: 2026-06-17
|
||||
/// 作用: 根据平台和语言地区生成正确的应用商店URL
|
||||
/// 上次更新: 初始版本,支持iOS/Android/鸿蒙/Windows/macOS多平台多地区
|
||||
/// 上次更新: Android端商店名称由硬编码"Google Play"改为多语言"应用商店"(t.about.appStore)
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
@@ -78,14 +78,16 @@ class AppStoreService {
|
||||
/// [locale] 当前语言Locale,用于判断地区
|
||||
static Uri getIOSAppStoreUrl(Locale locale) {
|
||||
final countryCode = locale.countryCode ?? '';
|
||||
final appId = _iosAppIdsByRegion[countryCode.toUpperCase()] ??
|
||||
_iosDefaultAppId;
|
||||
final appId =
|
||||
_iosAppIdsByRegion[countryCode.toUpperCase()] ?? _iosDefaultAppId;
|
||||
|
||||
// App Store URL格式: https://apps.apple.com/{region}/app/id{appId}
|
||||
// 如果有地区代码,加入地区路径以正确跳转
|
||||
final region = countryCode.isNotEmpty ? '$countryCode/' : '';
|
||||
final url = 'https://apps.apple.com/${region}app/id$appId';
|
||||
Log.d('AppStoreService: iOS URL = $url (region: $countryCode, appId: $appId)');
|
||||
Log.d(
|
||||
'AppStoreService: iOS URL = $url (region: $countryCode, appId: $appId)',
|
||||
);
|
||||
return Uri.parse(url);
|
||||
}
|
||||
|
||||
@@ -167,9 +169,15 @@ class AppStoreService {
|
||||
}
|
||||
|
||||
/// 获取应用商店显示名称
|
||||
///
|
||||
/// 根据当前平台返回对应应用商店的本地化名称。
|
||||
/// - iOS/macOS: App Store
|
||||
/// - Android: 应用商店(多语言,使用 t.about.appStore)
|
||||
/// - 鸿蒙: 华为应用市场(多语言)
|
||||
/// - Windows: Microsoft Store
|
||||
static String getStoreName(T t) {
|
||||
if (pu.isIOS) return 'App Store';
|
||||
if (pu.isAndroid) return 'Google Play';
|
||||
if (pu.isAndroid) return t.about.appStore;
|
||||
if (pu.isOhos) return t.about.huaweiStore;
|
||||
if (pu.isWindows) return 'Microsoft Store';
|
||||
if (pu.isMacOS) return 'App Store';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/// 创建时间: 2026-05-30
|
||||
/// 更新时间: 2026-06-17
|
||||
/// 作用: 展示开发中/测试中/预览中的功能列表和问题列表,接入远程FeatureFlag服务
|
||||
/// 上次更新: 问卷进度实时保存(草稿持久化)、输入法遮挡修复、硬编码替换为多语言翻译键
|
||||
/// 上次更新: 修复问卷提交后按钮隐藏只在iOS端生效的问题——Sheet返回值即时更新UI + await修复时序
|
||||
/// ============================================================
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@@ -108,34 +108,41 @@ class _ExperimentalFeaturesPageState
|
||||
),
|
||||
// 底部问卷按钮(提交后隐藏)
|
||||
if (!_questionnaireSubmitted)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
AppSpacing.md, AppSpacing.sm, AppSpacing.md, AppSpacing.md,
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: CupertinoButton(
|
||||
color: ext.accent,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(CupertinoIcons.question_circle_fill, size: 18, color: ext.textOnAccent),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
t.beta.questionnaireBtn,
|
||||
style: AppTypography.subhead.copyWith(
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
AppSpacing.md,
|
||||
AppSpacing.sm,
|
||||
AppSpacing.md,
|
||||
AppSpacing.md,
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: CupertinoButton(
|
||||
color: ext.accent,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
padding: const EdgeInsets.symmetric(vertical: 14),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
CupertinoIcons.question_circle_fill,
|
||||
size: 18,
|
||||
color: ext.textOnAccent,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
t.beta.questionnaireBtn,
|
||||
style: AppTypography.subhead.copyWith(
|
||||
color: ext.textOnAccent,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () => _showQuestionnaire(ext, t),
|
||||
),
|
||||
onPressed: () => _showQuestionnaire(ext, t),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -145,15 +152,22 @@ class _ExperimentalFeaturesPageState
|
||||
// ---- 问卷 ----
|
||||
|
||||
/// 弹出问卷Sheet
|
||||
///
|
||||
/// Sheet 关闭后返回是否已提交:
|
||||
/// - 优先使用返回值立即更新 UI(避免 SharedPreferences 跨平台时序差异)
|
||||
/// - 再异步读取 SharedPreferences 作为兜底,确保状态最终一致
|
||||
Future<void> _showQuestionnaire(AppThemeExtension ext, T t) async {
|
||||
await showCupertinoModalPopup<void>(
|
||||
final submitted = await showCupertinoModalPopup<bool>(
|
||||
context: context,
|
||||
builder: (_) => _QuestionnaireSheet(ext: ext, t: t),
|
||||
);
|
||||
// Sheet关闭后刷新问卷提交状态
|
||||
if (mounted) {
|
||||
_loadQuestionnaireSubmitted();
|
||||
if (!mounted) return;
|
||||
// 立即根据 Sheet 返回值更新 UI(所有平台一致生效)
|
||||
if (submitted == true && !_questionnaireSubmitted) {
|
||||
setState(() => _questionnaireSubmitted = true);
|
||||
}
|
||||
// 异步刷新作为兜底,确保与持久化状态一致
|
||||
_loadQuestionnaireSubmitted();
|
||||
}
|
||||
|
||||
// ---- 分段控制器 ----
|
||||
@@ -234,7 +248,11 @@ class _ExperimentalFeaturesPageState
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildFlagsList(AppThemeExtension ext, T t, List<FeatureFlagItem> flags) {
|
||||
Widget _buildFlagsList(
|
||||
AppThemeExtension ext,
|
||||
T t,
|
||||
List<FeatureFlagItem> flags,
|
||||
) {
|
||||
// 仅显示服务端启用的功能标志
|
||||
final visibleFlags = flags.where((f) => f.enabled).toList();
|
||||
|
||||
@@ -262,7 +280,12 @@ class _ExperimentalFeaturesPageState
|
||||
ext: ext,
|
||||
t: t,
|
||||
onToggle: (key, enabled) {
|
||||
_showToggleConfirmDialog(ext, t, visibleFlags[index], enabled);
|
||||
_showToggleConfirmDialog(
|
||||
ext,
|
||||
t,
|
||||
visibleFlags[index],
|
||||
enabled,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -674,7 +697,10 @@ class _RemoteFeatureCard extends StatelessWidget {
|
||||
Icon(CupertinoIcons.group_solid, size: 12, color: ext.textHint),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
t.beta.rolloutPercentage.replaceAll('{0}', '${(flag.rolloutPercentage * 100).toInt()}'),
|
||||
t.beta.rolloutPercentage.replaceAll(
|
||||
'{0}',
|
||||
'${(flag.rolloutPercentage * 100).toInt()}',
|
||||
),
|
||||
style: AppTypography.caption2.copyWith(color: ext.textHint),
|
||||
),
|
||||
if (flag.targetGroup != null) ...[
|
||||
@@ -692,12 +718,15 @@ class _RemoteFeatureCard extends StatelessWidget {
|
||||
|
||||
/// 显示关联问题数量
|
||||
Widget _buildIssueCount() {
|
||||
final pendingCount =
|
||||
flag.issues.where((i) => i.status == IssueStatus.pending).length;
|
||||
final fixingCount =
|
||||
flag.issues.where((i) => i.status == IssueStatus.fixing).length;
|
||||
final fixedCount =
|
||||
flag.issues.where((i) => i.status == IssueStatus.fixed).length;
|
||||
final pendingCount = flag.issues
|
||||
.where((i) => i.status == IssueStatus.pending)
|
||||
.length;
|
||||
final fixingCount = flag.issues
|
||||
.where((i) => i.status == IssueStatus.fixing)
|
||||
.length;
|
||||
final fixedCount = flag.issues
|
||||
.where((i) => i.status == IssueStatus.fixed)
|
||||
.length;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
@@ -917,16 +946,31 @@ class _QuestionnaireSheetState extends State<_QuestionnaireSheet> {
|
||||
void _answer(bool yes) {
|
||||
if (_step == 0) {
|
||||
// 问题1: 了解Google Play
|
||||
if (!yes) { setState(() => _step = -1); _clearDraft(); return; }
|
||||
setState(() => _step = 1); _saveDraft();
|
||||
if (!yes) {
|
||||
setState(() => _step = -1);
|
||||
_clearDraft();
|
||||
return;
|
||||
}
|
||||
setState(() => _step = 1);
|
||||
_saveDraft();
|
||||
} else if (_step == 1) {
|
||||
// 问题2: 有GMS设备
|
||||
if (!yes) { setState(() => _step = -1); _clearDraft(); return; }
|
||||
setState(() => _step = 2); _saveDraft();
|
||||
if (!yes) {
|
||||
setState(() => _step = -1);
|
||||
_clearDraft();
|
||||
return;
|
||||
}
|
||||
setState(() => _step = 2);
|
||||
_saveDraft();
|
||||
} else if (_step == 2) {
|
||||
// 问题3: 愿意参与内测
|
||||
if (!yes) { setState(() => _step = -1); _clearDraft(); return; }
|
||||
setState(() => _step = 3); _saveDraft();
|
||||
if (!yes) {
|
||||
setState(() => _step = -1);
|
||||
_clearDraft();
|
||||
return;
|
||||
}
|
||||
setState(() => _step = 3);
|
||||
_saveDraft();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -946,7 +990,10 @@ class _QuestionnaireSheetState extends State<_QuestionnaireSheet> {
|
||||
// 提交成功,保存标记并清除草稿
|
||||
await _markQuestionnaireSubmitted();
|
||||
_clearDraft();
|
||||
setState(() { _step = 4; _isSubmitting = false; });
|
||||
setState(() {
|
||||
_step = 4;
|
||||
_isSubmitting = false;
|
||||
});
|
||||
} else {
|
||||
AppToast.showError(widget.t.beta.qSubmitFailed);
|
||||
setState(() => _isSubmitting = false);
|
||||
@@ -982,7 +1029,8 @@ class _QuestionnaireSheetState extends State<_QuestionnaireSheet> {
|
||||
children: [
|
||||
// 拖拽指示器
|
||||
Container(
|
||||
width: 36, height: 5,
|
||||
width: 36,
|
||||
height: 5,
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: ext.textHint.withValues(alpha: 0.3),
|
||||
@@ -991,7 +1039,10 @@ class _QuestionnaireSheetState extends State<_QuestionnaireSheet> {
|
||||
),
|
||||
if (_step >= 0 && _step <= 3) ...[
|
||||
// 进度
|
||||
Text('${_step + 1}/4', style: AppTypography.caption1.copyWith(color: ext.textHint)),
|
||||
Text(
|
||||
'${_step + 1}/4',
|
||||
style: AppTypography.caption1.copyWith(color: ext.textHint),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// 问题
|
||||
Text(
|
||||
@@ -1008,7 +1059,13 @@ class _QuestionnaireSheetState extends State<_QuestionnaireSheet> {
|
||||
child: CupertinoButton(
|
||||
color: ext.accent,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Text(t.beta.qYes, style: TextStyle(color: ext.textOnAccent, fontWeight: FontWeight.w600)),
|
||||
child: Text(
|
||||
t.beta.qYes,
|
||||
style: TextStyle(
|
||||
color: ext.textOnAccent,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
onPressed: () => _answer(true),
|
||||
),
|
||||
),
|
||||
@@ -1017,7 +1074,10 @@ class _QuestionnaireSheetState extends State<_QuestionnaireSheet> {
|
||||
child: CupertinoButton(
|
||||
color: ext.bgElevated,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Text(t.beta.qNo, style: TextStyle(color: ext.textSecondary)),
|
||||
child: Text(
|
||||
t.beta.qNo,
|
||||
style: TextStyle(color: ext.textSecondary),
|
||||
),
|
||||
onPressed: () => _answer(false),
|
||||
),
|
||||
),
|
||||
@@ -1045,7 +1105,13 @@ class _QuestionnaireSheetState extends State<_QuestionnaireSheet> {
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: _isSubmitting
|
||||
? CupertinoActivityIndicator(color: ext.textOnAccent)
|
||||
: Text(t.beta.qSubmit, style: TextStyle(color: ext.textOnAccent, fontWeight: FontWeight.w600)),
|
||||
: Text(
|
||||
t.beta.qSubmit,
|
||||
style: TextStyle(
|
||||
color: ext.textOnAccent,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
onPressed: _isSubmitting ? null : _submitEmail,
|
||||
),
|
||||
),
|
||||
@@ -1054,39 +1120,68 @@ class _QuestionnaireSheetState extends State<_QuestionnaireSheet> {
|
||||
// 不符合条件
|
||||
Icon(CupertinoIcons.info_circle, size: 48, color: ext.textHint),
|
||||
const SizedBox(height: 12),
|
||||
Text(t.beta.qEndThankYou, style: AppTypography.headline.copyWith(color: ext.textPrimary)),
|
||||
Text(
|
||||
t.beta.qEndThankYou,
|
||||
style: AppTypography.headline.copyWith(color: ext.textPrimary),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(t.beta.qEndNotQualified, style: AppTypography.subhead.copyWith(color: ext.textSecondary), textAlign: TextAlign.center),
|
||||
Text(
|
||||
t.beta.qEndNotQualified,
|
||||
style: AppTypography.subhead.copyWith(color: ext.textSecondary),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: CupertinoButton(
|
||||
color: ext.bgElevated,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Text(t.beta.close, style: TextStyle(color: ext.textSecondary)),
|
||||
onPressed: () {
|
||||
child: Text(
|
||||
t.beta.close,
|
||||
style: TextStyle(color: ext.textSecondary),
|
||||
),
|
||||
onPressed: () async {
|
||||
// 不符合条件关闭时也保存标记并清除草稿
|
||||
_markQuestionnaireSubmitted();
|
||||
// 使用 await 确保写入完成后再关闭,避免跨平台时序问题
|
||||
final navigator = Navigator.of(context);
|
||||
await _markQuestionnaireSubmitted();
|
||||
_clearDraft();
|
||||
Navigator.pop(context);
|
||||
if (mounted) navigator.pop(true);
|
||||
},
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
// 完成
|
||||
Icon(CupertinoIcons.checkmark_circle_fill, size: 48, color: ext.successColor),
|
||||
Icon(
|
||||
CupertinoIcons.checkmark_circle_fill,
|
||||
size: 48,
|
||||
color: ext.successColor,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(t.beta.qSubmitSuccess, style: AppTypography.headline.copyWith(color: ext.textPrimary)),
|
||||
Text(
|
||||
t.beta.qSubmitSuccess,
|
||||
style: AppTypography.headline.copyWith(color: ext.textPrimary),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(t.beta.qEndThanks, style: AppTypography.subhead.copyWith(color: ext.textSecondary), textAlign: TextAlign.center),
|
||||
Text(
|
||||
t.beta.qEndThanks,
|
||||
style: AppTypography.subhead.copyWith(color: ext.textSecondary),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: CupertinoButton(
|
||||
color: ext.accent,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Text(t.beta.gotIt, style: TextStyle(color: ext.textOnAccent, fontWeight: FontWeight.w600)),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
child: Text(
|
||||
t.beta.gotIt,
|
||||
style: TextStyle(
|
||||
color: ext.textOnAccent,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1367,6 +1367,7 @@ const ar = T(
|
||||
rateDialogContent2: 'دعمكم هو حافزنا',
|
||||
laterButton: 'لاحقاً',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: 'متجر التطبيقات',
|
||||
goRate: 'تقييم',
|
||||
emailHint1: 'إذا لم يكن هناك رد خلال 24 ساعة، جرب بريداً آخر',
|
||||
emailHint2: 'أي بريد إلكتروني مناسب للتواصل',
|
||||
|
||||
@@ -1376,6 +1376,7 @@ const bn = T(
|
||||
rateDialogContent2: 'আপনার সমর্থন আমাদের অনুপ্রেরণা',
|
||||
laterButton: 'পরে',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: 'অ্যাপ স্টোর',
|
||||
goRate: 'মূল্যায়ন',
|
||||
emailHint1: '২৪ ঘন্টায় কোনো উত্তর না পেলে অন্য ইমেইল চেষ্টা করুন',
|
||||
emailHint2: 'যেকোনো ইমেইল দিয়ে যোগাযোগ করুন',
|
||||
|
||||
@@ -1375,6 +1375,7 @@ const de = T(
|
||||
rateDialogContent2: 'Ihre Unterstützung ist unsere Motivation',
|
||||
laterButton: 'Später',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: 'App Store',
|
||||
goRate: 'Bewerten',
|
||||
emailHint1: 'Wenn innerhalb von 24h keine Antwort, andere E-Mail versuchen',
|
||||
emailHint2: 'Jede E-Mail ist für die Kontaktaufnahme geeignet',
|
||||
|
||||
@@ -1386,6 +1386,7 @@ const en = T(
|
||||
rateDialogContent2: 'Your support is our motivation',
|
||||
laterButton: 'Later',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: 'App Store',
|
||||
goRate: 'Rate',
|
||||
emailHint1: 'If no reply within 24h, try another email',
|
||||
emailHint2: 'Any email is fine to contact',
|
||||
|
||||
@@ -1385,6 +1385,7 @@ const es = T(
|
||||
rateDialogContent2: 'Tu apoyo es nuestra motivación',
|
||||
laterButton: 'Más tarde',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: 'Tienda de aplicaciones',
|
||||
goRate: 'Valorar',
|
||||
emailHint1: 'Sin respuesta en 24h, prueba otro correo',
|
||||
emailHint2: 'Cualquier correo es válido para contactar',
|
||||
|
||||
@@ -1391,6 +1391,7 @@ const fr = T(
|
||||
rateDialogContent2: 'Votre soutien est notre motivation',
|
||||
laterButton: 'Plus tard',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: 'App Store',
|
||||
goRate: 'Évaluer',
|
||||
emailHint1: 'Sans réponse sous 24h, essayez un autre e-mail',
|
||||
emailHint2: 'Tout e-mail est valable pour nous contacter',
|
||||
|
||||
@@ -1359,6 +1359,7 @@ const hi = T(
|
||||
rateDialogContent2: 'आपका समर्थन हमारी प्रेरणा है',
|
||||
laterButton: 'बाद में',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: 'ऐप स्टोर',
|
||||
goRate: 'रेट करें',
|
||||
emailHint1: '24 घंटे में कोई जवाब नहीं, दूसरा ईमेल आज़माएं',
|
||||
emailHint2: 'कोई भी ईमेल संपर्क के लिए ठीक है',
|
||||
|
||||
@@ -1384,6 +1384,7 @@ const it = T(
|
||||
rateDialogContent2: 'Il tuo supporto è la nostra motivazione',
|
||||
laterButton: 'Più tardi',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: 'App Store',
|
||||
goRate: 'Valuta',
|
||||
emailHint1: 'Se nessuna risposta entro 24h, prova un\'altra email',
|
||||
emailHint2: 'Qualsiasi email va bene per contattarci',
|
||||
|
||||
@@ -1337,6 +1337,7 @@ const ja = T(
|
||||
rateDialogContent2: 'あなたのサポートが私たちの原動力です',
|
||||
laterButton: '後で',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: 'アプリストア',
|
||||
goRate: '評価する',
|
||||
emailHint1: '24時間以内に返信がない場合は別のメールをお試しください',
|
||||
emailHint2: 'どのメールでもご連絡いただけます',
|
||||
|
||||
@@ -1339,6 +1339,7 @@ const ko = T(
|
||||
rateDialogContent2: '여러분의 지원이 우리의 원동력입니다',
|
||||
laterButton: '나중에',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: '앱 스토어',
|
||||
goRate: '평가하기',
|
||||
emailHint1: '24시간 내 답장이 없으면 다른 이메일을 시도하세요',
|
||||
emailHint2: '어떤 이메일로든 연락 가능합니다',
|
||||
|
||||
@@ -1382,6 +1382,7 @@ const pt = T(
|
||||
rateDialogContent2: 'Seu apoio é nossa motivação',
|
||||
laterButton: 'Mais tarde',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: 'Loja de aplicativos',
|
||||
goRate: 'Avaliar',
|
||||
emailHint1: 'Sem resposta em 24h, tente outro e-mail',
|
||||
emailHint2: 'Qualquer e-mail serve para contato',
|
||||
|
||||
@@ -1378,6 +1378,7 @@ const ru = T(
|
||||
rateDialogContent2: 'Ваша поддержка — наша мотивация',
|
||||
laterButton: 'Позже',
|
||||
huaweiStore: 'Huawei AppGallery',
|
||||
appStore: 'Магазин приложений',
|
||||
goRate: 'Оценить',
|
||||
emailHint1: 'Если нет ответа 24ч, попробуйте другой email',
|
||||
emailHint2: 'Любой email подходит для связи',
|
||||
|
||||
@@ -1318,6 +1318,7 @@ const zhCN = T(
|
||||
rateDialogContent2: '您的支持是我们前进的动力',
|
||||
laterButton: '下次再说',
|
||||
huaweiStore: '华为应用市场',
|
||||
appStore: '应用商店',
|
||||
goRate: '去评价',
|
||||
emailHint1: '若超24小时无回复可更换其他邮箱',
|
||||
emailHint2: '任意邮箱均可联系',
|
||||
|
||||
@@ -1317,6 +1317,7 @@ const zhTW = T(
|
||||
rateDialogContent2: '您的支持是我們前進的動力',
|
||||
laterButton: '下次再說',
|
||||
huaweiStore: '華為應用市場',
|
||||
appStore: '應用商店',
|
||||
goRate: '去評價',
|
||||
emailHint1: '若超24小時無回覆可更換其他信箱',
|
||||
emailHint2: '任意信箱均可聯繫',
|
||||
|
||||
@@ -124,6 +124,7 @@ class TAbout {
|
||||
required this.rateDialogContent2,
|
||||
required this.laterButton,
|
||||
required this.huaweiStore,
|
||||
required this.appStore,
|
||||
required this.goRate,
|
||||
required this.emailHint1,
|
||||
required this.emailHint2,
|
||||
@@ -526,6 +527,9 @@ class TAbout {
|
||||
/// 华为应用市场
|
||||
final String huaweiStore;
|
||||
|
||||
/// 应用商店(通用名称,用于Android端评分跳转弹窗)
|
||||
final String appStore;
|
||||
|
||||
/// 去评价
|
||||
final String goRate;
|
||||
|
||||
@@ -717,6 +721,7 @@ class TAbout {
|
||||
'rateDialogContent2': rateDialogContent2,
|
||||
'laterButton': laterButton,
|
||||
'huaweiStore': huaweiStore,
|
||||
'appStore': appStore,
|
||||
'goRate': goRate,
|
||||
'emailHint1': emailHint1,
|
||||
'emailHint2': emailHint2,
|
||||
@@ -1090,6 +1095,9 @@ class TAbout {
|
||||
huaweiStore: map['huaweiStore']?.isNotEmpty == true
|
||||
? map['huaweiStore']!
|
||||
: (fallback?.huaweiStore ?? ''),
|
||||
appStore: map['appStore']?.isNotEmpty == true
|
||||
? map['appStore']!
|
||||
: (fallback?.appStore ?? ''),
|
||||
goRate: map['goRate']?.isNotEmpty == true
|
||||
? map['goRate']!
|
||||
: (fallback?.goRate ?? ''),
|
||||
|
||||
@@ -17,7 +17,7 @@ import flutter_app_group_directory
|
||||
import flutter_image_compress_macos
|
||||
import flutter_inappwebview_macos
|
||||
import flutter_local_notifications
|
||||
import flutter_secure_storage_darwin
|
||||
import flutter_secure_storage_macos
|
||||
import flutter_tts
|
||||
import flutter_webrtc
|
||||
import gal
|
||||
@@ -54,7 +54,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FlutterImageCompressMacosPlugin.register(with: registry.registrar(forPlugin: "FlutterImageCompressMacosPlugin"))
|
||||
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||
FlutterLocalNotificationsPlugin.register(with: registry.registrar(forPlugin: "FlutterLocalNotificationsPlugin"))
|
||||
FlutterSecureStorageDarwinPlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStorageDarwinPlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
FlutterTtsPlugin.register(with: registry.registrar(forPlugin: "FlutterTtsPlugin"))
|
||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
||||
|
||||
Reference in New Issue
Block a user