diff --git a/lib/src/features/journey/journey_screen.dart b/lib/src/features/journey/journey_screen.dart index e62cda3..377ad7d 100644 --- a/lib/src/features/journey/journey_screen.dart +++ b/lib/src/features/journey/journey_screen.dart @@ -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( @@ -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}); diff --git a/lib/src/features/journey/widgets/journey_overview_panel.dart b/lib/src/features/journey/widgets/journey_overview_panel.dart new file mode 100644 index 0000000..a66042d --- /dev/null +++ b/lib/src/features/journey/widgets/journey_overview_panel.dart @@ -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), + ), + ], + ), + ], + ); + } +} diff --git a/lib/src/features/journey/widgets/journey_stats_panel.dart b/lib/src/features/journey/widgets/journey_stats_panel.dart index 839a377..e2d12e0 100644 --- a/lib/src/features/journey/widgets/journey_stats_panel.dart +++ b/lib/src/features/journey/widgets/journey_stats_panel.dart @@ -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( - 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( + 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, + ], ), ), ], diff --git a/lib/src/features/me/me_screen.dart b/lib/src/features/me/me_screen.dart index 5510f40..6bb5a4f 100644 --- a/lib/src/features/me/me_screen.dart +++ b/lib/src/features/me/me_screen.dart @@ -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( - 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( + 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 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, + ), + ), + ], ), - ], + ), ); } } diff --git a/lib/src/l10n/app_en.arb b/lib/src/l10n/app_en.arb index b115af6..2dc75b5 100644 --- a/lib/src/l10n/app_en.arb +++ b/lib/src/l10n/app_en.arb @@ -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", diff --git a/lib/src/l10n/app_zh.arb b/lib/src/l10n/app_zh.arb index 15713d8..df2532d 100644 --- a/lib/src/l10n/app_zh.arb +++ b/lib/src/l10n/app_zh.arb @@ -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": { diff --git a/lib/src/l10n/generated/app_localizations.dart b/lib/src/l10n/generated/app_localizations.dart index b47a8aa..d2e677f 100644 --- a/lib/src/l10n/generated/app_localizations.dart +++ b/lib/src/l10n/generated/app_localizations.dart @@ -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. diff --git a/lib/src/l10n/generated/app_localizations_en.dart b/lib/src/l10n/generated/app_localizations_en.dart index 286f571..3e027ff 100644 --- a/lib/src/l10n/generated/app_localizations_en.dart +++ b/lib/src/l10n/generated/app_localizations_en.dart @@ -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'; diff --git a/lib/src/l10n/generated/app_localizations_zh.dart b/lib/src/l10n/generated/app_localizations_zh.dart index 1d0fbb6..8adc352 100644 --- a/lib/src/l10n/generated/app_localizations_zh.dart +++ b/lib/src/l10n/generated/app_localizations_zh.dart @@ -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 => '历史'; diff --git a/lib/src/widgets/seed_illustration.dart b/lib/src/widgets/seed_illustration.dart new file mode 100644 index 0000000..500d388 --- /dev/null +++ b/lib/src/widgets/seed_illustration.dart @@ -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), + ), + ], + ), + ); + } +} diff --git a/test/widget_test.dart b/test/widget_test.dart index ecc5e2a..7f7c021 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -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); + final widget = tester.widget>( + 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); }); }