feat: polish focus seed product UI

This commit is contained in:
2026-06-12 02:15:17 +08:00
parent 33e96072c9
commit 185492395e
11 changed files with 1108 additions and 268 deletions
+72 -16
View File
@@ -6,12 +6,13 @@ import '../../l10n/l10n.dart';
import '../../models/journey_stats.dart';
import '../../models/today_progress.dart';
import '../../widgets/focus_seed_header.dart';
import '../../widgets/seed_illustration.dart';
import '../../widgets/surface_panel.dart';
import '../reflection/reflection_sheet.dart';
import 'history_screen.dart';
import 'widgets/growth_week_strip.dart';
import 'widgets/journey_overview_panel.dart';
import 'widgets/journey_stats_panel.dart';
import 'widgets/journey_timeline.dart';
class JourneyScreen extends StatelessWidget {
const JourneyScreen({required this.controller, super.key});
@@ -39,15 +40,16 @@ class JourneyScreen extends StatelessWidget {
child: ListView(
padding: const EdgeInsets.fromLTRB(20, 6, 20, 32),
children: [
JourneyTimeline(
JourneyOverviewPanel(
progress: progress,
onReflect: () => showReflectionSheet(context, controller),
),
const SizedBox(height: 12),
JourneyStatsPanel(stats: stats),
const SizedBox(height: 12),
SurfacePanel(
padding: const EdgeInsets.symmetric(horizontal: 14),
GrowthWeekStrip(state: state),
const SizedBox(height: 12),
_HistoryGateway(
onTap:
() => Navigator.of(context).push(
MaterialPageRoute<void>(
@@ -55,19 +57,7 @@ class JourneyScreen extends StatelessWidget {
(context) => HistoryScreen(controller: controller),
),
),
child: ListTile(
contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.history_rounded),
title: Text(l10n.viewHistory),
subtitle: Text(l10n.viewHistorySubtitle),
trailing: const Icon(
Icons.arrow_forward_ios_rounded,
size: 16,
),
),
),
const SizedBox(height: 12),
GrowthWeekStrip(state: state),
if (recentSessions.isNotEmpty) ...[
const SizedBox(height: 18),
_SectionTitle(
@@ -157,6 +147,72 @@ class JourneyScreen extends StatelessWidget {
}
}
class _HistoryGateway extends StatelessWidget {
const _HistoryGateway({required this.onTap});
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final theme = Theme.of(context);
final scheme = theme.colorScheme;
return SurfacePanel(
elevated: true,
padding: EdgeInsets.zero,
onTap: onTap,
child: Container(
padding: const EdgeInsets.fromLTRB(16, 14, 14, 14),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
scheme.secondary.withValues(alpha: 0.10),
scheme.surfaceContainerLowest,
],
),
),
child: Row(
children: [
const SeedIllustration(
size: 68,
tone: SeedIllustrationTone.archive,
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.viewHistory,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 3),
Text(
l10n.viewHistorySubtitle,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(
color: scheme.onSurfaceVariant,
),
),
],
),
),
const SizedBox(width: 10),
Icon(
Icons.arrow_forward_ios_rounded,
size: 17,
color: scheme.primary,
),
],
),
),
);
}
}
class _SectionTitle extends StatelessWidget {
const _SectionTitle({required this.title, required this.detail});
@@ -0,0 +1,174 @@
import 'package:flutter/material.dart';
import '../../../l10n/l10n.dart';
import '../../../models/today_progress.dart';
import '../../../widgets/seed_illustration.dart';
import '../../../widgets/surface_panel.dart';
class JourneyOverviewPanel extends StatelessWidget {
const JourneyOverviewPanel({
required this.progress,
required this.onReflect,
super.key,
});
final TodayProgress progress;
final VoidCallback onReflect;
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final scheme = Theme.of(context).colorScheme;
final completed = progress.completedSteps;
final progressValue = completed / 4;
final title = switch (completed) {
0 => l10n.journeyOverviewStartTitle,
4 => l10n.journeyOverviewDoneTitle,
_ => l10n.journeyOverviewActiveTitle,
};
final subtitle = switch (completed) {
0 => l10n.journeyOverviewStartBody,
4 => l10n.journeyOverviewDoneBody,
_ => l10n.journeyOverviewActiveBody,
};
final showReflectAction =
progress.hasIntention && progress.hasFocus && !progress.hasReflection;
return SurfacePanel(
elevated: true,
padding: EdgeInsets.zero,
color: scheme.surfaceContainerLowest,
child: Container(
padding: const EdgeInsets.fromLTRB(18, 16, 16, 18),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
scheme.primary.withValues(alpha: 0.12),
scheme.surfaceContainerLowest,
scheme.secondary.withValues(alpha: 0.07),
],
),
),
child: LayoutBuilder(
builder: (context, constraints) {
final textScale = MediaQuery.textScalerOf(context).scale(14) / 14;
final compact = constraints.maxWidth < 360 || textScale > 1.25;
final textContent = _OverviewText(
title: title,
subtitle: subtitle,
completed: completed,
progressValue: progressValue,
showReflectAction: showReflectAction,
onReflect: onReflect,
);
if (compact) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Align(
alignment: Alignment.centerRight,
child: SeedIllustration(size: 88),
),
const SizedBox(height: 8),
textContent,
],
);
}
return Row(
children: [
Expanded(child: textContent),
const SizedBox(width: 12),
const SeedIllustration(size: 112),
],
);
},
),
),
);
}
}
class _OverviewText extends StatelessWidget {
const _OverviewText({
required this.title,
required this.subtitle,
required this.completed,
required this.progressValue,
required this.showReflectAction,
required this.onReflect,
});
final String title;
final String subtitle;
final int completed;
final double progressValue;
final bool showReflectAction;
final VoidCallback onReflect;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final l10n = context.l10n;
final scheme = theme.colorScheme;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.journeyOverviewLabel,
style: theme.textTheme.labelSmall?.copyWith(
color: scheme.primary,
fontWeight: FontWeight.w800,
),
),
const SizedBox(height: 6),
Text(
title,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 6),
Text(
subtitle,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyMedium?.copyWith(
color: scheme.onSurfaceVariant,
),
),
const SizedBox(height: 14),
ClipRRect(
borderRadius: BorderRadius.circular(99),
child: LinearProgressIndicator(
minHeight: 8,
value: progressValue,
backgroundColor: scheme.surfaceContainerHigh,
),
),
const SizedBox(height: 10),
Row(
children: [
Expanded(
child: Text(
l10n.journeyOverviewProgress(completed),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.labelLarge,
),
),
if (showReflectAction)
TextButton.icon(
onPressed: onReflect,
icon: const Icon(Icons.edit_note_rounded, size: 18),
label: Text(l10n.reviewNow),
),
],
),
],
);
}
}
@@ -24,7 +24,7 @@ class JourneyStatsPanel extends StatelessWidget {
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800),
),
const SizedBox(height: 12),
const SizedBox(height: 10),
LayoutBuilder(
builder: (context, constraints) {
final columns = constraints.maxWidth > 520 ? 4 : 2;
@@ -32,9 +32,9 @@ class JourneyStatsPanel extends StatelessWidget {
crossAxisCount: columns,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
childAspectRatio: columns == 4 ? 1.35 : 1.85,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: columns == 4 ? 2.05 : 2.15,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
children: [
_StatItem(
icon: Icons.timer_outlined,
@@ -93,32 +93,47 @@ class _StatItem extends StatelessWidget {
borderRadius: BorderRadius.circular(12),
),
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 10),
child: Row(
children: [
Icon(icon, color: scheme.primary, size: 20),
TweenAnimationBuilder<double>(
tween: Tween(end: animatedTarget.toDouble()),
duration: const Duration(milliseconds: 280),
curve: Curves.easeOutCubic,
builder:
(context, animatedValue, _) => Text(
valueBuilder(animatedValue.round()),
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: scheme.surfaceContainerLowest.withValues(alpha: 0.72),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: scheme.primary, size: 19),
),
const SizedBox(width: 9),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TweenAnimationBuilder<double>(
tween: Tween(end: animatedTarget.toDouble()),
duration: const Duration(milliseconds: 280),
curve: Curves.easeOutCubic,
builder:
(context, animatedValue, _) => Text(
valueBuilder(animatedValue.round()),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w900,
),
),
),
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w900,
style: theme.textTheme.labelSmall?.copyWith(
color: scheme.onSurfaceVariant,
),
),
),
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(
color: scheme.onSurfaceVariant,
],
),
),
],
+272 -100
View File
@@ -6,7 +6,7 @@ import '../../models/appearance_preference.dart';
import '../../models/language_preference.dart';
import '../../widgets/dialogs.dart';
import '../../widgets/focus_seed_header.dart';
import '../../widgets/settings_tile.dart';
import '../../widgets/seed_illustration.dart';
import '../../widgets/surface_panel.dart';
class MeScreen extends StatelessWidget {
@@ -42,9 +42,15 @@ class MeScreen extends StatelessWidget {
child: ListView(
padding: const EdgeInsets.fromLTRB(20, 6, 20, 32),
children: [
Text(
l10n.appearance,
style: Theme.of(context).textTheme.titleMedium,
_PersonalGardenCard(
focusSessions: state.sessions.length,
habits: state.habits.length,
reflections: state.reflections.length,
),
const SizedBox(height: 18),
_SectionHeader(
title: l10n.preferencesTitle,
detail: l10n.preferencesDetail,
),
const SizedBox(height: 8),
SurfacePanel(
@@ -76,7 +82,7 @@ class MeScreen extends StatelessWidget {
),
),
if (showLanguageTools) ...[
const SizedBox(height: 18),
const SizedBox(height: 16),
_SectionHeader(
title: l10n.language,
detail: l10n.developmentOnly,
@@ -112,59 +118,47 @@ class MeScreen extends StatelessWidget {
),
],
const SizedBox(height: 18),
Text(
l10n.localData,
style: Theme.of(context).textTheme.titleMedium,
_SectionHeader(
title: l10n.localData,
detail: l10n.localDataDetail,
),
const SizedBox(height: 8),
_DataSnapshotCard(
focusSessions: state.sessions.length,
habits: state.habits.length,
reflections: state.reflections.length,
_ActionGrid(
children: [
_ActionCard(
icon: Icons.cleaning_services_outlined,
title: l10n.clearToday,
subtitle: l10n.clearTodaySubtitle,
onTap:
() => confirmClearToday(context, () async {
final cleared = await controller.clearToday();
if (context.mounted && cleared) {
showSnack(context, l10n.todayDataCleared);
}
}),
),
_ActionCard(
icon: Icons.privacy_tip_outlined,
title: l10n.privacyTitle,
subtitle: l10n.privacySubtitle,
onTap: () => showPrivacySheet(context),
),
],
),
const SizedBox(height: 12),
SurfacePanel(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: Column(
children: [
SettingsTile(
icon: Icons.cleaning_services_outlined,
title: l10n.clearToday,
subtitle: l10n.clearTodaySubtitle,
onTap:
() => confirmClearToday(context, () async {
final cleared = await controller.clearToday();
if (context.mounted && cleared) {
showSnack(context, l10n.todayDataCleared);
}
}),
),
const Divider(),
SettingsTile(
icon: Icons.privacy_tip_outlined,
title: l10n.privacyTitle,
subtitle: l10n.privacySubtitle,
onTap: () => showPrivacySheet(context),
),
],
),
),
const SizedBox(height: 12),
SurfacePanel(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: SettingsTile(
icon: Icons.delete_forever_outlined,
title: l10n.deleteAllLocalData,
subtitle: l10n.deleteAllLocalDataSubtitle,
destructive: true,
onTap:
() => confirmClearData(context, () async {
final cleared = await controller.clearData();
if (context.mounted && cleared) {
showSnack(context, l10n.localDataCleared);
}
}),
),
_ActionCard(
icon: Icons.delete_forever_outlined,
title: l10n.deleteAllLocalData,
subtitle: l10n.deleteAllLocalDataSubtitle,
destructive: true,
fullWidth: true,
onTap:
() => confirmClearData(context, () async {
final cleared = await controller.clearData();
if (context.mounted && cleared) {
showSnack(context, l10n.localDataCleared);
}
}),
),
const SizedBox(height: 18),
Text(
@@ -182,8 +176,8 @@ class MeScreen extends StatelessWidget {
}
}
class _DataSnapshotCard extends StatelessWidget {
const _DataSnapshotCard({
class _PersonalGardenCard extends StatelessWidget {
const _PersonalGardenCard({
required this.focusSessions,
required this.habits,
required this.reflections,
@@ -196,32 +190,89 @@ class _DataSnapshotCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
final theme = Theme.of(context);
final scheme = theme.colorScheme;
return SurfacePanel(
elevated: true,
child: Row(
children: [
Expanded(
child: _DataMetric(
icon: Icons.timer_outlined,
label: l10n.focusDataLabel,
value: focusSessions,
),
padding: EdgeInsets.zero,
child: Container(
padding: const EdgeInsets.fromLTRB(16, 16, 14, 16),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
scheme.primary.withValues(alpha: 0.10),
scheme.surfaceContainerLowest,
scheme.tertiary.withValues(alpha: 0.07),
],
),
Expanded(
child: _DataMetric(
icon: Icons.checklist_rounded,
label: l10n.habitsDataLabel,
value: habits,
),
child: Column(
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
l10n.personalGardenTitle,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 5),
Text(
l10n.personalGardenSubtitle,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyMedium?.copyWith(
color: scheme.onSurfaceVariant,
),
),
],
),
),
const SizedBox(width: 10),
const SeedIllustration(
size: 88,
tone: SeedIllustrationTone.data,
),
],
),
),
Expanded(
child: _DataMetric(
icon: Icons.edit_note_rounded,
label: l10n.notesDataLabel,
value: reflections,
const SizedBox(height: 14),
Row(
children: [
Expanded(
child: _DataMetric(
icon: Icons.timer_outlined,
label: l10n.focusDataLabel,
value: focusSessions,
),
),
const SizedBox(width: 8),
Expanded(
child: _DataMetric(
icon: Icons.checklist_rounded,
label: l10n.habitsDataLabel,
value: habits,
),
),
const SizedBox(width: 8),
Expanded(
child: _DataMetric(
icon: Icons.edit_note_rounded,
label: l10n.notesDataLabel,
value: reflections,
),
),
],
),
),
],
],
),
),
);
}
@@ -242,32 +293,153 @@ class _DataMetric extends StatelessWidget {
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,
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 9),
decoration: BoxDecoration(
color: scheme.surfaceContainerLowest.withValues(alpha: 0.72),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: scheme.outlineVariant.withValues(alpha: 0.6)),
),
child: Row(
children: [
Icon(icon, color: scheme.primary, size: 18),
const SizedBox(width: 7),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
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.titleMedium?.copyWith(
fontWeight: FontWeight.w900,
),
),
),
),
),
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(
color: scheme.onSurfaceVariant,
Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.labelSmall?.copyWith(
color: scheme.onSurfaceVariant,
),
),
],
),
),
],
),
);
}
}
class _ActionGrid extends StatelessWidget {
const _ActionGrid({required this.children});
final List<Widget> children;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final compact = constraints.maxWidth < 350;
if (compact) {
return Column(
children: [
for (var index = 0; index < children.length; index++) ...[
if (index > 0) const SizedBox(height: 10),
children[index],
],
],
);
}
return GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisSpacing: 10,
childAspectRatio: 1.22,
children: children,
);
},
);
}
}
class _ActionCard extends StatelessWidget {
const _ActionCard({
required this.icon,
required this.title,
required this.subtitle,
required this.onTap,
this.destructive = false,
this.fullWidth = false,
});
final IconData icon;
final String title;
final String subtitle;
final VoidCallback onTap;
final bool destructive;
final bool fullWidth;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final accent = destructive ? scheme.error : scheme.primary;
return SurfacePanel(
onTap: onTap,
padding: const EdgeInsets.all(14),
borderColor: destructive ? scheme.error.withValues(alpha: 0.38) : null,
color:
destructive
? scheme.errorContainer.withValues(alpha: 0.22)
: scheme.surfaceContainerLowest,
child: SizedBox(
width: fullWidth ? double.infinity : null,
height: fullWidth ? 108 : 118,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 38,
height: 38,
decoration: BoxDecoration(
color: accent.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: accent, size: 21),
),
const Spacer(),
Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleSmall?.copyWith(
color: destructive ? scheme.error : null,
fontWeight: FontWeight.w900,
),
),
const SizedBox(height: 3),
Text(
subtitle,
maxLines: fullWidth ? 2 : 3,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall?.copyWith(
color:
destructive
? scheme.onErrorContainer
: scheme.onSurfaceVariant,
),
),
],
),
],
),
);
}
}
+38 -17
View File
@@ -26,8 +26,8 @@
}
}
},
"todayIntentionLabel": "TODAY'S INTENTION",
"chooseOneOutcome": "Choose one outcome",
"todayIntentionLabel": "TODAY'S FOCUS",
"chooseOneOutcome": "Name today's focus",
"focusLabel": "FOCUS",
"timerReady": "Ready when you are",
"timerRunning": "Deep work in progress",
@@ -35,7 +35,7 @@
"timerCompleted": "Session complete",
"reflectOneMinute": "Reflect for 1 min",
"startAnotherSession": "Start another session",
"setTodaysIntention": "Set today's intention",
"setTodaysIntention": "Set today's focus",
"pause": "Pause",
"end": "End",
"resume": "Resume",
@@ -117,12 +117,12 @@
"close": "Close",
"copy": "Copy",
"clearTodayDataTitle": "Clear today?",
"clearTodayDataBody": "This removes today's intention, habit checks, focus sessions, and reflection. Past days stay untouched.",
"clearTodayDataBody": "This resets today's focus, 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",
"privacyTitle": "Privacy & data",
"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 clear today's progress or delete all local data from Me at any time.",
"reflectionEmptyError": "Write one honest sentence.",
@@ -136,9 +136,9 @@
"moodClear": "Clear",
"moodProud": "Proud",
"moodTired": "Tired",
"meTitle": "Your space",
"meSubtitle": "Private by default. No account required.",
"appearance": "Appearance",
"meTitle": "Your garden",
"meSubtitle": "Preferences and records stay on this device.",
"appearance": "Theme",
"system": "System",
"light": "Light",
"dark": "Dark",
@@ -147,19 +147,40 @@
"languageDefault": "Default",
"languageEnglish": "English",
"languageChinese": "Chinese",
"localData": "Local data",
"preferencesTitle": "Preferences",
"preferencesDetail": "Make it feel right",
"localData": "Local records",
"localDataDetail": "Private on device",
"personalGardenTitle": "Your focus garden",
"personalGardenSubtitle": "Every small session leaves a trace you can return to.",
"focusDataLabel": "Focus",
"habitsDataLabel": "Habits",
"notesDataLabel": "Notes",
"clearToday": "Clear today",
"clearTodaySubtitle": "Remove only today's progress.",
"privacySubtitle": "Everything stays on this device.",
"deleteAllLocalData": "Delete all local data",
"deleteAllLocalDataSubtitle": "Reset Focus Seed on this device.",
"clearTodaySubtitle": "Reset only today's loop.",
"privacySubtitle": "No account or cloud sync.",
"deleteAllLocalData": "Delete all data",
"deleteAllLocalDataSubtitle": "Remove this device's Focus Seed records.",
"todayDataCleared": "Today's data cleared",
"localDataCleared": "Local data deleted",
"journeyTitle": "Today's journey",
"journeyStatsTitle": "This week",
"journeyTitle": "Progress",
"journeyOverviewLabel": "TODAY'S LOOP",
"journeyOverviewStartTitle": "Start with one clear focus",
"journeyOverviewStartBody": "Pick the thing that would make today feel finished.",
"journeyOverviewActiveTitle": "Your day is taking shape",
"journeyOverviewActiveBody": "Keep the loop moving: focus, habits, then a short reflection.",
"journeyOverviewDoneTitle": "Loop complete",
"journeyOverviewDoneBody": "You left a clean record for future you.",
"journeyOverviewProgress": "{completed}/4 done",
"@journeyOverviewProgress": {
"placeholders": {
"completed": {
"type": "int"
}
}
},
"reviewNow": "Reflect now",
"journeyStatsTitle": "7-day rhythm",
"weekFocusLabel": "Focus",
"streakLabel": "Streak",
"activeDaysLabel": "Active",
@@ -180,8 +201,8 @@
}
}
},
"viewHistory": "View history",
"viewHistorySubtitle": "Review focus sessions and reflections.",
"viewHistory": "Record library",
"viewHistorySubtitle": "Open focus sessions and reflections.",
"historyTitle": "History",
"focusHistory": "Focus",
"notesHistory": "Notes",
+61 -40
View File
@@ -26,8 +26,8 @@
}
}
},
"todayIntentionLabel": "\u4eca\u65e5\u76ee\u6807",
"chooseOneOutcome": "\u9009\u4e00\u4e2a\u7ed3\u679c",
"todayIntentionLabel": "\u4eca\u65e5\u91cd\u70b9",
"chooseOneOutcome": "\u5199\u4e0b\u4eca\u5929\u7684\u91cd\u70b9",
"focusLabel": "\u4e13\u6ce8",
"timerReady": "\u51c6\u5907\u597d\u4e86\u5c31\u5f00\u59cb",
"timerRunning": "\u4e13\u6ce8\u4e2d",
@@ -35,7 +35,7 @@
"timerCompleted": "\u5df2\u5b8c\u6210",
"reflectOneMinute": "\u590d\u76d8 1 \u5206\u949f",
"startAnotherSession": "\u518d\u6765\u4e00\u8f6e",
"setTodaysIntention": "\u8bbe\u7f6e\u4eca\u65e5\u76ee\u6807",
"setTodaysIntention": "\u8bbe\u7f6e\u4eca\u65e5\u91cd\u70b9",
"pause": "\u6682\u505c",
"end": "\u7ed3\u675f",
"resume": "\u7ee7\u7eed",
@@ -116,15 +116,15 @@
"deleteHabitBody": "\u8fd9\u4e2a\u4e60\u60ef\u7684\u5386\u53f2\u5b8c\u6210\u8bb0\u5f55\u4e5f\u4f1a\u5220\u9664\u3002",
"close": "\u5173\u95ed",
"copy": "\u590d\u5236",
"clearTodayDataTitle": "清理今日数据?",
"clearTodayDataBody": "只清除今天的目标、打卡、专注记录和复盘,过去日期的数据会保留。",
"clearAllDataTitle": "删除全部本地数据?",
"clearAllDataBody": "会删除本机的目标、习惯、专注记录和复盘,无法撤销。",
"clearTodayDataTitle": "\u6e05\u7406\u4eca\u65e5\u6570\u636e\uff1f",
"clearTodayDataBody": "\u53ea\u91cd\u7f6e\u4eca\u5929\u7684\u91cd\u70b9\u3001\u6253\u5361\u3001\u4e13\u6ce8\u8bb0\u5f55\u548c\u590d\u76d8\uff0c\u8fc7\u53bb\u65e5\u671f\u7684\u6570\u636e\u4f1a\u4fdd\u7559\u3002",
"clearAllDataTitle": "\u5220\u9664\u5168\u90e8\u672c\u5730\u6570\u636e\uff1f",
"clearAllDataBody": "\u4f1a\u5220\u9664\u672c\u673a\u7684\u76ee\u6807\u3001\u4e60\u60ef\u3001\u4e13\u6ce8\u8bb0\u5f55\u548c\u590d\u76d8\uff0c\u65e0\u6cd5\u64a4\u9500\u3002",
"clear": "\u6e05\u9664",
"deleteAll": "全部删除",
"privacyTitle": "\u9690\u79c1",
"deleteAll": "\u5168\u90e8\u5220\u9664",
"privacyTitle": "\u9690\u79c1\u4e0e\u6570\u636e",
"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": "你可以随时清理今日进度,或删除本机的全部本地数据。",
"privacyBodyTwo": "\u4f60\u53ef\u4ee5\u968f\u65f6\u6e05\u7406\u4eca\u65e5\u8fdb\u5ea6\uff0c\u6216\u5220\u9664\u672c\u673a\u7684\u5168\u90e8\u672c\u5730\u6570\u636e\u3002",
"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",
@@ -136,9 +136,9 @@
"moodClear": "\u6e05\u9192",
"moodProud": "\u6ee1\u610f",
"moodTired": "\u75b2\u60eb",
"meTitle": "\u6211\u7684\u7a7a\u95f4",
"meSubtitle": "\u672c\u5730\u79c1\u6709\uff0c\u65e0\u9700\u8d26\u53f7\u3002",
"appearance": "\u5916\u89c2",
"meTitle": "\u6211\u7684\u82b1\u56ed",
"meSubtitle": "\u504f\u597d\u548c\u8bb0\u5f55\u90fd\u53ea\u4fdd\u5b58\u5728\u672c\u673a\u3002",
"appearance": "\u4e3b\u9898",
"system": "\u7cfb\u7edf",
"light": "\u6d45\u8272",
"dark": "\u6df1\u8272",
@@ -147,24 +147,45 @@
"languageDefault": "\u9ed8\u8ba4",
"languageEnglish": "English",
"languageChinese": "\u4e2d\u6587",
"localData": "本地数据",
"focusDataLabel": "专注",
"habitsDataLabel": "习惯",
"notesDataLabel": "记录",
"clearToday": "清理今日",
"clearTodaySubtitle": "只移除今天的进度。",
"privacySubtitle": "\u6570\u636e\u53ea\u4fdd\u5b58\u5728\u672c\u673a\u3002",
"deleteAllLocalData": "删除全部本地数据",
"deleteAllLocalDataSubtitle": "重置这台设备上的 Focus Seed。",
"todayDataCleared": "今日数据已清理",
"localDataCleared": "本地数据已删除",
"journeyTitle": "\u4eca\u65e5\u8fdb\u5c55",
"journeyStatsTitle": "本周",
"weekFocusLabel": "专注",
"streakLabel": "连续",
"activeDaysLabel": "活跃",
"habitRateLabel": "习惯",
"daysValue": "{count}天",
"preferencesTitle": "\u504f\u597d",
"preferencesDetail": "\u8ba9\u754c\u9762\u66f4\u987a\u624b",
"localData": "\u672c\u5730\u8bb0\u5f55",
"localDataDetail": "\u53ea\u5728\u672c\u673a",
"personalGardenTitle": "\u4f60\u7684\u4e13\u6ce8\u82b1\u56ed",
"personalGardenSubtitle": "\u6bcf\u4e00\u6b21\u5c0f\u4e13\u6ce8\uff0c\u90fd\u4f1a\u7559\u4e0b\u53ef\u56de\u770b\u7684\u75d5\u8ff9\u3002",
"focusDataLabel": "\u4e13\u6ce8",
"habitsDataLabel": "\u4e60\u60ef",
"notesDataLabel": "\u8bb0\u5f55",
"clearToday": "\u6e05\u7406\u4eca\u5929",
"clearTodaySubtitle": "\u53ea\u91cd\u7f6e\u4eca\u5929\u7684\u95ed\u73af\u3002",
"privacySubtitle": "\u65e0\u8d26\u53f7\uff0c\u65e0\u4e91\u540c\u6b65\u3002",
"deleteAllLocalData": "\u5220\u9664\u5168\u90e8\u6570\u636e",
"deleteAllLocalDataSubtitle": "\u79fb\u9664\u8fd9\u53f0\u8bbe\u5907\u4e0a\u7684 Focus Seed \u8bb0\u5f55\u3002",
"todayDataCleared": "\u4eca\u65e5\u6570\u636e\u5df2\u6e05\u7406",
"localDataCleared": "\u672c\u5730\u6570\u636e\u5df2\u5220\u9664",
"journeyTitle": "\u8fdb\u5c55",
"journeyOverviewLabel": "\u4eca\u65e5\u95ed\u73af",
"journeyOverviewStartTitle": "\u5148\u5b9a\u4e0b\u4e00\u4e2a\u91cd\u70b9",
"journeyOverviewStartBody": "\u5199\u4e0b\u4eca\u5929\u6700\u503c\u5f97\u5b8c\u6210\u7684\u90a3\u4ef6\u4e8b\u3002",
"journeyOverviewActiveTitle": "\u4eca\u5929\u6b63\u5728\u6210\u5f62",
"journeyOverviewActiveBody": "\u7ee7\u7eed\u63a8\u8fdb\uff1a\u4e13\u6ce8\u3001\u6253\u5361\uff0c\u7136\u540e\u7528\u4e00\u53e5\u8bdd\u590d\u76d8\u3002",
"journeyOverviewDoneTitle": "\u4eca\u65e5\u95ed\u73af\u5b8c\u6210",
"journeyOverviewDoneBody": "\u4f60\u5df2\u7ecf\u7ed9\u672a\u6765\u7684\u81ea\u5df1\u7559\u4e0b\u4e86\u4e00\u6761\u6e05\u6670\u8bb0\u5f55\u3002",
"journeyOverviewProgress": "\u5df2\u5b8c\u6210 {completed}/4",
"@journeyOverviewProgress": {
"placeholders": {
"completed": {
"type": "int"
}
}
},
"reviewNow": "\u73b0\u5728\u590d\u76d8",
"journeyStatsTitle": "7 \u65e5\u8282\u594f",
"weekFocusLabel": "\u4e13\u6ce8",
"streakLabel": "\u8fde\u7eed",
"activeDaysLabel": "\u6d3b\u8dc3",
"habitRateLabel": "\u4e60\u60ef",
"daysValue": "{count}\u5929",
"@daysValue": {
"placeholders": {
"count": {
@@ -180,15 +201,15 @@
}
}
},
"viewHistory": "查看历史",
"viewHistorySubtitle": "回看专注记录和复盘。",
"historyTitle": "历史",
"focusHistory": "专注",
"notesHistory": "复盘",
"emptyFocusHistoryTitle": "还没有专注记录",
"emptyFocusHistoryBody": "从今日页开始一轮专注,完成后会出现在这里。",
"emptyNotesHistoryTitle": "还没有复盘",
"emptyNotesHistoryBody": "专注后保存一句复盘,让进展更清晰。",
"viewHistory": "\u8bb0\u5f55\u5e93",
"viewHistorySubtitle": "\u67e5\u770b\u4e13\u6ce8\u8bb0\u5f55\u548c\u590d\u76d8\u3002",
"historyTitle": "\u5386\u53f2",
"focusHistory": "\u4e13\u6ce8",
"notesHistory": "\u590d\u76d8",
"emptyFocusHistoryTitle": "\u8fd8\u6ca1\u6709\u4e13\u6ce8\u8bb0\u5f55",
"emptyFocusHistoryBody": "\u4ece\u4eca\u65e5\u9875\u5f00\u59cb\u4e00\u8f6e\u4e13\u6ce8\uff0c\u5b8c\u6210\u540e\u4f1a\u51fa\u73b0\u5728\u8fd9\u91cc\u3002",
"emptyNotesHistoryTitle": "\u8fd8\u6ca1\u6709\u590d\u76d8",
"emptyNotesHistoryBody": "\u4e13\u6ce8\u540e\u4fdd\u5b58\u4e00\u53e5\u590d\u76d8\uff0c\u8ba9\u8fdb\u5c55\u66f4\u6e05\u6670\u3002",
"recentFocus": "\u6700\u8fd1\u4e13\u6ce8",
"sessionsTotal": "\u5171 {count} \u6b21",
"@sessionsTotal": {
+101 -17
View File
@@ -194,13 +194,13 @@ abstract class AppLocalizations {
/// No description provided for @todayIntentionLabel.
///
/// In en, this message translates to:
/// **'TODAY\'S INTENTION'**
/// **'TODAY\'S FOCUS'**
String get todayIntentionLabel;
/// No description provided for @chooseOneOutcome.
///
/// In en, this message translates to:
/// **'Choose one outcome'**
/// **'Name today\'s focus'**
String get chooseOneOutcome;
/// No description provided for @focusLabel.
@@ -248,7 +248,7 @@ abstract class AppLocalizations {
/// No description provided for @setTodaysIntention.
///
/// In en, this message translates to:
/// **'Set today\'s intention'**
/// **'Set today\'s focus'**
String get setTodaysIntention;
/// No description provided for @pause.
@@ -512,7 +512,7 @@ abstract class AppLocalizations {
/// No description provided for @clearTodayDataBody.
///
/// In en, this message translates to:
/// **'This removes today\'s intention, habit checks, focus sessions, and reflection. Past days stay untouched.'**
/// **'This resets today\'s focus, habit checks, focus sessions, and reflection. Past days stay untouched.'**
String get clearTodayDataBody;
/// No description provided for @clearAllDataTitle.
@@ -542,7 +542,7 @@ abstract class AppLocalizations {
/// No description provided for @privacyTitle.
///
/// In en, this message translates to:
/// **'Privacy'**
/// **'Privacy & data'**
String get privacyTitle;
/// No description provided for @privacyBodyOne.
@@ -626,19 +626,19 @@ abstract class AppLocalizations {
/// No description provided for @meTitle.
///
/// In en, this message translates to:
/// **'Your space'**
/// **'Your garden'**
String get meTitle;
/// No description provided for @meSubtitle.
///
/// In en, this message translates to:
/// **'Private by default. No account required.'**
/// **'Preferences and records stay on this device.'**
String get meSubtitle;
/// No description provided for @appearance.
///
/// In en, this message translates to:
/// **'Appearance'**
/// **'Theme'**
String get appearance;
/// No description provided for @system.
@@ -689,12 +689,42 @@ abstract class AppLocalizations {
/// **'Chinese'**
String get languageChinese;
/// No description provided for @preferencesTitle.
///
/// In en, this message translates to:
/// **'Preferences'**
String get preferencesTitle;
/// No description provided for @preferencesDetail.
///
/// In en, this message translates to:
/// **'Make it feel right'**
String get preferencesDetail;
/// No description provided for @localData.
///
/// In en, this message translates to:
/// **'Local data'**
/// **'Local records'**
String get localData;
/// No description provided for @localDataDetail.
///
/// In en, this message translates to:
/// **'Private on device'**
String get localDataDetail;
/// No description provided for @personalGardenTitle.
///
/// In en, this message translates to:
/// **'Your focus garden'**
String get personalGardenTitle;
/// No description provided for @personalGardenSubtitle.
///
/// In en, this message translates to:
/// **'Every small session leaves a trace you can return to.'**
String get personalGardenSubtitle;
/// No description provided for @focusDataLabel.
///
/// In en, this message translates to:
@@ -722,25 +752,25 @@ abstract class AppLocalizations {
/// No description provided for @clearTodaySubtitle.
///
/// In en, this message translates to:
/// **'Remove only today\'s progress.'**
/// **'Reset only today\'s loop.'**
String get clearTodaySubtitle;
/// No description provided for @privacySubtitle.
///
/// In en, this message translates to:
/// **'Everything stays on this device.'**
/// **'No account or cloud sync.'**
String get privacySubtitle;
/// No description provided for @deleteAllLocalData.
///
/// In en, this message translates to:
/// **'Delete all local data'**
/// **'Delete all data'**
String get deleteAllLocalData;
/// No description provided for @deleteAllLocalDataSubtitle.
///
/// In en, this message translates to:
/// **'Reset Focus Seed on this device.'**
/// **'Remove this device\'s Focus Seed records.'**
String get deleteAllLocalDataSubtitle;
/// No description provided for @todayDataCleared.
@@ -758,13 +788,67 @@ abstract class AppLocalizations {
/// No description provided for @journeyTitle.
///
/// In en, this message translates to:
/// **'Today\'s journey'**
/// **'Progress'**
String get journeyTitle;
/// No description provided for @journeyOverviewLabel.
///
/// In en, this message translates to:
/// **'TODAY\'S LOOP'**
String get journeyOverviewLabel;
/// No description provided for @journeyOverviewStartTitle.
///
/// In en, this message translates to:
/// **'Start with one clear focus'**
String get journeyOverviewStartTitle;
/// No description provided for @journeyOverviewStartBody.
///
/// In en, this message translates to:
/// **'Pick the thing that would make today feel finished.'**
String get journeyOverviewStartBody;
/// No description provided for @journeyOverviewActiveTitle.
///
/// In en, this message translates to:
/// **'Your day is taking shape'**
String get journeyOverviewActiveTitle;
/// No description provided for @journeyOverviewActiveBody.
///
/// In en, this message translates to:
/// **'Keep the loop moving: focus, habits, then a short reflection.'**
String get journeyOverviewActiveBody;
/// No description provided for @journeyOverviewDoneTitle.
///
/// In en, this message translates to:
/// **'Loop complete'**
String get journeyOverviewDoneTitle;
/// No description provided for @journeyOverviewDoneBody.
///
/// In en, this message translates to:
/// **'You left a clean record for future you.'**
String get journeyOverviewDoneBody;
/// No description provided for @journeyOverviewProgress.
///
/// In en, this message translates to:
/// **'{completed}/4 done'**
String journeyOverviewProgress(int completed);
/// No description provided for @reviewNow.
///
/// In en, this message translates to:
/// **'Reflect now'**
String get reviewNow;
/// No description provided for @journeyStatsTitle.
///
/// In en, this message translates to:
/// **'This week'**
/// **'7-day rhythm'**
String get journeyStatsTitle;
/// No description provided for @weekFocusLabel.
@@ -806,13 +890,13 @@ abstract class AppLocalizations {
/// No description provided for @viewHistory.
///
/// In en, this message translates to:
/// **'View history'**
/// **'Record library'**
String get viewHistory;
/// No description provided for @viewHistorySubtitle.
///
/// In en, this message translates to:
/// **'Review focus sessions and reflections.'**
/// **'Open focus sessions and reflections.'**
String get viewHistorySubtitle;
/// No description provided for @historyTitle.
@@ -59,10 +59,10 @@ class AppLocalizationsEn extends AppLocalizations {
}
@override
String get todayIntentionLabel => 'TODAY\'S INTENTION';
String get todayIntentionLabel => 'TODAY\'S FOCUS';
@override
String get chooseOneOutcome => 'Choose one outcome';
String get chooseOneOutcome => 'Name today\'s focus';
@override
String get focusLabel => 'FOCUS';
@@ -86,7 +86,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get startAnotherSession => 'Start another session';
@override
String get setTodaysIntention => 'Set today\'s intention';
String get setTodaysIntention => 'Set today\'s focus';
@override
String get pause => 'Pause';
@@ -228,7 +228,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get clearTodayDataTitle => 'Clear today?';
@override
String get clearTodayDataBody => 'This removes today\'s intention, habit checks, focus sessions, and reflection. Past days stay untouched.';
String get clearTodayDataBody => 'This resets today\'s focus, habit checks, focus sessions, and reflection. Past days stay untouched.';
@override
String get clearAllDataTitle => 'Delete all local data?';
@@ -243,7 +243,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get deleteAll => 'Delete all';
@override
String get privacyTitle => 'Privacy';
String get privacyTitle => 'Privacy & data';
@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.';
@@ -285,13 +285,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get moodTired => 'Tired';
@override
String get meTitle => 'Your space';
String get meTitle => 'Your garden';
@override
String get meSubtitle => 'Private by default. No account required.';
String get meSubtitle => 'Preferences and records stay on this device.';
@override
String get appearance => 'Appearance';
String get appearance => 'Theme';
@override
String get system => 'System';
@@ -318,7 +318,22 @@ class AppLocalizationsEn extends AppLocalizations {
String get languageChinese => 'Chinese';
@override
String get localData => 'Local data';
String get preferencesTitle => 'Preferences';
@override
String get preferencesDetail => 'Make it feel right';
@override
String get localData => 'Local records';
@override
String get localDataDetail => 'Private on device';
@override
String get personalGardenTitle => 'Your focus garden';
@override
String get personalGardenSubtitle => 'Every small session leaves a trace you can return to.';
@override
String get focusDataLabel => 'Focus';
@@ -333,16 +348,16 @@ class AppLocalizationsEn extends AppLocalizations {
String get clearToday => 'Clear today';
@override
String get clearTodaySubtitle => 'Remove only today\'s progress.';
String get clearTodaySubtitle => 'Reset only today\'s loop.';
@override
String get privacySubtitle => 'Everything stays on this device.';
String get privacySubtitle => 'No account or cloud sync.';
@override
String get deleteAllLocalData => 'Delete all local data';
String get deleteAllLocalData => 'Delete all data';
@override
String get deleteAllLocalDataSubtitle => 'Reset Focus Seed on this device.';
String get deleteAllLocalDataSubtitle => 'Remove this device\'s Focus Seed records.';
@override
String get todayDataCleared => 'Today\'s data cleared';
@@ -351,10 +366,39 @@ class AppLocalizationsEn extends AppLocalizations {
String get localDataCleared => 'Local data deleted';
@override
String get journeyTitle => 'Today\'s journey';
String get journeyTitle => 'Progress';
@override
String get journeyStatsTitle => 'This week';
String get journeyOverviewLabel => 'TODAY\'S LOOP';
@override
String get journeyOverviewStartTitle => 'Start with one clear focus';
@override
String get journeyOverviewStartBody => 'Pick the thing that would make today feel finished.';
@override
String get journeyOverviewActiveTitle => 'Your day is taking shape';
@override
String get journeyOverviewActiveBody => 'Keep the loop moving: focus, habits, then a short reflection.';
@override
String get journeyOverviewDoneTitle => 'Loop complete';
@override
String get journeyOverviewDoneBody => 'You left a clean record for future you.';
@override
String journeyOverviewProgress(int completed) {
return '$completed/4 done';
}
@override
String get reviewNow => 'Reflect now';
@override
String get journeyStatsTitle => '7-day rhythm';
@override
String get weekFocusLabel => 'Focus';
@@ -379,10 +423,10 @@ class AppLocalizationsEn extends AppLocalizations {
}
@override
String get viewHistory => 'View history';
String get viewHistory => 'Record library';
@override
String get viewHistorySubtitle => 'Review focus sessions and reflections.';
String get viewHistorySubtitle => 'Open focus sessions and reflections.';
@override
String get historyTitle => 'History';
@@ -59,10 +59,10 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
String get todayIntentionLabel => '今日目标';
String get todayIntentionLabel => '今日重点';
@override
String get chooseOneOutcome => '选一个结果';
String get chooseOneOutcome => '写下今天的重点';
@override
String get focusLabel => '专注';
@@ -86,7 +86,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get startAnotherSession => '再来一轮';
@override
String get setTodaysIntention => '设置今日目标';
String get setTodaysIntention => '设置今日重点';
@override
String get pause => '暂停';
@@ -228,7 +228,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get clearTodayDataTitle => '清理今日数据?';
@override
String get clearTodayDataBody => '只清除今天的目标、打卡、专注记录和复盘,过去日期的数据会保留。';
String get clearTodayDataBody => '只重置今天的重点、打卡、专注记录和复盘,过去日期的数据会保留。';
@override
String get clearAllDataTitle => '删除全部本地数据?';
@@ -243,7 +243,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get deleteAll => '全部删除';
@override
String get privacyTitle => '隐私';
String get privacyTitle => '隐私与数据';
@override
String get privacyBodyOne => 'Focus Seed 只在本机保存目标、习惯、专注记录、复盘和设置。当前版本没有登录、云同步、广告、分析 SDK 或付费 SDK。';
@@ -285,13 +285,13 @@ class AppLocalizationsZh extends AppLocalizations {
String get moodTired => '疲惫';
@override
String get meTitle => '我的空间';
String get meTitle => '我的花园';
@override
String get meSubtitle => '本地私有,无需账号。';
String get meSubtitle => '偏好和记录都只保存在本机。';
@override
String get appearance => '外观';
String get appearance => '主题';
@override
String get system => '系统';
@@ -318,7 +318,22 @@ class AppLocalizationsZh extends AppLocalizations {
String get languageChinese => '中文';
@override
String get localData => '本地数据';
String get preferencesTitle => '偏好';
@override
String get preferencesDetail => '让界面更顺手';
@override
String get localData => '本地记录';
@override
String get localDataDetail => '只在本机';
@override
String get personalGardenTitle => '你的专注花园';
@override
String get personalGardenSubtitle => '每一次小专注,都会留下可回看的痕迹。';
@override
String get focusDataLabel => '专注';
@@ -330,19 +345,19 @@ class AppLocalizationsZh extends AppLocalizations {
String get notesDataLabel => '记录';
@override
String get clearToday => '清理今日';
String get clearToday => '清理今天';
@override
String get clearTodaySubtitle => '只移除今天的进度。';
String get clearTodaySubtitle => '只重置今天的闭环。';
@override
String get privacySubtitle => '数据只保存在本机。';
String get privacySubtitle => '无账号,无云同步。';
@override
String get deleteAllLocalData => '删除全部本地数据';
String get deleteAllLocalData => '删除全部数据';
@override
String get deleteAllLocalDataSubtitle => '重置这台设备上的 Focus Seed。';
String get deleteAllLocalDataSubtitle => '移除这台设备上的 Focus Seed 记录。';
@override
String get todayDataCleared => '今日数据已清理';
@@ -351,10 +366,39 @@ class AppLocalizationsZh extends AppLocalizations {
String get localDataCleared => '本地数据已删除';
@override
String get journeyTitle => '今日进展';
String get journeyTitle => '进展';
@override
String get journeyStatsTitle => '本周';
String get journeyOverviewLabel => '今日闭环';
@override
String get journeyOverviewStartTitle => '先定下一个重点';
@override
String get journeyOverviewStartBody => '写下今天最值得完成的那件事。';
@override
String get journeyOverviewActiveTitle => '今天正在成形';
@override
String get journeyOverviewActiveBody => '继续推进:专注、打卡,然后用一句话复盘。';
@override
String get journeyOverviewDoneTitle => '今日闭环完成';
@override
String get journeyOverviewDoneBody => '你已经给未来的自己留下了一条清晰记录。';
@override
String journeyOverviewProgress(int completed) {
return '已完成 $completed/4';
}
@override
String get reviewNow => '现在复盘';
@override
String get journeyStatsTitle => '7 日节奏';
@override
String get weekFocusLabel => '专注';
@@ -379,10 +423,10 @@ class AppLocalizationsZh extends AppLocalizations {
}
@override
String get viewHistory => '查看历史';
String get viewHistory => '记录库';
@override
String get viewHistorySubtitle => '回看专注记录和复盘。';
String get viewHistorySubtitle => '查看专注记录和复盘。';
@override
String get historyTitle => '历史';
+200
View File
@@ -0,0 +1,200 @@
import 'package:flutter/material.dart';
enum SeedIllustrationTone { garden, archive, data }
class SeedIllustration extends StatelessWidget {
const SeedIllustration({
this.tone = SeedIllustrationTone.garden,
this.size = 118,
super.key,
});
final SeedIllustrationTone tone;
final double size;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final accent = switch (tone) {
SeedIllustrationTone.garden => scheme.primary,
SeedIllustrationTone.archive => scheme.secondary,
SeedIllustrationTone.data => scheme.tertiary,
};
final softAccent = accent.withValues(alpha: 0.18);
return ExcludeSemantics(
child: SizedBox.square(
dimension: size,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Positioned.fill(
child: DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: RadialGradient(
colors: [
softAccent,
scheme.surfaceContainerHigh.withValues(alpha: 0.28),
Colors.transparent,
],
),
),
),
),
Positioned(
bottom: size * 0.16,
child: Container(
width: size * 0.62,
height: size * 0.28,
decoration: BoxDecoration(
color: scheme.surfaceContainerHighest.withValues(alpha: 0.78),
borderRadius: BorderRadius.circular(size),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.10),
blurRadius: 18,
offset: const Offset(0, 8),
),
],
),
),
),
Positioned(
top: size * 0.18,
left: size * 0.28,
child: Transform.rotate(
angle: -0.62,
child: _LeafBlob(
width: size * 0.26,
height: size * 0.42,
color: accent.withValues(alpha: 0.78),
),
),
),
Positioned(
top: size * 0.16,
right: size * 0.28,
child: Transform.rotate(
angle: 0.62,
child: _LeafBlob(
width: size * 0.28,
height: size * 0.45,
color: scheme.primaryContainer.withValues(alpha: 0.86),
),
),
),
Positioned(
top: size * 0.44,
child: Container(
width: size * 0.08,
height: size * 0.28,
decoration: BoxDecoration(
color: scheme.primary.withValues(alpha: 0.78),
borderRadius: BorderRadius.circular(99),
),
),
),
Positioned(
bottom: size * 0.26,
child: Transform.rotate(
angle: 0.18,
child: Container(
width: size * 0.18,
height: size * 0.28,
decoration: BoxDecoration(
color: scheme.tertiaryContainer.withValues(alpha: 0.90),
borderRadius: BorderRadius.circular(99),
border: Border.all(
color: scheme.tertiary.withValues(alpha: 0.28),
),
boxShadow: [
BoxShadow(
color: accent.withValues(alpha: 0.22),
blurRadius: 12,
offset: const Offset(0, 5),
),
],
),
),
),
),
Positioned(
right: size * 0.10,
top: size * 0.30,
child: _FloatingDot(size: size * 0.09, color: accent),
),
Positioned(
left: size * 0.12,
bottom: size * 0.34,
child: _FloatingDot(size: size * 0.065, color: scheme.secondary),
),
],
),
),
);
}
}
class _LeafBlob extends StatelessWidget {
const _LeafBlob({
required this.width,
required this.height,
required this.color,
});
final double width;
final double height;
final Color color;
@override
Widget build(BuildContext context) {
return Container(
width: width,
height: height,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(width),
topRight: Radius.circular(width * 0.35),
bottomLeft: Radius.circular(width * 0.35),
bottomRight: Radius.circular(width),
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.10),
blurRadius: 12,
offset: const Offset(0, 5),
),
],
),
);
}
}
class _FloatingDot extends StatelessWidget {
const _FloatingDot({required this.size, required this.color});
final double size;
final Color color;
@override
Widget build(BuildContext context) {
return Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: color.withValues(alpha: 0.42),
boxShadow: [
BoxShadow(
color: color.withValues(alpha: 0.22),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
),
);
}
}
+27 -18
View File
@@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:focus_seed/src/app/focus_seed_app.dart';
import 'package:focus_seed/src/data/focus_seed_store.dart';
import 'package:focus_seed/src/data/local/focus_seed_database.dart';
import 'package:focus_seed/src/models/language_preference.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
@@ -20,10 +21,10 @@ void main() {
await pumpFocusSeedApp(tester, locale: const Locale('en'));
expect(find.text('Focus Seed'), findsOneWidget);
expect(find.text("TODAY'S INTENTION"), findsOneWidget);
expect(find.text("Set today's intention"), findsOneWidget);
expect(find.text("TODAY'S FOCUS"), findsOneWidget);
expect(find.text("Set today's focus"), findsOneWidget);
await tester.tap(find.text('Choose one outcome'));
await tester.tap(find.text("Name today's focus"));
await tester.pumpAndSettle();
await tester.enterText(
find.byType(TextField),
@@ -41,12 +42,11 @@ void main() {
await tester.tap(find.text('Journey'));
await tester.pumpAndSettle();
expect(find.text("Today's journey"), findsOneWidget);
expect(find.text('Progress'), findsOneWidget);
await tester.tap(find.text('Me'));
await tester.pumpAndSettle();
expect(find.text('Your space'), findsOneWidget);
expect(find.text('Appearance'), findsOneWidget);
expect(find.text('Your garden'), findsOneWidget);
});
testWidgets('Me manages local data without JSON tools', (tester) async {
@@ -55,10 +55,15 @@ void main() {
await tester.tap(find.text('Me'));
await tester.pumpAndSettle();
expect(find.text('Local data'), findsOneWidget);
final meScroll = find.byType(Scrollable).first;
expect(find.text('Local records'), findsOneWidget);
expect(find.text('Clear today'), findsOneWidget);
await tester.scrollUntilVisible(find.text('Delete all local data'), 180);
expect(find.text('Delete all local data'), findsOneWidget);
await tester.scrollUntilVisible(
find.text('Delete all data'),
180,
scrollable: meScroll,
);
expect(find.text('Delete all data'), findsOneWidget);
expect(find.text('Export JSON'), findsNothing);
expect(find.text('Import JSON'), findsNothing);
});
@@ -71,19 +76,19 @@ void main() {
final journeyScroll = find.byType(Scrollable).first;
await tester.scrollUntilVisible(
find.text('This week'),
find.text('7-day rhythm'),
180,
scrollable: journeyScroll,
);
expect(find.text('This week'), findsOneWidget);
expect(find.text('7-day rhythm'), findsOneWidget);
await tester.scrollUntilVisible(
find.text('View history'),
find.text('Record library'),
180,
scrollable: journeyScroll,
);
expect(find.text('View history'), findsOneWidget);
expect(find.text('Record library'), findsOneWidget);
await tester.tap(find.text('View history'));
await tester.tap(find.text('Record library'));
await tester.pumpAndSettle();
expect(find.text('History'), findsOneWidget);
@@ -117,8 +122,8 @@ void main() {
await pumpFocusSeedApp(tester);
expect(find.text('Focus Seed'), findsOneWidget);
expect(find.text('\u4eca\u65e5\u76ee\u6807'), findsOneWidget);
expect(find.text('\u8bbe\u7f6e\u4eca\u65e5\u76ee\u6807'), findsOneWidget);
expect(find.text('\u4eca\u65e5\u91cd\u70b9'), findsOneWidget);
expect(find.text('\u8bbe\u7f6e\u4eca\u65e5\u91cd\u70b9'), findsOneWidget);
await tester.scrollUntilVisible(
find.text('\u8ba1\u5212\u4e00\u4e2a\u660e\u786e\u4efb\u52a1'),
180,
@@ -138,11 +143,15 @@ void main() {
await tester.pumpAndSettle();
expect(find.text('\u8bed\u8a00'), findsOneWidget);
await tester.tap(find.text('English'));
final languageSegments = find.byType(SegmentedButton<LanguagePreference>);
final widget = tester.widget<SegmentedButton<LanguagePreference>>(
languageSegments,
);
widget.onSelectionChanged?.call({LanguagePreference.english});
await tester.pumpAndSettle();
expect(find.text('Your space'), findsOneWidget);
expect(find.text('Language'), findsOneWidget);
expect(find.text('Development only'), findsOneWidget);
});
}