Files
gp/lib/src/features/journey/widgets/journey_overview_panel.dart
T

273 lines
8.1 KiB
Dart

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,
borderColor: scheme.outlineVariant.withValues(alpha: 0.45),
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
scheme.primary.withValues(alpha: 0.20),
scheme.surfaceContainerLowest.withValues(alpha: 0.96),
scheme.secondary.withValues(alpha: 0.14),
],
),
),
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 Stack(
children: [
const Positioned(
right: -18,
top: -20,
child: Opacity(
opacity: 0.58,
child: SeedIllustration(size: 128, hero: true),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 17, 106, 18),
child: textContent,
),
],
);
}
return Stack(
children: [
Positioned(
right: -28,
top: -22,
bottom: -18,
child: Opacity(
opacity: 0.96,
child: SeedIllustration(
size: constraints.maxWidth.clamp(146, 184).toDouble(),
hero: true,
),
),
),
Positioned(
right: 92,
bottom: 18,
child: _ProgressBadge(completed: completed),
),
Padding(
padding: const EdgeInsets.fromLTRB(18, 17, 168, 18),
child: textContent,
),
],
);
},
),
),
);
}
}
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: 16),
_ProgressSeeds(completed: completed, progressValue: progressValue),
const SizedBox(height: 12),
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),
),
],
),
],
);
}
}
class _ProgressSeeds extends StatelessWidget {
const _ProgressSeeds({required this.completed, required this.progressValue});
final int completed;
final double progressValue;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Semantics(
value: '${(progressValue * 100).round()}%',
child: Row(
children: List.generate(4, (index) {
final filled = index < completed;
return Expanded(
child: AnimatedContainer(
duration: const Duration(milliseconds: 220),
curve: Curves.easeOutCubic,
height: 10,
margin: EdgeInsets.only(right: index == 3 ? 0 : 7),
decoration: BoxDecoration(
color:
filled
? scheme.primary
: scheme.surfaceContainerHigh.withValues(alpha: 0.82),
borderRadius: BorderRadius.circular(99),
boxShadow:
filled
? [
BoxShadow(
color: scheme.primary.withValues(alpha: 0.28),
blurRadius: 10,
offset: const Offset(0, 4),
),
]
: null,
),
),
);
}),
),
);
}
}
class _ProgressBadge extends StatelessWidget {
const _ProgressBadge({required this.completed});
final int completed;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
final theme = Theme.of(context);
return DecoratedBox(
decoration: BoxDecoration(
color: scheme.surfaceContainerLowest.withValues(alpha: 0.82),
borderRadius: BorderRadius.circular(99),
border: Border.all(
color: scheme.outlineVariant.withValues(alpha: 0.65),
),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.10),
blurRadius: 14,
offset: const Offset(0, 7),
),
],
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 11, vertical: 7),
child: Text(
'$completed/4',
style: theme.textTheme.labelLarge?.copyWith(
color: scheme.primary,
fontWeight: FontWeight.w900,
),
),
),
);
}
}