feat: replace json tools with local data controls
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
@@ -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 => '今日进展';
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user