import 'package:flutter/material.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; import '../data/models/message.dart'; class JournalPrepCard extends StatefulWidget { /// The first assistant message from today's journal — the daily prep /// (LLM-generated briefing-style opener), or null if not yet generated. final Message? message; /// Called when the user taps "Generate now". final VoidCallback? onGenerateNow; const JournalPrepCard({ super.key, required this.message, this.onGenerateNow, }); @override State createState() => _JournalPrepCardState(); } class _JournalPrepCardState extends State { bool _expanded = false; @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; final textTheme = Theme.of(context).textTheme; return Card( margin: const EdgeInsets.fromLTRB(12, 8, 12, 4), elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), side: BorderSide( color: scheme.outlineVariant.withValues(alpha: 0.5), width: 1, ), ), child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(LucideIcons.bookOpen, size: 18, color: scheme.primary), const SizedBox(width: 8), Text( _todayLabel(), style: textTheme.labelMedium?.copyWith( color: scheme.onSurfaceVariant, ), ), ], ), const SizedBox(height: 10), if (widget.message == null) ...[ Text( 'No prep yet today.', style: textTheme.bodyMedium?.copyWith( color: scheme.onSurfaceVariant, ), ), const SizedBox(height: 12), if (widget.onGenerateNow != null) FilledButton.tonal( onPressed: widget.onGenerateNow, child: const Text('Generate now'), ), ] else ...[ AnimatedSize( duration: const Duration(milliseconds: 250), curve: Curves.easeInOut, alignment: Alignment.topCenter, child: _expanded ? MarkdownBody(data: widget.message!.content) : _TruncatedMarkdown( data: widget.message!.content, maxLines: 5, ), ), const SizedBox(height: 8), GestureDetector( onTap: () => setState(() => _expanded = !_expanded), child: Text( _expanded ? 'Show less ↑' : 'Show more ↓', style: TextStyle( color: scheme.primary, fontSize: 13, fontWeight: FontWeight.w500, ), ), ), ], ], ), ), ); } String _todayLabel() { final now = DateTime.now(); const days = [ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ]; const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; return '${days[now.weekday - 1]}, ${months[now.month - 1]} ${now.day}'; } } /// Renders Markdown truncated to [maxLines] visible lines. class _TruncatedMarkdown extends StatelessWidget { final String data; final int maxLines; const _TruncatedMarkdown({required this.data, required this.maxLines}); @override Widget build(BuildContext context) { return ConstrainedBox( constraints: BoxConstraints(maxHeight: maxLines * 20.0), child: ClipRect(child: MarkdownBody(data: data)), ); } }