feat: replace json tools with local data controls

This commit is contained in:
2026-06-12 01:11:50 +08:00
parent b88b947e66
commit b287a56731
10 changed files with 415 additions and 254 deletions
+31 -14
View File
@@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
@@ -168,22 +166,41 @@ class FocusSeedController extends ChangeNotifier {
return _commit(current.copyWith(reflections: reflections));
}
Future<bool> importState(String raw) async {
try {
final decoded = jsonDecode(raw);
if (decoded is! Map<String, dynamic>) {
return false;
}
return _commit(AppState.fromJson(decoded));
} on Object {
return false;
}
}
Future<bool> clearData() async {
return _commit(AppState.starter(defaultHabitTitles: _defaultHabitTitles));
}
Future<bool> clearToday() async {
final current = _state;
if (current == null) {
return false;
}
final today = todayKey();
final habits = current.habits
.map(
(habit) => habit.copyWith(
completedDates: Set<String>.from(habit.completedDates)
..remove(today),
),
)
.toList(growable: false);
final sessions = current.sessions
.where((session) => dateKey(session.completedAt) != today)
.toList(growable: false);
final reflections = current.reflections
.where((reflection) => reflection.dateKey != today)
.toList(growable: false);
_analytics.track('today_cleared');
return _commit(
current.copyWith(
anchor: current.anchorDateKey == today ? '' : current.anchor,
habits: habits,
sessions: sessions,
reflections: reflections,
),
);
}
Future<bool> _commit(AppState nextState) async {
final previousState = _state;
_state = nextState;
+107 -23
View File
@@ -113,35 +113,29 @@ class MeScreen extends StatelessWidget {
],
const SizedBox(height: 18),
Text(
l10n.yourData,
l10n.localData,
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
_DataSnapshotCard(
focusSessions: state.sessions.length,
habits: state.habits.length,
reflections: state.reflections.length,
),
const SizedBox(height: 12),
SurfacePanel(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: Column(
children: [
SettingsTile(
icon: Icons.ios_share_outlined,
title: l10n.exportJson,
subtitle: l10n.exportJsonSubtitle,
onTap: () => showExportDialog(context, state),
),
const Divider(),
SettingsTile(
icon: Icons.file_upload_outlined,
title: l10n.importJson,
subtitle: l10n.importJsonSubtitle,
icon: Icons.cleaning_services_outlined,
title: l10n.clearToday,
subtitle: l10n.clearTodaySubtitle,
onTap:
() => showImportDialog(context, (raw) async {
final imported = await controller.importState(raw);
if (context.mounted) {
showSnack(
context,
imported
? l10n.dataImported
: l10n.importFailed,
);
() => confirmClearToday(context, () async {
final cleared = await controller.clearToday();
if (context.mounted && cleared) {
showSnack(context, l10n.todayDataCleared);
}
}),
),
@@ -159,9 +153,9 @@ class MeScreen extends StatelessWidget {
SurfacePanel(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: SettingsTile(
icon: Icons.delete_outline,
title: l10n.resetLocalGarden,
subtitle: l10n.resetLocalGardenSubtitle,
icon: Icons.delete_forever_outlined,
title: l10n.deleteAllLocalData,
subtitle: l10n.deleteAllLocalDataSubtitle,
destructive: true,
onTap:
() => confirmClearData(context, () async {
@@ -188,6 +182,96 @@ class MeScreen extends StatelessWidget {
}
}
class _DataSnapshotCard extends StatelessWidget {
const _DataSnapshotCard({
required this.focusSessions,
required this.habits,
required this.reflections,
});
final int focusSessions;
final int habits;
final int reflections;
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return SurfacePanel(
elevated: true,
child: Row(
children: [
Expanded(
child: _DataMetric(
icon: Icons.timer_outlined,
label: l10n.focusDataLabel,
value: focusSessions,
),
),
Expanded(
child: _DataMetric(
icon: Icons.checklist_rounded,
label: l10n.habitsDataLabel,
value: habits,
),
),
Expanded(
child: _DataMetric(
icon: Icons.edit_note_rounded,
label: l10n.notesDataLabel,
value: reflections,
),
),
],
),
);
}
}
class _DataMetric extends StatelessWidget {
const _DataMetric({
required this.icon,
required this.label,
required this.value,
});
final IconData icon;
final String label;
final int value;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scheme = theme.colorScheme;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, color: scheme.primary),
const SizedBox(height: 8),
TweenAnimationBuilder<double>(
tween: Tween(end: value.toDouble()),
duration: const Duration(milliseconds: 260),
curve: Curves.easeOutCubic,
builder:
(context, animatedValue, _) => Text(
animatedValue.round().toString(),
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w800,
),
),
),
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(
color: scheme.onSurfaceVariant,
),
),
],
);
}
}
class _SectionHeader extends StatelessWidget {
const _SectionHeader({required this.title, required this.detail});
+16 -16
View File
@@ -114,19 +114,17 @@
"intentionHint": "What matters most today?",
"deleteHabitTitle": "Delete habit?",
"deleteHabitBody": "Past completion records for this habit will be removed.",
"exportJson": "Export JSON",
"close": "Close",
"copy": "Copy",
"exportCopied": "Export copied",
"importJson": "Import JSON",
"importHint": "Paste Focus Seed JSON export",
"import": "Import",
"clearDataTitle": "Clear local data?",
"clearDataBody": "This resets habits, focus sessions, and reflections on this device.",
"clearTodayDataTitle": "Clear today?",
"clearTodayDataBody": "This removes today's intention, habit checks, focus sessions, and reflection. Past days stay untouched.",
"clearAllDataTitle": "Delete all local data?",
"clearAllDataBody": "This deletes intentions, habits, focus sessions, and reflections on this device. It cannot be undone.",
"clear": "Clear",
"deleteAll": "Delete all",
"privacyTitle": "Privacy",
"privacyBodyOne": "Focus Seed stores your intention, habits, focus sessions, reflections, and settings locally on this device. This version has no sign-in, cloud sync, ads, analytics SDK, or billing SDK.",
"privacyBodyTwo": "You can export, import, or clear local data from Me.",
"privacyBodyTwo": "You can clear today's progress or delete all local data from Me at any time.",
"reflectionEmptyError": "Write one honest sentence.",
"reflectionSaveError": "Could not save. Please try again.",
"reflectionSheetTitle": "How did it go?",
@@ -149,15 +147,17 @@
"languageDefault": "Default",
"languageEnglish": "English",
"languageChinese": "Chinese",
"yourData": "Your data",
"exportJsonSubtitle": "Copy a readable local backup.",
"importJsonSubtitle": "Restore a previous Focus Seed export.",
"localData": "Local data",
"focusDataLabel": "Focus",
"habitsDataLabel": "Habits",
"notesDataLabel": "Notes",
"clearToday": "Clear today",
"clearTodaySubtitle": "Remove only today's progress.",
"privacySubtitle": "Everything stays on this device.",
"resetLocalGarden": "Reset local garden",
"resetLocalGardenSubtitle": "Clear intentions, habits, sessions, and notes.",
"dataImported": "Data imported",
"importFailed": "Import failed",
"localDataCleared": "Local data cleared",
"deleteAllLocalData": "Delete all local data",
"deleteAllLocalDataSubtitle": "Reset Focus Seed on this device.",
"todayDataCleared": "Today's data cleared",
"localDataCleared": "Local data deleted",
"journeyTitle": "Today's journey",
"recentFocus": "Recent focus",
"sessionsTotal": "{count} sessions total",
+16 -16
View File
@@ -114,19 +114,17 @@
"intentionHint": "\u4eca\u5929\u6700\u91cd\u8981\u7684\u662f\u4ec0\u4e48\uff1f",
"deleteHabitTitle": "\u5220\u9664\u4e60\u60ef\uff1f",
"deleteHabitBody": "\u8fd9\u4e2a\u4e60\u60ef\u7684\u5386\u53f2\u5b8c\u6210\u8bb0\u5f55\u4e5f\u4f1a\u5220\u9664\u3002",
"exportJson": "\u5bfc\u51fa JSON",
"close": "\u5173\u95ed",
"copy": "\u590d\u5236",
"exportCopied": "\u5df2\u590d\u5236\u5bfc\u51fa\u5185\u5bb9",
"importJson": "\u5bfc\u5165 JSON",
"importHint": "\u7c98\u8d34 Focus Seed \u7684 JSON \u5bfc\u51fa\u5185\u5bb9",
"import": "\u5bfc\u5165",
"clearDataTitle": "\u6e05\u9664\u672c\u5730\u6570\u636e\uff1f",
"clearDataBody": "\u4f1a\u6e05\u9664\u672c\u673a\u7684\u76ee\u6807\u3001\u4e60\u60ef\u3001\u4e13\u6ce8\u8bb0\u5f55\u548c\u590d\u76d8\u3002",
"clearTodayDataTitle": "清理今日数据?",
"clearTodayDataBody": "只清除今天的目标、打卡、专注记录和复盘,过去日期的数据会保留。",
"clearAllDataTitle": "删除全部本地数据?",
"clearAllDataBody": "会删除本机的目标、习惯、专注记录和复盘,无法撤销。",
"clear": "\u6e05\u9664",
"deleteAll": "全部删除",
"privacyTitle": "\u9690\u79c1",
"privacyBodyOne": "Focus Seed \u53ea\u5728\u672c\u673a\u4fdd\u5b58\u76ee\u6807\u3001\u4e60\u60ef\u3001\u4e13\u6ce8\u8bb0\u5f55\u3001\u590d\u76d8\u548c\u8bbe\u7f6e\u3002\u5f53\u524d\u7248\u672c\u6ca1\u6709\u767b\u5f55\u3001\u4e91\u540c\u6b65\u3001\u5e7f\u544a\u3001\u5206\u6790 SDK \u6216\u4ed8\u8d39 SDK\u3002",
"privacyBodyTwo": "\u4f60\u53ef\u4ee5\u5728\u201c\u6211\u7684\u7a7a\u95f4\u201d\u5bfc\u51fa\u3001\u5bfc\u5165\u6216\u6e05\u9664\u672c\u5730\u6570\u636e\u3002",
"privacyBodyTwo": "你可以随时清理今日进度,或删除本机的全部本地数据。",
"reflectionEmptyError": "\u5199\u4e00\u53e5\u771f\u5b9e\u7684\u603b\u7ed3\u3002",
"reflectionSaveError": "\u4fdd\u5b58\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5\u3002",
"reflectionSheetTitle": "\u590d\u76d8\u4e00\u4e0b",
@@ -149,15 +147,17 @@
"languageDefault": "\u9ed8\u8ba4",
"languageEnglish": "English",
"languageChinese": "\u4e2d\u6587",
"yourData": "\u6570\u636e",
"exportJsonSubtitle": "\u590d\u5236\u4e00\u4efd\u672c\u5730\u5907\u4efd\u3002",
"importJsonSubtitle": "\u6062\u590d\u4e4b\u524d\u5bfc\u51fa\u7684\u6570\u636e\u3002",
"localData": "本地数据",
"focusDataLabel": "专注",
"habitsDataLabel": "习惯",
"notesDataLabel": "记录",
"clearToday": "清理今日",
"clearTodaySubtitle": "只移除今天的进度。",
"privacySubtitle": "\u6570\u636e\u53ea\u4fdd\u5b58\u5728\u672c\u673a\u3002",
"resetLocalGarden": "\u6e05\u9664\u672c\u5730\u6570\u636e",
"resetLocalGardenSubtitle": "\u6e05\u9664\u76ee\u6807\u3001\u4e60\u60ef\u3001\u8bb0\u5f55\u548c\u590d\u76d8\u3002",
"dataImported": "\u5df2\u5bfc\u5165\u6570\u636e",
"importFailed": "\u5bfc\u5165\u5931\u8d25",
"localDataCleared": "\u672c\u5730\u6570\u636e\u5df2\u6e05\u9664",
"deleteAllLocalData": "删除全部本地数据",
"deleteAllLocalDataSubtitle": "重置这台设备上的 Focus Seed。",
"todayDataCleared": "今日数据已清理",
"localDataCleared": "本地数据已删除",
"journeyTitle": "\u4eca\u65e5\u8fdb\u5c55",
"recentFocus": "\u6700\u8fd1\u4e13\u6ce8",
"sessionsTotal": "\u5171 {count} \u6b21",
+77 -72
View File
@@ -62,7 +62,8 @@ import 'app_localizations_zh.dart';
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
/// property.
abstract class AppLocalizations {
AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString());
AppLocalizations(String locale)
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
final String localeName;
@@ -70,7 +71,8 @@ abstract class AppLocalizations {
return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
}
static const LocalizationsDelegate<AppLocalizations> delegate = _AppLocalizationsDelegate();
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
/// A list of this localizations delegate along with the default localizations
/// delegates.
@@ -82,17 +84,18 @@ abstract class AppLocalizations {
/// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
Locale('en'),
Locale('zh')
Locale('zh'),
];
/// No description provided for @appTitle.
@@ -491,12 +494,6 @@ abstract class AppLocalizations {
/// **'Past completion records for this habit will be removed.'**
String get deleteHabitBody;
/// No description provided for @exportJson.
///
/// In en, this message translates to:
/// **'Export JSON'**
String get exportJson;
/// No description provided for @close.
///
/// In en, this message translates to:
@@ -509,41 +506,29 @@ abstract class AppLocalizations {
/// **'Copy'**
String get copy;
/// No description provided for @exportCopied.
/// No description provided for @clearTodayDataTitle.
///
/// In en, this message translates to:
/// **'Export copied'**
String get exportCopied;
/// **'Clear today?'**
String get clearTodayDataTitle;
/// No description provided for @importJson.
/// No description provided for @clearTodayDataBody.
///
/// In en, this message translates to:
/// **'Import JSON'**
String get importJson;
/// **'This removes today\'s intention, habit checks, focus sessions, and reflection. Past days stay untouched.'**
String get clearTodayDataBody;
/// No description provided for @importHint.
/// No description provided for @clearAllDataTitle.
///
/// In en, this message translates to:
/// **'Paste Focus Seed JSON export'**
String get importHint;
/// **'Delete all local data?'**
String get clearAllDataTitle;
/// No description provided for @import.
/// No description provided for @clearAllDataBody.
///
/// In en, this message translates to:
/// **'Import'**
String get import;
/// No description provided for @clearDataTitle.
///
/// In en, this message translates to:
/// **'Clear local data?'**
String get clearDataTitle;
/// No description provided for @clearDataBody.
///
/// In en, this message translates to:
/// **'This resets habits, focus sessions, and reflections on this device.'**
String get clearDataBody;
/// **'This deletes intentions, habits, focus sessions, and reflections on this device. It cannot be undone.'**
String get clearAllDataBody;
/// No description provided for @clear.
///
@@ -551,6 +536,12 @@ abstract class AppLocalizations {
/// **'Clear'**
String get clear;
/// No description provided for @deleteAll.
///
/// In en, this message translates to:
/// **'Delete all'**
String get deleteAll;
/// No description provided for @privacyTitle.
///
/// In en, this message translates to:
@@ -566,7 +557,7 @@ abstract class AppLocalizations {
/// No description provided for @privacyBodyTwo.
///
/// In en, this message translates to:
/// **'You can export, import, or clear local data from Me.'**
/// **'You can clear today\'s progress or delete all local data from Me at any time.'**
String get privacyBodyTwo;
/// No description provided for @reflectionEmptyError.
@@ -701,23 +692,41 @@ abstract class AppLocalizations {
/// **'Chinese'**
String get languageChinese;
/// No description provided for @yourData.
/// No description provided for @localData.
///
/// In en, this message translates to:
/// **'Your data'**
String get yourData;
/// **'Local data'**
String get localData;
/// No description provided for @exportJsonSubtitle.
/// No description provided for @focusDataLabel.
///
/// In en, this message translates to:
/// **'Copy a readable local backup.'**
String get exportJsonSubtitle;
/// **'Focus'**
String get focusDataLabel;
/// No description provided for @importJsonSubtitle.
/// No description provided for @habitsDataLabel.
///
/// In en, this message translates to:
/// **'Restore a previous Focus Seed export.'**
String get importJsonSubtitle;
/// **'Habits'**
String get habitsDataLabel;
/// No description provided for @notesDataLabel.
///
/// In en, this message translates to:
/// **'Notes'**
String get notesDataLabel;
/// No description provided for @clearToday.
///
/// In en, this message translates to:
/// **'Clear today'**
String get clearToday;
/// No description provided for @clearTodaySubtitle.
///
/// In en, this message translates to:
/// **'Remove only today\'s progress.'**
String get clearTodaySubtitle;
/// No description provided for @privacySubtitle.
///
@@ -725,34 +734,28 @@ abstract class AppLocalizations {
/// **'Everything stays on this device.'**
String get privacySubtitle;
/// No description provided for @resetLocalGarden.
/// No description provided for @deleteAllLocalData.
///
/// In en, this message translates to:
/// **'Reset local garden'**
String get resetLocalGarden;
/// **'Delete all local data'**
String get deleteAllLocalData;
/// No description provided for @resetLocalGardenSubtitle.
/// No description provided for @deleteAllLocalDataSubtitle.
///
/// In en, this message translates to:
/// **'Clear intentions, habits, sessions, and notes.'**
String get resetLocalGardenSubtitle;
/// **'Reset Focus Seed on this device.'**
String get deleteAllLocalDataSubtitle;
/// No description provided for @dataImported.
/// No description provided for @todayDataCleared.
///
/// In en, this message translates to:
/// **'Data imported'**
String get dataImported;
/// No description provided for @importFailed.
///
/// In en, this message translates to:
/// **'Import failed'**
String get importFailed;
/// **'Today\'s data cleared'**
String get todayDataCleared;
/// No description provided for @localDataCleared.
///
/// In en, this message translates to:
/// **'Local data cleared'**
/// **'Local data deleted'**
String get localDataCleared;
/// No description provided for @journeyTitle.
@@ -924,7 +927,8 @@ abstract class AppLocalizations {
String get defaultHabitReflect;
}
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
@@ -933,25 +937,26 @@ class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations>
}
@override
bool isSupported(Locale locale) => <String>['en', 'zh'].contains(locale.languageCode);
bool isSupported(Locale locale) =>
<String>['en', 'zh'].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'en': return AppLocalizationsEn();
case 'zh': return AppLocalizationsZh();
case 'en':
return AppLocalizationsEn();
case 'zh':
return AppLocalizationsZh();
}
throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.'
'that was used.',
);
}
@@ -216,10 +216,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get deleteHabitTitle => 'Delete habit?';
@override
String get deleteHabitBody => 'Past completion records for this habit will be removed.';
@override
String get exportJson => 'Export JSON';
String get deleteHabitBody =>
'Past completion records for this habit will be removed.';
@override
String get close => 'Close';
@@ -228,34 +226,35 @@ class AppLocalizationsEn extends AppLocalizations {
String get copy => 'Copy';
@override
String get exportCopied => 'Export copied';
String get clearTodayDataTitle => 'Clear today?';
@override
String get importJson => 'Import JSON';
String get clearTodayDataBody =>
'This removes today\'s intention, habit checks, focus sessions, and reflection. Past days stay untouched.';
@override
String get importHint => 'Paste Focus Seed JSON export';
String get clearAllDataTitle => 'Delete all local data?';
@override
String get import => 'Import';
@override
String get clearDataTitle => 'Clear local data?';
@override
String get clearDataBody => 'This resets habits, focus sessions, and reflections on this device.';
String get clearAllDataBody =>
'This deletes intentions, habits, focus sessions, and reflections on this device. It cannot be undone.';
@override
String get clear => 'Clear';
@override
String get deleteAll => 'Delete all';
@override
String get privacyTitle => 'Privacy';
@override
String get privacyBodyOne => 'Focus Seed stores your intention, habits, focus sessions, reflections, and settings locally on this device. This version has no sign-in, cloud sync, ads, analytics SDK, or billing SDK.';
String get privacyBodyOne =>
'Focus Seed stores your intention, habits, focus sessions, reflections, and settings locally on this device. This version has no sign-in, cloud sync, ads, analytics SDK, or billing SDK.';
@override
String get privacyBodyTwo => 'You can export, import, or clear local data from Me.';
String get privacyBodyTwo =>
'You can clear today\'s progress or delete all local data from Me at any time.';
@override
String get reflectionEmptyError => 'Write one honest sentence.';
@@ -324,31 +323,37 @@ class AppLocalizationsEn extends AppLocalizations {
String get languageChinese => 'Chinese';
@override
String get yourData => 'Your data';
String get localData => 'Local data';
@override
String get exportJsonSubtitle => 'Copy a readable local backup.';
String get focusDataLabel => 'Focus';
@override
String get importJsonSubtitle => 'Restore a previous Focus Seed export.';
String get habitsDataLabel => 'Habits';
@override
String get notesDataLabel => 'Notes';
@override
String get clearToday => 'Clear today';
@override
String get clearTodaySubtitle => 'Remove only today\'s progress.';
@override
String get privacySubtitle => 'Everything stays on this device.';
@override
String get resetLocalGarden => 'Reset local garden';
String get deleteAllLocalData => 'Delete all local data';
@override
String get resetLocalGardenSubtitle => 'Clear intentions, habits, sessions, and notes.';
String get deleteAllLocalDataSubtitle => 'Reset Focus Seed on this device.';
@override
String get dataImported => 'Data imported';
String get todayDataCleared => 'Today\'s data cleared';
@override
String get importFailed => 'Import failed';
@override
String get localDataCleared => 'Local data cleared';
String get localDataCleared => 'Local data deleted';
@override
String get journeyTitle => 'Today\'s journey';
@@ -404,7 +409,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get sevenDayGrowth => '7-day growth';
@override
String get sevenDayGrowthSubtitle => 'Small actions become visible over time.';
String get sevenDayGrowthSubtitle =>
'Small actions become visible over time.';
@override
String get growthSeed => 'seed';
@@ -430,7 +436,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get growthSproutSemantic => 'A sprout growing from today\'s intention';
@override
String get growthLeavesSemantic => 'A young plant growing with today\'s progress';
String get growthLeavesSemantic =>
'A young plant growing with today\'s progress';
@override
String get growthFlowerSemantic => 'A flower marking a complete daily cycle';
@@ -218,9 +218,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get deleteHabitBody => '这个习惯的历史完成记录也会删除。';
@override
String get exportJson => '导出 JSON';
@override
String get close => '关闭';
@@ -228,34 +225,32 @@ class AppLocalizationsZh extends AppLocalizations {
String get copy => '复制';
@override
String get exportCopied => '已复制导出内容';
String get clearTodayDataTitle => '清理今日数据?';
@override
String get importJson => '导入 JSON';
String get clearTodayDataBody => '只清除今天的目标、打卡、专注记录和复盘,过去日期的数据会保留。';
@override
String get importHint => '粘贴 Focus Seed 的 JSON 导出内容';
String get clearAllDataTitle => '删除全部本地数据?';
@override
String get import => '导入';
@override
String get clearDataTitle => '清除本地数据?';
@override
String get clearDataBody => '会清除本机的目标、习惯、专注记录和复盘。';
String get clearAllDataBody => '会删除本机的目标、习惯、专注记录和复盘,无法撤销。';
@override
String get clear => '清除';
@override
String get deleteAll => '全部删除';
@override
String get privacyTitle => '隐私';
@override
String get privacyBodyOne => 'Focus Seed 只在本机保存目标、习惯、专注记录、复盘和设置。当前版本没有登录、云同步、广告、分析 SDK 或付费 SDK。';
String get privacyBodyOne =>
'Focus Seed 只在本机保存目标、习惯、专注记录、复盘和设置。当前版本没有登录、云同步、广告、分析 SDK 或付费 SDK。';
@override
String get privacyBodyTwo => '你可以在“我的空间”导出、导入或清除本地数据。';
String get privacyBodyTwo => '你可以随时清理今日进度,或删除本机的全部本地数据。';
@override
String get reflectionEmptyError => '写一句真实的总结。';
@@ -324,31 +319,37 @@ class AppLocalizationsZh extends AppLocalizations {
String get languageChinese => '中文';
@override
String get yourData => '数据';
String get localData => '本地数据';
@override
String get exportJsonSubtitle => '复制一份本地备份。';
String get focusDataLabel => '专注';
@override
String get importJsonSubtitle => '恢复之前导出的数据。';
String get habitsDataLabel => '习惯';
@override
String get notesDataLabel => '记录';
@override
String get clearToday => '清理今日';
@override
String get clearTodaySubtitle => '只移除今天的进度。';
@override
String get privacySubtitle => '数据只保存在本机。';
@override
String get resetLocalGarden => '清除本地数据';
String get deleteAllLocalData => '删除全部本地数据';
@override
String get resetLocalGardenSubtitle => '清除目标、习惯、记录和复盘。';
String get deleteAllLocalDataSubtitle => '重置这台设备上的 Focus Seed。';
@override
String get dataImported => '已导入数据';
String get todayDataCleared => '今日数据已清理';
@override
String get importFailed => '导入失败';
@override
String get localDataCleared => '本地数据已清除';
String get localDataCleared => '本地数据已删除';
@override
String get journeyTitle => '今日进展';
+13 -60
View File
@@ -1,9 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../core/json_utils.dart';
import '../l10n/l10n.dart';
import '../models/app_state.dart';
Future<void> showHabitDialog(
BuildContext context,
@@ -123,64 +120,16 @@ Future<void> confirmDeleteHabit(
);
}
Future<void> showExportDialog(BuildContext context, AppState state) async {
final scaffoldContext = context;
final export = prettyJson(state.toJson());
await showDialog<void>(
context: context,
builder:
(context) => AlertDialog(
title: Text(context.l10n.exportJson),
content: SizedBox(
width: double.maxFinite,
child: SingleChildScrollView(
child: SelectableText(
export,
style: const TextStyle(fontFamily: 'monospace'),
),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(context.l10n.close),
),
FilledButton.icon(
onPressed: () async {
final message = context.l10n.exportCopied;
await Clipboard.setData(ClipboardData(text: export));
if (context.mounted) {
Navigator.pop(context);
showSnack(scaffoldContext, message);
}
},
icon: const Icon(Icons.copy),
label: Text(context.l10n.copy),
),
],
),
);
}
Future<void> showImportDialog(
Future<void> confirmClearToday(
BuildContext context,
ValueChanged<String> onImport,
VoidCallback onConfirm,
) async {
var value = '';
await showDialog<void>(
context: context,
builder:
(context) => AlertDialog(
title: Text(context.l10n.importJson),
content: TextFormField(
minLines: 8,
maxLines: 12,
onChanged: (nextValue) => value = nextValue,
decoration: InputDecoration(
border: const OutlineInputBorder(),
hintText: context.l10n.importHint,
),
),
title: Text(context.l10n.clearTodayDataTitle),
content: Text(context.l10n.clearTodayDataBody),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
@@ -188,10 +137,10 @@ Future<void> showImportDialog(
),
FilledButton(
onPressed: () {
onImport(value);
onConfirm();
Navigator.pop(context);
},
child: Text(context.l10n.import),
child: Text(context.l10n.clearToday),
),
],
),
@@ -206,19 +155,23 @@ Future<void> confirmClearData(
context: context,
builder:
(context) => AlertDialog(
title: Text(context.l10n.clearDataTitle),
content: Text(context.l10n.clearDataBody),
title: Text(context.l10n.clearAllDataTitle),
content: Text(context.l10n.clearAllDataBody),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(context.l10n.cancel),
),
FilledButton(
style: FilledButton.styleFrom(
backgroundColor: Theme.of(context).colorScheme.error,
foregroundColor: Theme.of(context).colorScheme.onError,
),
onPressed: () {
onConfirm();
Navigator.pop(context);
},
child: Text(context.l10n.clear),
child: Text(context.l10n.deleteAll),
),
],
),