feat: polish focus seed product UI
This commit is contained in:
@@ -6,12 +6,13 @@ import '../../l10n/l10n.dart';
|
|||||||
import '../../models/journey_stats.dart';
|
import '../../models/journey_stats.dart';
|
||||||
import '../../models/today_progress.dart';
|
import '../../models/today_progress.dart';
|
||||||
import '../../widgets/focus_seed_header.dart';
|
import '../../widgets/focus_seed_header.dart';
|
||||||
|
import '../../widgets/seed_illustration.dart';
|
||||||
import '../../widgets/surface_panel.dart';
|
import '../../widgets/surface_panel.dart';
|
||||||
import '../reflection/reflection_sheet.dart';
|
import '../reflection/reflection_sheet.dart';
|
||||||
import 'history_screen.dart';
|
import 'history_screen.dart';
|
||||||
import 'widgets/growth_week_strip.dart';
|
import 'widgets/growth_week_strip.dart';
|
||||||
|
import 'widgets/journey_overview_panel.dart';
|
||||||
import 'widgets/journey_stats_panel.dart';
|
import 'widgets/journey_stats_panel.dart';
|
||||||
import 'widgets/journey_timeline.dart';
|
|
||||||
|
|
||||||
class JourneyScreen extends StatelessWidget {
|
class JourneyScreen extends StatelessWidget {
|
||||||
const JourneyScreen({required this.controller, super.key});
|
const JourneyScreen({required this.controller, super.key});
|
||||||
@@ -39,15 +40,16 @@ class JourneyScreen extends StatelessWidget {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 6, 20, 32),
|
padding: const EdgeInsets.fromLTRB(20, 6, 20, 32),
|
||||||
children: [
|
children: [
|
||||||
JourneyTimeline(
|
JourneyOverviewPanel(
|
||||||
progress: progress,
|
progress: progress,
|
||||||
onReflect: () => showReflectionSheet(context, controller),
|
onReflect: () => showReflectionSheet(context, controller),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
JourneyStatsPanel(stats: stats),
|
JourneyStatsPanel(stats: stats),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
SurfacePanel(
|
GrowthWeekStrip(state: state),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
const SizedBox(height: 12),
|
||||||
|
_HistoryGateway(
|
||||||
onTap:
|
onTap:
|
||||||
() => Navigator.of(context).push(
|
() => Navigator.of(context).push(
|
||||||
MaterialPageRoute<void>(
|
MaterialPageRoute<void>(
|
||||||
@@ -55,19 +57,7 @@ class JourneyScreen extends StatelessWidget {
|
|||||||
(context) => HistoryScreen(controller: controller),
|
(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) ...[
|
if (recentSessions.isNotEmpty) ...[
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
_SectionTitle(
|
_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 {
|
class _SectionTitle extends StatelessWidget {
|
||||||
const _SectionTitle({required this.title, required this.detail});
|
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,
|
context,
|
||||||
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800),
|
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w800),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 10),
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final columns = constraints.maxWidth > 520 ? 4 : 2;
|
final columns = constraints.maxWidth > 520 ? 4 : 2;
|
||||||
@@ -32,9 +32,9 @@ class JourneyStatsPanel extends StatelessWidget {
|
|||||||
crossAxisCount: columns,
|
crossAxisCount: columns,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
childAspectRatio: columns == 4 ? 1.35 : 1.85,
|
childAspectRatio: columns == 4 ? 2.05 : 2.15,
|
||||||
mainAxisSpacing: 12,
|
mainAxisSpacing: 10,
|
||||||
crossAxisSpacing: 12,
|
crossAxisSpacing: 10,
|
||||||
children: [
|
children: [
|
||||||
_StatItem(
|
_StatItem(
|
||||||
icon: Icons.timer_outlined,
|
icon: Icons.timer_outlined,
|
||||||
@@ -93,32 +93,47 @@ class _StatItem extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 10),
|
||||||
child: Column(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
Icon(icon, color: scheme.primary, size: 20),
|
Container(
|
||||||
TweenAnimationBuilder<double>(
|
width: 32,
|
||||||
tween: Tween(end: animatedTarget.toDouble()),
|
height: 32,
|
||||||
duration: const Duration(milliseconds: 280),
|
decoration: BoxDecoration(
|
||||||
curve: Curves.easeOutCubic,
|
color: scheme.surfaceContainerLowest.withValues(alpha: 0.72),
|
||||||
builder:
|
borderRadius: BorderRadius.circular(10),
|
||||||
(context, animatedValue, _) => Text(
|
),
|
||||||
valueBuilder(animatedValue.round()),
|
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,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: theme.textTheme.titleLarge?.copyWith(
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
fontWeight: FontWeight.w900,
|
color: scheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
Text(
|
|
||||||
label,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
|
||||||
color: scheme.onSurfaceVariant,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
+272
-100
@@ -6,7 +6,7 @@ import '../../models/appearance_preference.dart';
|
|||||||
import '../../models/language_preference.dart';
|
import '../../models/language_preference.dart';
|
||||||
import '../../widgets/dialogs.dart';
|
import '../../widgets/dialogs.dart';
|
||||||
import '../../widgets/focus_seed_header.dart';
|
import '../../widgets/focus_seed_header.dart';
|
||||||
import '../../widgets/settings_tile.dart';
|
import '../../widgets/seed_illustration.dart';
|
||||||
import '../../widgets/surface_panel.dart';
|
import '../../widgets/surface_panel.dart';
|
||||||
|
|
||||||
class MeScreen extends StatelessWidget {
|
class MeScreen extends StatelessWidget {
|
||||||
@@ -42,9 +42,15 @@ class MeScreen extends StatelessWidget {
|
|||||||
child: ListView(
|
child: ListView(
|
||||||
padding: const EdgeInsets.fromLTRB(20, 6, 20, 32),
|
padding: const EdgeInsets.fromLTRB(20, 6, 20, 32),
|
||||||
children: [
|
children: [
|
||||||
Text(
|
_PersonalGardenCard(
|
||||||
l10n.appearance,
|
focusSessions: state.sessions.length,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
habits: state.habits.length,
|
||||||
|
reflections: state.reflections.length,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 18),
|
||||||
|
_SectionHeader(
|
||||||
|
title: l10n.preferencesTitle,
|
||||||
|
detail: l10n.preferencesDetail,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
SurfacePanel(
|
SurfacePanel(
|
||||||
@@ -76,7 +82,7 @@ class MeScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (showLanguageTools) ...[
|
if (showLanguageTools) ...[
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 16),
|
||||||
_SectionHeader(
|
_SectionHeader(
|
||||||
title: l10n.language,
|
title: l10n.language,
|
||||||
detail: l10n.developmentOnly,
|
detail: l10n.developmentOnly,
|
||||||
@@ -112,59 +118,47 @@ class MeScreen extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
Text(
|
_SectionHeader(
|
||||||
l10n.localData,
|
title: l10n.localData,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
detail: l10n.localDataDetail,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
_DataSnapshotCard(
|
_ActionGrid(
|
||||||
focusSessions: state.sessions.length,
|
children: [
|
||||||
habits: state.habits.length,
|
_ActionCard(
|
||||||
reflections: state.reflections.length,
|
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),
|
const SizedBox(height: 12),
|
||||||
SurfacePanel(
|
_ActionCard(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 14),
|
icon: Icons.delete_forever_outlined,
|
||||||
child: Column(
|
title: l10n.deleteAllLocalData,
|
||||||
children: [
|
subtitle: l10n.deleteAllLocalDataSubtitle,
|
||||||
SettingsTile(
|
destructive: true,
|
||||||
icon: Icons.cleaning_services_outlined,
|
fullWidth: true,
|
||||||
title: l10n.clearToday,
|
onTap:
|
||||||
subtitle: l10n.clearTodaySubtitle,
|
() => confirmClearData(context, () async {
|
||||||
onTap:
|
final cleared = await controller.clearData();
|
||||||
() => confirmClearToday(context, () async {
|
if (context.mounted && cleared) {
|
||||||
final cleared = await controller.clearToday();
|
showSnack(context, l10n.localDataCleared);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 18),
|
const SizedBox(height: 18),
|
||||||
Text(
|
Text(
|
||||||
@@ -182,8 +176,8 @@ class MeScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DataSnapshotCard extends StatelessWidget {
|
class _PersonalGardenCard extends StatelessWidget {
|
||||||
const _DataSnapshotCard({
|
const _PersonalGardenCard({
|
||||||
required this.focusSessions,
|
required this.focusSessions,
|
||||||
required this.habits,
|
required this.habits,
|
||||||
required this.reflections,
|
required this.reflections,
|
||||||
@@ -196,32 +190,89 @@ class _DataSnapshotCard extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final scheme = theme.colorScheme;
|
||||||
return SurfacePanel(
|
return SurfacePanel(
|
||||||
elevated: true,
|
elevated: true,
|
||||||
child: Row(
|
padding: EdgeInsets.zero,
|
||||||
children: [
|
child: Container(
|
||||||
Expanded(
|
padding: const EdgeInsets.fromLTRB(16, 16, 14, 16),
|
||||||
child: _DataMetric(
|
decoration: BoxDecoration(
|
||||||
icon: Icons.timer_outlined,
|
gradient: LinearGradient(
|
||||||
label: l10n.focusDataLabel,
|
begin: Alignment.topLeft,
|
||||||
value: focusSessions,
|
end: Alignment.bottomRight,
|
||||||
),
|
colors: [
|
||||||
|
scheme.primary.withValues(alpha: 0.10),
|
||||||
|
scheme.surfaceContainerLowest,
|
||||||
|
scheme.tertiary.withValues(alpha: 0.07),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
),
|
||||||
child: _DataMetric(
|
child: Column(
|
||||||
icon: Icons.checklist_rounded,
|
children: [
|
||||||
label: l10n.habitsDataLabel,
|
Row(
|
||||||
value: habits,
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 14),
|
||||||
Expanded(
|
Row(
|
||||||
child: _DataMetric(
|
children: [
|
||||||
icon: Icons.edit_note_rounded,
|
Expanded(
|
||||||
label: l10n.notesDataLabel,
|
child: _DataMetric(
|
||||||
value: reflections,
|
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) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scheme = theme.colorScheme;
|
final scheme = theme.colorScheme;
|
||||||
return Column(
|
return Container(
|
||||||
mainAxisSize: MainAxisSize.min,
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 9),
|
||||||
children: [
|
decoration: BoxDecoration(
|
||||||
Icon(icon, color: scheme.primary),
|
color: scheme.surfaceContainerLowest.withValues(alpha: 0.72),
|
||||||
const SizedBox(height: 8),
|
borderRadius: BorderRadius.circular(12),
|
||||||
TweenAnimationBuilder<double>(
|
border: Border.all(color: scheme.outlineVariant.withValues(alpha: 0.6)),
|
||||||
tween: Tween(end: value.toDouble()),
|
),
|
||||||
duration: const Duration(milliseconds: 260),
|
child: Row(
|
||||||
curve: Curves.easeOutCubic,
|
children: [
|
||||||
builder:
|
Icon(icon, color: scheme.primary, size: 18),
|
||||||
(context, animatedValue, _) => Text(
|
const SizedBox(width: 7),
|
||||||
animatedValue.round().toString(),
|
Expanded(
|
||||||
style: theme.textTheme.headlineSmall?.copyWith(
|
child: Column(
|
||||||
fontWeight: FontWeight.w800,
|
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,
|
||||||
Text(
|
maxLines: 1,
|
||||||
label,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 1,
|
style: theme.textTheme.labelSmall?.copyWith(
|
||||||
overflow: TextOverflow.ellipsis,
|
color: scheme.onSurfaceVariant,
|
||||||
style: theme.textTheme.bodySmall?.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
@@ -26,8 +26,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"todayIntentionLabel": "TODAY'S INTENTION",
|
"todayIntentionLabel": "TODAY'S FOCUS",
|
||||||
"chooseOneOutcome": "Choose one outcome",
|
"chooseOneOutcome": "Name today's focus",
|
||||||
"focusLabel": "FOCUS",
|
"focusLabel": "FOCUS",
|
||||||
"timerReady": "Ready when you are",
|
"timerReady": "Ready when you are",
|
||||||
"timerRunning": "Deep work in progress",
|
"timerRunning": "Deep work in progress",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"timerCompleted": "Session complete",
|
"timerCompleted": "Session complete",
|
||||||
"reflectOneMinute": "Reflect for 1 min",
|
"reflectOneMinute": "Reflect for 1 min",
|
||||||
"startAnotherSession": "Start another session",
|
"startAnotherSession": "Start another session",
|
||||||
"setTodaysIntention": "Set today's intention",
|
"setTodaysIntention": "Set today's focus",
|
||||||
"pause": "Pause",
|
"pause": "Pause",
|
||||||
"end": "End",
|
"end": "End",
|
||||||
"resume": "Resume",
|
"resume": "Resume",
|
||||||
@@ -117,12 +117,12 @@
|
|||||||
"close": "Close",
|
"close": "Close",
|
||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
"clearTodayDataTitle": "Clear today?",
|
"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?",
|
"clearAllDataTitle": "Delete all local data?",
|
||||||
"clearAllDataBody": "This deletes intentions, habits, focus sessions, and reflections on this device. It cannot be undone.",
|
"clearAllDataBody": "This deletes intentions, habits, focus sessions, and reflections on this device. It cannot be undone.",
|
||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
"deleteAll": "Delete all",
|
"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.",
|
"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.",
|
"privacyBodyTwo": "You can clear today's progress or delete all local data from Me at any time.",
|
||||||
"reflectionEmptyError": "Write one honest sentence.",
|
"reflectionEmptyError": "Write one honest sentence.",
|
||||||
@@ -136,9 +136,9 @@
|
|||||||
"moodClear": "Clear",
|
"moodClear": "Clear",
|
||||||
"moodProud": "Proud",
|
"moodProud": "Proud",
|
||||||
"moodTired": "Tired",
|
"moodTired": "Tired",
|
||||||
"meTitle": "Your space",
|
"meTitle": "Your garden",
|
||||||
"meSubtitle": "Private by default. No account required.",
|
"meSubtitle": "Preferences and records stay on this device.",
|
||||||
"appearance": "Appearance",
|
"appearance": "Theme",
|
||||||
"system": "System",
|
"system": "System",
|
||||||
"light": "Light",
|
"light": "Light",
|
||||||
"dark": "Dark",
|
"dark": "Dark",
|
||||||
@@ -147,19 +147,40 @@
|
|||||||
"languageDefault": "Default",
|
"languageDefault": "Default",
|
||||||
"languageEnglish": "English",
|
"languageEnglish": "English",
|
||||||
"languageChinese": "Chinese",
|
"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",
|
"focusDataLabel": "Focus",
|
||||||
"habitsDataLabel": "Habits",
|
"habitsDataLabel": "Habits",
|
||||||
"notesDataLabel": "Notes",
|
"notesDataLabel": "Notes",
|
||||||
"clearToday": "Clear today",
|
"clearToday": "Clear today",
|
||||||
"clearTodaySubtitle": "Remove only today's progress.",
|
"clearTodaySubtitle": "Reset only today's loop.",
|
||||||
"privacySubtitle": "Everything stays on this device.",
|
"privacySubtitle": "No account or cloud sync.",
|
||||||
"deleteAllLocalData": "Delete all local data",
|
"deleteAllLocalData": "Delete all data",
|
||||||
"deleteAllLocalDataSubtitle": "Reset Focus Seed on this device.",
|
"deleteAllLocalDataSubtitle": "Remove this device's Focus Seed records.",
|
||||||
"todayDataCleared": "Today's data cleared",
|
"todayDataCleared": "Today's data cleared",
|
||||||
"localDataCleared": "Local data deleted",
|
"localDataCleared": "Local data deleted",
|
||||||
"journeyTitle": "Today's journey",
|
"journeyTitle": "Progress",
|
||||||
"journeyStatsTitle": "This week",
|
"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",
|
"weekFocusLabel": "Focus",
|
||||||
"streakLabel": "Streak",
|
"streakLabel": "Streak",
|
||||||
"activeDaysLabel": "Active",
|
"activeDaysLabel": "Active",
|
||||||
@@ -180,8 +201,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"viewHistory": "View history",
|
"viewHistory": "Record library",
|
||||||
"viewHistorySubtitle": "Review focus sessions and reflections.",
|
"viewHistorySubtitle": "Open focus sessions and reflections.",
|
||||||
"historyTitle": "History",
|
"historyTitle": "History",
|
||||||
"focusHistory": "Focus",
|
"focusHistory": "Focus",
|
||||||
"notesHistory": "Notes",
|
"notesHistory": "Notes",
|
||||||
|
|||||||
+61
-40
@@ -26,8 +26,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"todayIntentionLabel": "\u4eca\u65e5\u76ee\u6807",
|
"todayIntentionLabel": "\u4eca\u65e5\u91cd\u70b9",
|
||||||
"chooseOneOutcome": "\u9009\u4e00\u4e2a\u7ed3\u679c",
|
"chooseOneOutcome": "\u5199\u4e0b\u4eca\u5929\u7684\u91cd\u70b9",
|
||||||
"focusLabel": "\u4e13\u6ce8",
|
"focusLabel": "\u4e13\u6ce8",
|
||||||
"timerReady": "\u51c6\u5907\u597d\u4e86\u5c31\u5f00\u59cb",
|
"timerReady": "\u51c6\u5907\u597d\u4e86\u5c31\u5f00\u59cb",
|
||||||
"timerRunning": "\u4e13\u6ce8\u4e2d",
|
"timerRunning": "\u4e13\u6ce8\u4e2d",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"timerCompleted": "\u5df2\u5b8c\u6210",
|
"timerCompleted": "\u5df2\u5b8c\u6210",
|
||||||
"reflectOneMinute": "\u590d\u76d8 1 \u5206\u949f",
|
"reflectOneMinute": "\u590d\u76d8 1 \u5206\u949f",
|
||||||
"startAnotherSession": "\u518d\u6765\u4e00\u8f6e",
|
"startAnotherSession": "\u518d\u6765\u4e00\u8f6e",
|
||||||
"setTodaysIntention": "\u8bbe\u7f6e\u4eca\u65e5\u76ee\u6807",
|
"setTodaysIntention": "\u8bbe\u7f6e\u4eca\u65e5\u91cd\u70b9",
|
||||||
"pause": "\u6682\u505c",
|
"pause": "\u6682\u505c",
|
||||||
"end": "\u7ed3\u675f",
|
"end": "\u7ed3\u675f",
|
||||||
"resume": "\u7ee7\u7eed",
|
"resume": "\u7ee7\u7eed",
|
||||||
@@ -116,15 +116,15 @@
|
|||||||
"deleteHabitBody": "\u8fd9\u4e2a\u4e60\u60ef\u7684\u5386\u53f2\u5b8c\u6210\u8bb0\u5f55\u4e5f\u4f1a\u5220\u9664\u3002",
|
"deleteHabitBody": "\u8fd9\u4e2a\u4e60\u60ef\u7684\u5386\u53f2\u5b8c\u6210\u8bb0\u5f55\u4e5f\u4f1a\u5220\u9664\u3002",
|
||||||
"close": "\u5173\u95ed",
|
"close": "\u5173\u95ed",
|
||||||
"copy": "\u590d\u5236",
|
"copy": "\u590d\u5236",
|
||||||
"clearTodayDataTitle": "清理今日数据?",
|
"clearTodayDataTitle": "\u6e05\u7406\u4eca\u65e5\u6570\u636e\uff1f",
|
||||||
"clearTodayDataBody": "只清除今天的目标、打卡、专注记录和复盘,过去日期的数据会保留。",
|
"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": "删除全部本地数据?",
|
"clearAllDataTitle": "\u5220\u9664\u5168\u90e8\u672c\u5730\u6570\u636e\uff1f",
|
||||||
"clearAllDataBody": "会删除本机的目标、习惯、专注记录和复盘,无法撤销。",
|
"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",
|
"clear": "\u6e05\u9664",
|
||||||
"deleteAll": "全部删除",
|
"deleteAll": "\u5168\u90e8\u5220\u9664",
|
||||||
"privacyTitle": "\u9690\u79c1",
|
"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",
|
"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",
|
"reflectionEmptyError": "\u5199\u4e00\u53e5\u771f\u5b9e\u7684\u603b\u7ed3\u3002",
|
||||||
"reflectionSaveError": "\u4fdd\u5b58\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5\u3002",
|
"reflectionSaveError": "\u4fdd\u5b58\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5\u3002",
|
||||||
"reflectionSheetTitle": "\u590d\u76d8\u4e00\u4e0b",
|
"reflectionSheetTitle": "\u590d\u76d8\u4e00\u4e0b",
|
||||||
@@ -136,9 +136,9 @@
|
|||||||
"moodClear": "\u6e05\u9192",
|
"moodClear": "\u6e05\u9192",
|
||||||
"moodProud": "\u6ee1\u610f",
|
"moodProud": "\u6ee1\u610f",
|
||||||
"moodTired": "\u75b2\u60eb",
|
"moodTired": "\u75b2\u60eb",
|
||||||
"meTitle": "\u6211\u7684\u7a7a\u95f4",
|
"meTitle": "\u6211\u7684\u82b1\u56ed",
|
||||||
"meSubtitle": "\u672c\u5730\u79c1\u6709\uff0c\u65e0\u9700\u8d26\u53f7\u3002",
|
"meSubtitle": "\u504f\u597d\u548c\u8bb0\u5f55\u90fd\u53ea\u4fdd\u5b58\u5728\u672c\u673a\u3002",
|
||||||
"appearance": "\u5916\u89c2",
|
"appearance": "\u4e3b\u9898",
|
||||||
"system": "\u7cfb\u7edf",
|
"system": "\u7cfb\u7edf",
|
||||||
"light": "\u6d45\u8272",
|
"light": "\u6d45\u8272",
|
||||||
"dark": "\u6df1\u8272",
|
"dark": "\u6df1\u8272",
|
||||||
@@ -147,24 +147,45 @@
|
|||||||
"languageDefault": "\u9ed8\u8ba4",
|
"languageDefault": "\u9ed8\u8ba4",
|
||||||
"languageEnglish": "English",
|
"languageEnglish": "English",
|
||||||
"languageChinese": "\u4e2d\u6587",
|
"languageChinese": "\u4e2d\u6587",
|
||||||
"localData": "本地数据",
|
"preferencesTitle": "\u504f\u597d",
|
||||||
"focusDataLabel": "专注",
|
"preferencesDetail": "\u8ba9\u754c\u9762\u66f4\u987a\u624b",
|
||||||
"habitsDataLabel": "习惯",
|
"localData": "\u672c\u5730\u8bb0\u5f55",
|
||||||
"notesDataLabel": "记录",
|
"localDataDetail": "\u53ea\u5728\u672c\u673a",
|
||||||
"clearToday": "清理今日",
|
"personalGardenTitle": "\u4f60\u7684\u4e13\u6ce8\u82b1\u56ed",
|
||||||
"clearTodaySubtitle": "只移除今天的进度。",
|
"personalGardenSubtitle": "\u6bcf\u4e00\u6b21\u5c0f\u4e13\u6ce8\uff0c\u90fd\u4f1a\u7559\u4e0b\u53ef\u56de\u770b\u7684\u75d5\u8ff9\u3002",
|
||||||
"privacySubtitle": "\u6570\u636e\u53ea\u4fdd\u5b58\u5728\u672c\u673a\u3002",
|
"focusDataLabel": "\u4e13\u6ce8",
|
||||||
"deleteAllLocalData": "删除全部本地数据",
|
"habitsDataLabel": "\u4e60\u60ef",
|
||||||
"deleteAllLocalDataSubtitle": "重置这台设备上的 Focus Seed。",
|
"notesDataLabel": "\u8bb0\u5f55",
|
||||||
"todayDataCleared": "今日数据已清理",
|
"clearToday": "\u6e05\u7406\u4eca\u5929",
|
||||||
"localDataCleared": "本地数据已删除",
|
"clearTodaySubtitle": "\u53ea\u91cd\u7f6e\u4eca\u5929\u7684\u95ed\u73af\u3002",
|
||||||
"journeyTitle": "\u4eca\u65e5\u8fdb\u5c55",
|
"privacySubtitle": "\u65e0\u8d26\u53f7\uff0c\u65e0\u4e91\u540c\u6b65\u3002",
|
||||||
"journeyStatsTitle": "本周",
|
"deleteAllLocalData": "\u5220\u9664\u5168\u90e8\u6570\u636e",
|
||||||
"weekFocusLabel": "专注",
|
"deleteAllLocalDataSubtitle": "\u79fb\u9664\u8fd9\u53f0\u8bbe\u5907\u4e0a\u7684 Focus Seed \u8bb0\u5f55\u3002",
|
||||||
"streakLabel": "连续",
|
"todayDataCleared": "\u4eca\u65e5\u6570\u636e\u5df2\u6e05\u7406",
|
||||||
"activeDaysLabel": "活跃",
|
"localDataCleared": "\u672c\u5730\u6570\u636e\u5df2\u5220\u9664",
|
||||||
"habitRateLabel": "习惯",
|
"journeyTitle": "\u8fdb\u5c55",
|
||||||
"daysValue": "{count}天",
|
"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": {
|
"@daysValue": {
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"count": {
|
"count": {
|
||||||
@@ -180,15 +201,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"viewHistory": "查看历史",
|
"viewHistory": "\u8bb0\u5f55\u5e93",
|
||||||
"viewHistorySubtitle": "回看专注记录和复盘。",
|
"viewHistorySubtitle": "\u67e5\u770b\u4e13\u6ce8\u8bb0\u5f55\u548c\u590d\u76d8\u3002",
|
||||||
"historyTitle": "历史",
|
"historyTitle": "\u5386\u53f2",
|
||||||
"focusHistory": "专注",
|
"focusHistory": "\u4e13\u6ce8",
|
||||||
"notesHistory": "复盘",
|
"notesHistory": "\u590d\u76d8",
|
||||||
"emptyFocusHistoryTitle": "还没有专注记录",
|
"emptyFocusHistoryTitle": "\u8fd8\u6ca1\u6709\u4e13\u6ce8\u8bb0\u5f55",
|
||||||
"emptyFocusHistoryBody": "从今日页开始一轮专注,完成后会出现在这里。",
|
"emptyFocusHistoryBody": "\u4ece\u4eca\u65e5\u9875\u5f00\u59cb\u4e00\u8f6e\u4e13\u6ce8\uff0c\u5b8c\u6210\u540e\u4f1a\u51fa\u73b0\u5728\u8fd9\u91cc\u3002",
|
||||||
"emptyNotesHistoryTitle": "还没有复盘",
|
"emptyNotesHistoryTitle": "\u8fd8\u6ca1\u6709\u590d\u76d8",
|
||||||
"emptyNotesHistoryBody": "专注后保存一句复盘,让进展更清晰。",
|
"emptyNotesHistoryBody": "\u4e13\u6ce8\u540e\u4fdd\u5b58\u4e00\u53e5\u590d\u76d8\uff0c\u8ba9\u8fdb\u5c55\u66f4\u6e05\u6670\u3002",
|
||||||
"recentFocus": "\u6700\u8fd1\u4e13\u6ce8",
|
"recentFocus": "\u6700\u8fd1\u4e13\u6ce8",
|
||||||
"sessionsTotal": "\u5171 {count} \u6b21",
|
"sessionsTotal": "\u5171 {count} \u6b21",
|
||||||
"@sessionsTotal": {
|
"@sessionsTotal": {
|
||||||
|
|||||||
@@ -194,13 +194,13 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @todayIntentionLabel.
|
/// No description provided for @todayIntentionLabel.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'TODAY\'S INTENTION'**
|
/// **'TODAY\'S FOCUS'**
|
||||||
String get todayIntentionLabel;
|
String get todayIntentionLabel;
|
||||||
|
|
||||||
/// No description provided for @chooseOneOutcome.
|
/// No description provided for @chooseOneOutcome.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Choose one outcome'**
|
/// **'Name today\'s focus'**
|
||||||
String get chooseOneOutcome;
|
String get chooseOneOutcome;
|
||||||
|
|
||||||
/// No description provided for @focusLabel.
|
/// No description provided for @focusLabel.
|
||||||
@@ -248,7 +248,7 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @setTodaysIntention.
|
/// No description provided for @setTodaysIntention.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Set today\'s intention'**
|
/// **'Set today\'s focus'**
|
||||||
String get setTodaysIntention;
|
String get setTodaysIntention;
|
||||||
|
|
||||||
/// No description provided for @pause.
|
/// No description provided for @pause.
|
||||||
@@ -512,7 +512,7 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @clearTodayDataBody.
|
/// No description provided for @clearTodayDataBody.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// 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;
|
String get clearTodayDataBody;
|
||||||
|
|
||||||
/// No description provided for @clearAllDataTitle.
|
/// No description provided for @clearAllDataTitle.
|
||||||
@@ -542,7 +542,7 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @privacyTitle.
|
/// No description provided for @privacyTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Privacy'**
|
/// **'Privacy & data'**
|
||||||
String get privacyTitle;
|
String get privacyTitle;
|
||||||
|
|
||||||
/// No description provided for @privacyBodyOne.
|
/// No description provided for @privacyBodyOne.
|
||||||
@@ -626,19 +626,19 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @meTitle.
|
/// No description provided for @meTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Your space'**
|
/// **'Your garden'**
|
||||||
String get meTitle;
|
String get meTitle;
|
||||||
|
|
||||||
/// No description provided for @meSubtitle.
|
/// No description provided for @meSubtitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Private by default. No account required.'**
|
/// **'Preferences and records stay on this device.'**
|
||||||
String get meSubtitle;
|
String get meSubtitle;
|
||||||
|
|
||||||
/// No description provided for @appearance.
|
/// No description provided for @appearance.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Appearance'**
|
/// **'Theme'**
|
||||||
String get appearance;
|
String get appearance;
|
||||||
|
|
||||||
/// No description provided for @system.
|
/// No description provided for @system.
|
||||||
@@ -689,12 +689,42 @@ abstract class AppLocalizations {
|
|||||||
/// **'Chinese'**
|
/// **'Chinese'**
|
||||||
String get languageChinese;
|
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.
|
/// No description provided for @localData.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Local data'**
|
/// **'Local records'**
|
||||||
String get localData;
|
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.
|
/// No description provided for @focusDataLabel.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -722,25 +752,25 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @clearTodaySubtitle.
|
/// No description provided for @clearTodaySubtitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Remove only today\'s progress.'**
|
/// **'Reset only today\'s loop.'**
|
||||||
String get clearTodaySubtitle;
|
String get clearTodaySubtitle;
|
||||||
|
|
||||||
/// No description provided for @privacySubtitle.
|
/// No description provided for @privacySubtitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Everything stays on this device.'**
|
/// **'No account or cloud sync.'**
|
||||||
String get privacySubtitle;
|
String get privacySubtitle;
|
||||||
|
|
||||||
/// No description provided for @deleteAllLocalData.
|
/// No description provided for @deleteAllLocalData.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Delete all local data'**
|
/// **'Delete all data'**
|
||||||
String get deleteAllLocalData;
|
String get deleteAllLocalData;
|
||||||
|
|
||||||
/// No description provided for @deleteAllLocalDataSubtitle.
|
/// No description provided for @deleteAllLocalDataSubtitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Reset Focus Seed on this device.'**
|
/// **'Remove this device\'s Focus Seed records.'**
|
||||||
String get deleteAllLocalDataSubtitle;
|
String get deleteAllLocalDataSubtitle;
|
||||||
|
|
||||||
/// No description provided for @todayDataCleared.
|
/// No description provided for @todayDataCleared.
|
||||||
@@ -758,13 +788,67 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @journeyTitle.
|
/// No description provided for @journeyTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Today\'s journey'**
|
/// **'Progress'**
|
||||||
String get journeyTitle;
|
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.
|
/// No description provided for @journeyStatsTitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'This week'**
|
/// **'7-day rhythm'**
|
||||||
String get journeyStatsTitle;
|
String get journeyStatsTitle;
|
||||||
|
|
||||||
/// No description provided for @weekFocusLabel.
|
/// No description provided for @weekFocusLabel.
|
||||||
@@ -806,13 +890,13 @@ abstract class AppLocalizations {
|
|||||||
/// No description provided for @viewHistory.
|
/// No description provided for @viewHistory.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'View history'**
|
/// **'Record library'**
|
||||||
String get viewHistory;
|
String get viewHistory;
|
||||||
|
|
||||||
/// No description provided for @viewHistorySubtitle.
|
/// No description provided for @viewHistorySubtitle.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Review focus sessions and reflections.'**
|
/// **'Open focus sessions and reflections.'**
|
||||||
String get viewHistorySubtitle;
|
String get viewHistorySubtitle;
|
||||||
|
|
||||||
/// No description provided for @historyTitle.
|
/// No description provided for @historyTitle.
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get todayIntentionLabel => 'TODAY\'S INTENTION';
|
String get todayIntentionLabel => 'TODAY\'S FOCUS';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chooseOneOutcome => 'Choose one outcome';
|
String get chooseOneOutcome => 'Name today\'s focus';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get focusLabel => 'FOCUS';
|
String get focusLabel => 'FOCUS';
|
||||||
@@ -86,7 +86,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get startAnotherSession => 'Start another session';
|
String get startAnotherSession => 'Start another session';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get setTodaysIntention => 'Set today\'s intention';
|
String get setTodaysIntention => 'Set today\'s focus';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get pause => 'Pause';
|
String get pause => 'Pause';
|
||||||
@@ -228,7 +228,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get clearTodayDataTitle => 'Clear today?';
|
String get clearTodayDataTitle => 'Clear today?';
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
String get clearAllDataTitle => 'Delete all local data?';
|
String get clearAllDataTitle => 'Delete all local data?';
|
||||||
@@ -243,7 +243,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get deleteAll => 'Delete all';
|
String get deleteAll => 'Delete all';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privacyTitle => 'Privacy';
|
String get privacyTitle => 'Privacy & data';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privacyBodyOne => 'Focus Seed stores your intention, habits, focus sessions, reflections, and settings locally on this device. This version has no sign-in, cloud sync, ads, analytics SDK, or billing SDK.';
|
String get privacyBodyOne => 'Focus Seed stores your intention, habits, focus sessions, reflections, and settings locally on this device. This version has no sign-in, cloud sync, ads, analytics SDK, or billing SDK.';
|
||||||
@@ -285,13 +285,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get moodTired => 'Tired';
|
String get moodTired => 'Tired';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get meTitle => 'Your space';
|
String get meTitle => 'Your garden';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get meSubtitle => 'Private by default. No account required.';
|
String get meSubtitle => 'Preferences and records stay on this device.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get appearance => 'Appearance';
|
String get appearance => 'Theme';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get system => 'System';
|
String get system => 'System';
|
||||||
@@ -318,7 +318,22 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get languageChinese => 'Chinese';
|
String get languageChinese => 'Chinese';
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
String get focusDataLabel => 'Focus';
|
String get focusDataLabel => 'Focus';
|
||||||
@@ -333,16 +348,16 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get clearToday => 'Clear today';
|
String get clearToday => 'Clear today';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get clearTodaySubtitle => 'Remove only today\'s progress.';
|
String get clearTodaySubtitle => 'Reset only today\'s loop.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privacySubtitle => 'Everything stays on this device.';
|
String get privacySubtitle => 'No account or cloud sync.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get deleteAllLocalData => 'Delete all local data';
|
String get deleteAllLocalData => 'Delete all data';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get deleteAllLocalDataSubtitle => 'Reset Focus Seed on this device.';
|
String get deleteAllLocalDataSubtitle => 'Remove this device\'s Focus Seed records.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get todayDataCleared => 'Today\'s data cleared';
|
String get todayDataCleared => 'Today\'s data cleared';
|
||||||
@@ -351,10 +366,39 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get localDataCleared => 'Local data deleted';
|
String get localDataCleared => 'Local data deleted';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get journeyTitle => 'Today\'s journey';
|
String get journeyTitle => 'Progress';
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
String get weekFocusLabel => 'Focus';
|
String get weekFocusLabel => 'Focus';
|
||||||
@@ -379,10 +423,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewHistory => 'View history';
|
String get viewHistory => 'Record library';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewHistorySubtitle => 'Review focus sessions and reflections.';
|
String get viewHistorySubtitle => 'Open focus sessions and reflections.';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get historyTitle => 'History';
|
String get historyTitle => 'History';
|
||||||
|
|||||||
@@ -59,10 +59,10 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get todayIntentionLabel => '今日目标';
|
String get todayIntentionLabel => '今日重点';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get chooseOneOutcome => '选一个结果';
|
String get chooseOneOutcome => '写下今天的重点';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get focusLabel => '专注';
|
String get focusLabel => '专注';
|
||||||
@@ -86,7 +86,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get startAnotherSession => '再来一轮';
|
String get startAnotherSession => '再来一轮';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get setTodaysIntention => '设置今日目标';
|
String get setTodaysIntention => '设置今日重点';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get pause => '暂停';
|
String get pause => '暂停';
|
||||||
@@ -228,7 +228,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get clearTodayDataTitle => '清理今日数据?';
|
String get clearTodayDataTitle => '清理今日数据?';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get clearTodayDataBody => '只清除今天的目标、打卡、专注记录和复盘,过去日期的数据会保留。';
|
String get clearTodayDataBody => '只重置今天的重点、打卡、专注记录和复盘,过去日期的数据会保留。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get clearAllDataTitle => '删除全部本地数据?';
|
String get clearAllDataTitle => '删除全部本地数据?';
|
||||||
@@ -243,7 +243,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get deleteAll => '全部删除';
|
String get deleteAll => '全部删除';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privacyTitle => '隐私';
|
String get privacyTitle => '隐私与数据';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privacyBodyOne => 'Focus Seed 只在本机保存目标、习惯、专注记录、复盘和设置。当前版本没有登录、云同步、广告、分析 SDK 或付费 SDK。';
|
String get privacyBodyOne => 'Focus Seed 只在本机保存目标、习惯、专注记录、复盘和设置。当前版本没有登录、云同步、广告、分析 SDK 或付费 SDK。';
|
||||||
@@ -285,13 +285,13 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get moodTired => '疲惫';
|
String get moodTired => '疲惫';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get meTitle => '我的空间';
|
String get meTitle => '我的花园';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get meSubtitle => '本地私有,无需账号。';
|
String get meSubtitle => '偏好和记录都只保存在本机。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get appearance => '外观';
|
String get appearance => '主题';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get system => '系统';
|
String get system => '系统';
|
||||||
@@ -318,7 +318,22 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get languageChinese => '中文';
|
String get languageChinese => '中文';
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
String get focusDataLabel => '专注';
|
String get focusDataLabel => '专注';
|
||||||
@@ -330,19 +345,19 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get notesDataLabel => '记录';
|
String get notesDataLabel => '记录';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get clearToday => '清理今日';
|
String get clearToday => '清理今天';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get clearTodaySubtitle => '只移除今天的进度。';
|
String get clearTodaySubtitle => '只重置今天的闭环。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get privacySubtitle => '数据只保存在本机。';
|
String get privacySubtitle => '无账号,无云同步。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get deleteAllLocalData => '删除全部本地数据';
|
String get deleteAllLocalData => '删除全部数据';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get deleteAllLocalDataSubtitle => '重置这台设备上的 Focus Seed。';
|
String get deleteAllLocalDataSubtitle => '移除这台设备上的 Focus Seed 记录。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get todayDataCleared => '今日数据已清理';
|
String get todayDataCleared => '今日数据已清理';
|
||||||
@@ -351,10 +366,39 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get localDataCleared => '本地数据已删除';
|
String get localDataCleared => '本地数据已删除';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get journeyTitle => '今日进展';
|
String get journeyTitle => '进展';
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
String get weekFocusLabel => '专注';
|
String get weekFocusLabel => '专注';
|
||||||
@@ -379,10 +423,10 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewHistory => '查看历史';
|
String get viewHistory => '记录库';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get viewHistorySubtitle => '回看专注记录和复盘。';
|
String get viewHistorySubtitle => '查看专注记录和复盘。';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get historyTitle => '历史';
|
String get historyTitle => '历史';
|
||||||
|
|||||||
@@ -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
@@ -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/app/focus_seed_app.dart';
|
||||||
import 'package:focus_seed/src/data/focus_seed_store.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/data/local/focus_seed_database.dart';
|
||||||
|
import 'package:focus_seed/src/models/language_preference.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@@ -20,10 +21,10 @@ void main() {
|
|||||||
await pumpFocusSeedApp(tester, locale: const Locale('en'));
|
await pumpFocusSeedApp(tester, locale: const Locale('en'));
|
||||||
|
|
||||||
expect(find.text('Focus Seed'), findsOneWidget);
|
expect(find.text('Focus Seed'), findsOneWidget);
|
||||||
expect(find.text("TODAY'S INTENTION"), findsOneWidget);
|
expect(find.text("TODAY'S FOCUS"), findsOneWidget);
|
||||||
expect(find.text("Set today's intention"), 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.pumpAndSettle();
|
||||||
await tester.enterText(
|
await tester.enterText(
|
||||||
find.byType(TextField),
|
find.byType(TextField),
|
||||||
@@ -41,12 +42,11 @@ void main() {
|
|||||||
|
|
||||||
await tester.tap(find.text('Journey'));
|
await tester.tap(find.text('Journey'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.text("Today's journey"), findsOneWidget);
|
expect(find.text('Progress'), findsOneWidget);
|
||||||
|
|
||||||
await tester.tap(find.text('Me'));
|
await tester.tap(find.text('Me'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.text('Your space'), findsOneWidget);
|
expect(find.text('Your garden'), findsOneWidget);
|
||||||
expect(find.text('Appearance'), findsOneWidget);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Me manages local data without JSON tools', (tester) async {
|
testWidgets('Me manages local data without JSON tools', (tester) async {
|
||||||
@@ -55,10 +55,15 @@ void main() {
|
|||||||
await tester.tap(find.text('Me'));
|
await tester.tap(find.text('Me'));
|
||||||
await tester.pumpAndSettle();
|
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);
|
expect(find.text('Clear today'), findsOneWidget);
|
||||||
await tester.scrollUntilVisible(find.text('Delete all local data'), 180);
|
await tester.scrollUntilVisible(
|
||||||
expect(find.text('Delete all local data'), findsOneWidget);
|
find.text('Delete all data'),
|
||||||
|
180,
|
||||||
|
scrollable: meScroll,
|
||||||
|
);
|
||||||
|
expect(find.text('Delete all data'), findsOneWidget);
|
||||||
expect(find.text('Export JSON'), findsNothing);
|
expect(find.text('Export JSON'), findsNothing);
|
||||||
expect(find.text('Import JSON'), findsNothing);
|
expect(find.text('Import JSON'), findsNothing);
|
||||||
});
|
});
|
||||||
@@ -71,19 +76,19 @@ void main() {
|
|||||||
|
|
||||||
final journeyScroll = find.byType(Scrollable).first;
|
final journeyScroll = find.byType(Scrollable).first;
|
||||||
await tester.scrollUntilVisible(
|
await tester.scrollUntilVisible(
|
||||||
find.text('This week'),
|
find.text('7-day rhythm'),
|
||||||
180,
|
180,
|
||||||
scrollable: journeyScroll,
|
scrollable: journeyScroll,
|
||||||
);
|
);
|
||||||
expect(find.text('This week'), findsOneWidget);
|
expect(find.text('7-day rhythm'), findsOneWidget);
|
||||||
await tester.scrollUntilVisible(
|
await tester.scrollUntilVisible(
|
||||||
find.text('View history'),
|
find.text('Record library'),
|
||||||
180,
|
180,
|
||||||
scrollable: journeyScroll,
|
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();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.text('History'), findsOneWidget);
|
expect(find.text('History'), findsOneWidget);
|
||||||
@@ -117,8 +122,8 @@ void main() {
|
|||||||
await pumpFocusSeedApp(tester);
|
await pumpFocusSeedApp(tester);
|
||||||
|
|
||||||
expect(find.text('Focus Seed'), findsOneWidget);
|
expect(find.text('Focus Seed'), findsOneWidget);
|
||||||
expect(find.text('\u4eca\u65e5\u76ee\u6807'), findsOneWidget);
|
expect(find.text('\u4eca\u65e5\u91cd\u70b9'), findsOneWidget);
|
||||||
expect(find.text('\u8bbe\u7f6e\u4eca\u65e5\u76ee\u6807'), findsOneWidget);
|
expect(find.text('\u8bbe\u7f6e\u4eca\u65e5\u91cd\u70b9'), findsOneWidget);
|
||||||
await tester.scrollUntilVisible(
|
await tester.scrollUntilVisible(
|
||||||
find.text('\u8ba1\u5212\u4e00\u4e2a\u660e\u786e\u4efb\u52a1'),
|
find.text('\u8ba1\u5212\u4e00\u4e2a\u660e\u786e\u4efb\u52a1'),
|
||||||
180,
|
180,
|
||||||
@@ -138,11 +143,15 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.text('\u8bed\u8a00'), findsOneWidget);
|
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();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.text('Your space'), findsOneWidget);
|
|
||||||
expect(find.text('Language'), findsOneWidget);
|
expect(find.text('Language'), findsOneWidget);
|
||||||
|
expect(find.text('Development only'), findsOneWidget);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user