inou/design/flutter/screens/styleguide_screen.dart

1334 lines
39 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Flutter Styleguide — matches inou.com/styleguide exactly
import 'package:flutter/material.dart';
import '../inou_theme.dart';
import '../widgets/widgets.dart';
class StyleguideScreen extends StatefulWidget {
const StyleguideScreen({super.key});
@override
State<StyleguideScreen> createState() => _StyleguideScreenState();
}
class _StyleguideScreenState extends State<StyleguideScreen> {
String? _selectedOption = 'Option 1';
String _selectedSex = 'male';
bool _checkboxValue = true;
String _selectedLLM = 'claude';
String _selectedUnits = 'metric';
bool _showGeneDetails = true;
bool _showVitalHistory = false;
bool _showNoteDetails = false;
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: InouTheme.bg,
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: InouTheme.maxWidth),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header
Text('Style Guide', style: InouTheme.pageTitle),
const SizedBox(height: 8),
Text(
'Design system components for inou',
style: InouTheme.intro,
),
const SizedBox(height: 32),
// Text Blocks
_buildTextBlocksSection(),
// Typography
_buildTypographySection(),
// Colors
_buildColorsSection(),
// Buttons
_buildButtonsSection(),
// Badges
_buildBadgesSection(),
// Messages
_buildMessagesSection(),
// Form Elements
_buildFormsSection(),
// Settings
_buildSettingsSection(),
// Profile Cards
_buildProfileCardsSection(),
// Data Cards (Imaging, Labs)
_buildDataCardsSection(),
// Genetics
_buildGeneticsSection(),
// Vitals
_buildVitalsSection(),
// Notes
_buildNotesSection(),
// Supplements
_buildSupplementsSection(),
// Peptides
_buildPeptidesSection(),
// Upload Area
_buildUploadSection(),
// Empty State
_buildEmptyStateSection(),
const SizedBox(height: 48),
],
),
),
),
),
);
}
Widget _buildTextBlocksSection() {
return InouCard(
title: 'Text Blocks',
indicatorColor: InouTheme.indicatorImaging,
child: Padding(
padding: const EdgeInsets.all(32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Your data. Your rules.',
style: InouTheme.pageTitle,
),
const SizedBox(height: 16),
RichText(
text: TextSpan(
style: InouTheme.bodyLight,
children: [
const TextSpan(text: 'We built '),
TextSpan(
text: 'inou',
style: TextStyle(
fontWeight: FontWeight.w700,
color: InouTheme.accent,
),
),
const TextSpan(
text: ' because health data is personal. Not personal like "preferences" — personal like your body, your history, your family. So we made privacy the foundation, not an afterthought.',
),
],
),
),
const SizedBox(height: 32),
Text(
'What we collect',
style: InouTheme.sectionTitle,
),
const SizedBox(height: 16),
Text(
'Account information.',
style: InouTheme.subsectionTitle,
),
const SizedBox(height: 8),
Text(
'Name, email address, date of birth, and sex. Date of birth and sex help provide accurate medical context — an MRI interpretation differs significantly between a 6-year-old and a 16-year-old.',
style: InouTheme.bodyLight,
),
],
),
),
);
}
Widget _buildTypographySection() {
return InouCard(
title: 'Typography Scale',
indicatorColor: InouTheme.indicatorLabs,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_TypographyRow('Page Title', InouTheme.pageTitle, '2.5rem / 700'),
_TypographyRow('Section Title', InouTheme.sectionTitle, '1.4rem / 600'),
_TypographyRow('Subsection Title', InouTheme.subsectionTitle, '1.1rem / 600'),
_TypographyRow(
'LABEL / CATEGORY',
InouTheme.label.copyWith(color: InouTheme.textSubtle),
'0.75rem / 600 / caps',
isUppercase: true,
),
_TypographyRow(
'Intro text — larger, lighter',
InouTheme.intro,
'1.15rem / 300',
),
_TypographyRow(
'Body light — long-form',
InouTheme.bodyLight,
'1rem / 300',
),
_TypographyRow(
'Body regular — UI labels',
InouTheme.body,
'1rem / 400',
),
_TypographyRow(
'Mono: 1,234,567.89',
InouTheme.mono,
'SF Mono',
),
],
),
),
);
}
Widget _buildColorsSection() {
return InouCard(
title: 'Colors',
indicatorColor: InouTheme.indicatorUploads,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
_ColorRow('Accent', InouTheme.accent, '#B45309'),
_ColorRow('Text', InouTheme.text, '#1C1917'),
_ColorRow('Text Muted', InouTheme.textMuted, '#78716C'),
_ColorRow('Background', InouTheme.bg, '#F8F7F6', hasBorder: true),
_ColorRow('Success', InouTheme.success, '#059669'),
_ColorRow('Danger', InouTheme.danger, '#DC2626'),
],
),
),
);
}
Widget _buildButtonsSection() {
return InouCard(
title: 'Buttons',
indicatorColor: InouTheme.indicatorVitals,
child: Padding(
padding: const EdgeInsets.all(24),
child: Wrap(
spacing: 12,
runSpacing: 12,
children: [
InouButton(text: 'Primary', onPressed: () {}),
InouButton(
text: 'Secondary',
variant: ButtonVariant.secondary,
onPressed: () {},
),
InouButton(
text: 'Danger',
variant: ButtonVariant.danger,
onPressed: () {},
),
InouButton(
text: 'Small',
size: ButtonSize.small,
onPressed: () {},
),
],
),
),
);
}
Widget _buildBadgesSection() {
return InouCard(
title: 'Badges',
indicatorColor: InouTheme.indicatorMedications,
child: Padding(
padding: const EdgeInsets.all(24),
child: Wrap(
spacing: 12,
runSpacing: 12,
children: const [
InouBadge(text: 'default'),
InouBadge(text: 'care', variant: BadgeVariant.care),
InouBadge(text: 'COMING SOON', variant: BadgeVariant.comingSoon),
InouBadge(text: 'processing', variant: BadgeVariant.processing),
],
),
),
);
}
Widget _buildMessagesSection() {
return InouCard(
title: 'Messages',
indicatorColor: InouTheme.indicatorRecords,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: const [
InouMessage(
message: 'Error message — something went wrong.',
type: MessageType.error,
),
SizedBox(height: 12),
InouMessage(
message: "Info message — here's some useful information.",
type: MessageType.info,
),
SizedBox(height: 12),
InouMessage(
message: 'Success message — operation completed.',
type: MessageType.success,
),
],
),
),
);
}
Widget _buildFormsSection() {
return InouCard(
title: 'Form Elements',
indicatorColor: InouTheme.indicatorJournal,
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const InouTextField(
label: 'Text Input',
placeholder: 'Enter text...',
),
const SizedBox(height: 16),
InouSelect<String>(
label: 'Select',
value: _selectedOption,
options: const [
InouSelectOption(value: 'Option 1', label: 'Option 1'),
InouSelectOption(value: 'Option 2', label: 'Option 2'),
InouSelectOption(value: 'Option 3', label: 'Option 3'),
],
onChanged: (v) => setState(() => _selectedOption = v),
),
const SizedBox(height: 16),
const InouTextField(
label: 'Code Input',
placeholder: '123456',
isCode: true,
maxLength: 6,
keyboardType: TextInputType.number,
),
const SizedBox(height: 16),
InouRadioGroup<String>(
value: _selectedSex,
options: const [
InouRadioOption(value: 'male', label: 'Male'),
InouRadioOption(value: 'female', label: 'Female'),
],
onChanged: (v) => setState(() => _selectedSex = v ?? 'male'),
),
const SizedBox(height: 16),
InouCheckbox(
value: _checkboxValue,
label: 'Can add data (supplements, notes, etc.)',
onChanged: (v) => setState(() => _checkboxValue = v ?? false),
),
],
),
),
);
}
Widget _buildSettingsSection() {
return InouCard(
title: 'Settings',
indicatorColor: InouTheme.indicatorPrivacy,
child: Column(
children: [
// LLM Selector
_SettingsRow(
label: 'Primary AI Assistant',
description: 'Used for "Ask AI" prompts and analysis',
child: Column(
children: [
_LLMOption(
icon: '🤖',
name: 'Claude (Anthropic)',
value: 'claude',
selected: _selectedLLM == 'claude',
onTap: () => setState(() => _selectedLLM = 'claude'),
),
_LLMOption(
icon: '💬',
name: 'ChatGPT (OpenAI)',
value: 'chatgpt',
selected: _selectedLLM == 'chatgpt',
onTap: () => setState(() => _selectedLLM = 'chatgpt'),
),
_LLMOption(
icon: '',
name: 'Grok (xAI)',
value: 'grok',
selected: _selectedLLM == 'grok',
onTap: () => setState(() => _selectedLLM = 'grok'),
),
],
),
),
const Divider(height: 1, color: InouTheme.border),
// Units Selector
_SettingsRow(
label: 'Units',
description: 'Measurement system for vitals',
child: InouSelect<String>(
value: _selectedUnits,
options: const [
InouSelectOption(value: 'metric', label: 'Metric (kg, cm, °C)'),
InouSelectOption(value: 'imperial', label: 'Imperial (lb, in, °F)'),
],
onChanged: (v) => setState(() => _selectedUnits = v ?? 'metric'),
),
),
],
),
);
}
Widget _buildProfileCardsSection() {
return InouCard(
title: 'Profile Cards',
indicatorColor: InouTheme.indicatorImaging,
child: Padding(
padding: const EdgeInsets.all(24),
child: LayoutBuilder(
builder: (context, constraints) {
final cardWidth = constraints.maxWidth > 700
? (constraints.maxWidth - 24) / 3
: constraints.maxWidth;
return Wrap(
spacing: 12,
runSpacing: 12,
children: [
SizedBox(
width: cardWidth,
height: 180,
child: InouProfileCard(
name: 'Johan Jongsma',
role: 'you',
dob: '1985-03-15',
sex: 'Male',
stats: const [
ProfileStat('📷', '3 studies'),
ProfileStat('🧪', '12 labs'),
ProfileStat('🧬', 'genome'),
],
onTap: () {},
),
),
SizedBox(
width: cardWidth,
height: 180,
child: InouProfileCard(
name: 'Sophia',
role: 'my role: Parent',
dob: '2017-01-01',
sex: 'Female',
isCare: true,
stats: const [
ProfileStat('📷', '16 studies'),
ProfileStat('🧪', '0 labs'),
],
onTap: () {},
),
),
SizedBox(
width: cardWidth,
height: 180,
child: InouAddCard(
label: 'Add dossier',
onTap: () {},
),
),
],
);
},
),
),
);
}
Widget _buildDataCardsSection() {
return Column(
children: [
// Imaging
InouCard(
title: 'Imaging',
subtitle: '16 studies · 4113 slices',
indicatorColor: InouTheme.indicatorImaging,
trailing: InouButton(
text: 'Open viewer',
variant: ButtonVariant.secondary,
size: ButtonSize.small,
onPressed: () {},
),
child: Column(
children: [
InouDataRow(
label: 'MRI BRAIN W/WO CONTRAST',
meta: '13 series',
date: '5/5/2022',
isExpandable: true,
children: [
InouChildRow(label: 'AX T1', meta: '24 slices'),
InouChildRow(label: 'AX T2 FLAIR', meta: '24 slices'),
InouChildRow(label: 'SAG T1', meta: '20 slices'),
],
),
InouDataRow(
label: 'XR CHEST AP ONLY',
date: '5/6/2022',
trailing: GestureDetector(
onTap: () {},
child: Text('', style: TextStyle(
color: InouTheme.accent,
fontSize: 18,
fontWeight: FontWeight.w500,
)),
),
),
],
),
),
// Labs
InouCard(
title: 'Labs',
subtitle: '4 panels · 23 results',
indicatorColor: InouTheme.indicatorLabs,
child: Column(
children: [
InouDataRow(
label: 'Complete Blood Count (CBC)',
meta: '8 tests',
date: '12/15/2024',
isExpandable: true,
initiallyExpanded: true,
children: const [
InouChildRow(
label: 'Hemoglobin',
value: '14.2 g/dL',
meta: '12.016.0',
),
InouChildRow(
label: 'White Blood Cells',
value: '7.8 K/µL',
meta: '4.511.0',
),
InouChildRow(
label: 'Platelets',
value: '142 K/µL',
meta: '150400',
valueColor: InouTheme.danger,
),
],
),
],
),
),
],
);
}
Widget _buildGeneticsSection() {
return InouCard(
title: 'Genetics',
subtitle: 'Medication Response · 47 variants',
indicatorColor: InouTheme.indicatorGenetics,
child: Column(
children: [
InouDataRow(
label: 'Medication Response',
meta: '47 variants',
isExpandable: true,
initiallyExpanded: _showGeneDetails,
onExpandChanged: (v) => setState(() => _showGeneDetails = v),
children: [
_GeneVariantRow(
gene: 'CYP2C19',
rsid: 'rs4244285',
allele: 'G;A',
status: 'intermediate',
summary: 'Intermediate metabolizer for clopidogrel (Plavix). May need dose adjustment or alternative medication.',
onAskAI: () {},
),
],
),
// Show more link
InkWell(
onTap: () {},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: const BoxDecoration(
border: Border(top: BorderSide(color: InouTheme.border)),
),
child: Center(
child: Text(
'Show all 47 variants in Medication Response →',
style: InouTheme.small.copyWith(color: InouTheme.accent),
),
),
),
),
InouDataRow(
label: 'Metabolism',
meta: '23 variants',
isExpandable: true,
),
InouDataRow(
label: 'Cardiovascular',
meta: '18 variants',
isExpandable: true,
),
],
),
);
}
Widget _buildVitalsSection() {
return InouCard(
title: 'Vitals',
subtitle: 'Self-reported measurements',
indicatorColor: InouTheme.indicatorVitals,
trailing: InouButton(
text: '+ Add',
variant: ButtonVariant.secondary,
size: ButtonSize.small,
onPressed: () {},
),
child: Column(
children: [
InouDataRow(
label: 'Temperature',
value: '37.2 °C',
meta: 'today',
leading: const InouNoteIcon(emoji: '🌡', color: InouTheme.danger),
isExpandable: true,
initiallyExpanded: _showVitalHistory,
onExpandChanged: (v) => setState(() => _showVitalHistory = v),
children: [
_VitalHistoryRow(date: 'Today, 8:30 AM', value: '37.2 °C'),
_VitalHistoryRow(date: 'Yesterday, 8:15 AM', value: '36.8 °C'),
_VitalHistoryRow(date: 'Dec 24, 7:45 AM', value: '37.0 °C'),
],
),
InouDataRow(
label: 'Weight',
value: '72.4 kg',
meta: 'today',
leading: InouNoteIcon(emoji: '', color: Colors.blue.shade600),
isExpandable: true,
),
InouDataRow(
label: 'Blood Pressure',
value: '118/76',
meta: 'yesterday',
leading: InouNoteIcon(emoji: '', color: Colors.pink.shade600),
isExpandable: true,
),
],
),
);
}
Widget _buildNotesSection() {
return InouCard(
title: 'Notes',
subtitle: 'Health journal entries',
indicatorColor: InouTheme.indicatorJournal,
trailing: InouButton(
text: '+ Add',
variant: ButtonVariant.secondary,
size: ButtonSize.small,
onPressed: () {},
),
child: Column(
children: [
InouDataRow(
label: 'Knee injury',
meta: '3 photos',
date: 'Dec 20',
leading: const InouNoteIcon(emoji: '📷', color: Color(0xFF6366F1)),
trailing: _NoteCategoryBadge(text: 'injury'),
isExpandable: true,
initiallyExpanded: _showNoteDetails,
onExpandChanged: (v) => setState(() => _showNoteDetails = v),
children: [
_NotePhotosRow(),
_NoteTimelineRow(
date: 'Dec 20, 3:45 PM',
text: 'Jim fell on his knee at soccer practice. Swelling visible, applied ice.',
),
_NoteTimelineRow(
date: 'Dec 22, 10:20 AM',
text: 'Swelling reduced. Still some bruising. Can walk without pain.',
),
_NoteTimelineRow(
date: 'Dec 26, 9:15 AM',
text: 'Almost fully healed. Light bruise remaining.',
),
],
),
InouDataRow(
label: 'Mild headache after workout',
date: 'Dec 25',
leading: const InouNoteIcon(emoji: '📝', color: InouTheme.accent),
isExpandable: true,
),
],
),
);
}
Widget _buildSupplementsSection() {
return InouCard(
title: 'Supplements',
subtitle: 'Daily routine',
indicatorColor: InouTheme.indicatorMedications,
trailing: InouButton(
text: '+ Add',
variant: ButtonVariant.secondary,
size: ButtonSize.small,
onPressed: () {},
),
child: Column(
children: const [
_SupplementRow(
name: 'Vitamin D3',
dose: '1 capsule',
amount: '5000 IU',
timing: 'morning, with food',
),
_SupplementRow(
name: 'Omega-3 Fish Oil',
dose: '2 capsules',
amount: '2000 mg EPA/DHA',
timing: 'morning, with food',
),
_SupplementRow(
name: 'Magnesium Glycinate',
dose: '2 capsules',
amount: '400 mg',
timing: 'evening',
),
_SupplementRow(
name: 'Liquid B12',
dose: '5 ml',
amount: '1000 mcg',
timing: 'morning',
),
],
),
);
}
Widget _buildPeptidesSection() {
return InouCard(
title: 'Peptides',
subtitle: 'Therapeutic protocols',
indicatorColor: InouTheme.indicatorMedications,
child: Column(
children: const [
_PeptideRow(
name: 'BPC-157',
dose: '250 mcg subQ · 2x daily',
endDate: 'until Jan 23, 2025',
status: 'active',
),
_PeptideRow(
name: 'TB-500',
dose: '2.5 mg subQ · 2x weekly',
endDate: 'until Feb 5, 2025',
status: 'active',
),
_PeptideRow(
name: 'BPC-157',
dose: '250 mcg subQ · 2x daily',
endDate: 'Aug 15 Sep 7, 2025',
status: 'completed',
),
],
),
);
}
Widget _buildUploadSection() {
return InouCard(
title: 'Upload Area',
indicatorColor: InouTheme.indicatorUploads,
child: Padding(
padding: const EdgeInsets.all(24),
child: Container(
padding: const EdgeInsets.all(40),
decoration: BoxDecoration(
border: Border.all(
color: InouTheme.border,
width: 2,
style: BorderStyle.solid,
),
borderRadius: InouTheme.borderRadiusLg,
),
child: Column(
children: [
Icon(
Icons.cloud_upload_outlined,
size: 32,
color: InouTheme.accent,
),
const SizedBox(height: 12),
Text(
'Click or drag files here',
style: InouTheme.body.copyWith(fontWeight: FontWeight.w500),
),
const SizedBox(height: 4),
Text(
'DICOM, PDF, CSV, VCF, and more',
style: InouTheme.small,
),
],
),
),
),
);
}
Widget _buildEmptyStateSection() {
return InouCard(
title: 'Empty State',
indicatorColor: InouTheme.indicatorRecords,
child: Container(
padding: const EdgeInsets.all(32),
decoration: BoxDecoration(
color: InouTheme.bgCard,
border: Border.all(color: InouTheme.border),
borderRadius: InouTheme.borderRadiusLg,
),
child: Center(
child: Text(
'No lab data',
style: InouTheme.body.copyWith(color: InouTheme.textMuted),
),
),
),
);
}
}
// ============================================
// Helper widgets
// ============================================
class _TypographyRow extends StatelessWidget {
final String text;
final TextStyle style;
final String spec;
final bool isUppercase;
const _TypographyRow(this.text, this.style, this.spec, {this.isUppercase = false});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Expanded(
child: Text(
isUppercase ? text.toUpperCase() : text,
style: style,
),
),
Text(
spec,
style: InouTheme.mono.copyWith(color: InouTheme.textMuted),
),
],
),
);
}
}
class _ColorRow extends StatelessWidget {
final String name;
final Color color;
final String hex;
final bool hasBorder;
const _ColorRow(this.name, this.color, this.hex, {this.hasBorder = false});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(6),
border: hasBorder ? Border.all(color: InouTheme.border) : null,
),
),
const SizedBox(width: 16),
Expanded(
child: Text(name, style: InouTheme.body.copyWith(fontWeight: FontWeight.w500)),
),
Text(hex, style: InouTheme.mono.copyWith(color: InouTheme.textMuted)),
],
),
);
}
}
class _SettingsRow extends StatelessWidget {
final String label;
final String description;
final Widget child;
const _SettingsRow({
required this.label,
required this.description,
required this.child,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 2,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: InouTheme.body.copyWith(fontWeight: FontWeight.w500)),
const SizedBox(height: 2),
Text(description, style: InouTheme.small),
],
),
),
const SizedBox(width: 24),
Expanded(
flex: 3,
child: child,
),
],
),
);
}
}
class _LLMOption extends StatelessWidget {
final String icon;
final String name;
final String value;
final bool selected;
final VoidCallback onTap;
const _LLMOption({
required this.icon,
required this.name,
required this.value,
required this.selected,
required this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: selected ? InouTheme.accentLight : InouTheme.bgCard,
border: Border.all(
color: selected ? InouTheme.accent : InouTheme.border,
),
borderRadius: BorderRadius.circular(6),
),
child: Row(
children: [
Container(
width: 24,
height: 24,
decoration: BoxDecoration(
color: InouTheme.bg,
borderRadius: BorderRadius.circular(4),
),
alignment: Alignment.center,
child: Text(icon, style: const TextStyle(fontSize: 12)),
),
const SizedBox(width: 8),
Text(
name,
style: InouTheme.body.copyWith(
color: selected ? InouTheme.accent : InouTheme.text,
),
),
],
),
),
);
}
}
class _GeneVariantRow extends StatelessWidget {
final String gene;
final String rsid;
final String allele;
final String status;
final String summary;
final VoidCallback onAskAI;
const _GeneVariantRow({
required this.gene,
required this.rsid,
required this.allele,
required this.status,
required this.summary,
required this.onAskAI,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
border: Border(top: BorderSide(color: InouTheme.border)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(gene, style: InouTheme.body.copyWith(fontWeight: FontWeight.w600)),
const SizedBox(width: 8),
Text(rsid, style: InouTheme.mono.copyWith(fontSize: 12, color: InouTheme.textMuted)),
const Spacer(),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: InouTheme.bg,
borderRadius: BorderRadius.circular(4),
),
child: Text(allele, style: InouTheme.mono.copyWith(fontWeight: FontWeight.w600)),
),
const SizedBox(width: 8),
Text(status, style: InouTheme.small.copyWith(color: InouTheme.accent)),
],
),
const SizedBox(height: 8),
Text(summary, style: InouTheme.small.copyWith(height: 1.4)),
const SizedBox(height: 12),
GestureDetector(
onTap: onAskAI,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: InouTheme.accentLight,
border: Border.all(color: InouTheme.accent),
borderRadius: BorderRadius.circular(4),
),
child: Text(
'Ask AI',
style: InouTheme.small.copyWith(
color: InouTheme.accent,
fontWeight: FontWeight.w500,
),
),
),
),
],
),
);
}
}
class _VitalHistoryRow extends StatelessWidget {
final String date;
final String value;
const _VitalHistoryRow({required this.date, required this.value});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(date, style: InouTheme.small),
Text(value, style: InouTheme.mono),
],
),
);
}
}
class _NoteCategoryBadge extends StatelessWidget {
final String text;
const _NoteCategoryBadge({required this.text});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: InouTheme.bg,
borderRadius: BorderRadius.circular(3),
),
child: Text(
text,
style: InouTheme.small.copyWith(
fontSize: 11.25,
color: InouTheme.textSubtle,
),
),
);
}
}
class _NotePhotosRow extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: InouTheme.bg,
border: Border(top: BorderSide(color: InouTheme.border)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
_PhotoPlaceholder(label: 'Dec 20, 3:45 PM'),
const SizedBox(width: 12),
_PhotoPlaceholder(label: 'Dec 22, 10:20 AM'),
const SizedBox(width: 12),
_PhotoPlaceholder(label: 'Dec 26, 9:15 AM'),
const SizedBox(width: 12),
_AddPhotoPlaceholder(),
],
),
],
),
);
}
}
class _PhotoPlaceholder extends StatelessWidget {
final String label;
const _PhotoPlaceholder({required this.label});
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: InouTheme.border,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: InouTheme.border),
),
alignment: Alignment.center,
child: const Text('🦵', style: TextStyle(fontSize: 24)),
),
const SizedBox(height: 4),
SizedBox(
width: 64,
child: Text(
label.split(', ').first,
style: InouTheme.small.copyWith(fontSize: 10.5),
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
),
),
],
);
}
}
class _AddPhotoPlaceholder extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
width: 64,
height: 64,
decoration: BoxDecoration(
color: InouTheme.bgCard,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: InouTheme.border, style: BorderStyle.solid),
),
alignment: Alignment.center,
child: Text('+', style: TextStyle(fontSize: 24, color: InouTheme.accent)),
),
const SizedBox(height: 4),
Text(
'Add photo',
style: InouTheme.small.copyWith(fontSize: 10.5, color: InouTheme.accent),
textAlign: TextAlign.center,
),
],
);
}
}
class _NoteTimelineRow extends StatelessWidget {
final String date;
final String text;
const _NoteTimelineRow({required this.date, required this.text});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: InouTheme.border, style: BorderStyle.solid)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(date, style: InouTheme.small),
const SizedBox(height: 4),
Text(text, style: InouTheme.body.copyWith(fontSize: 13.5)),
],
),
);
}
}
class _SupplementRow extends StatelessWidget {
final String name;
final String dose;
final String amount;
final String timing;
const _SupplementRow({
required this.name,
required this.dose,
required this.amount,
required this.timing,
});
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: InouTheme.border)),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(name, style: InouTheme.body.copyWith(fontWeight: FontWeight.w500)),
const SizedBox(height: 2),
Text(
'$dose · $amount',
style: InouTheme.small,
),
],
),
),
Text(timing, style: InouTheme.small.copyWith(color: InouTheme.textSubtle)),
],
),
);
}
}
class _PeptideRow extends StatelessWidget {
final String name;
final String dose;
final String endDate;
final String status;
const _PeptideRow({
required this.name,
required this.dose,
required this.endDate,
required this.status,
});
@override
Widget build(BuildContext context) {
final isActive = status == 'active';
return Container(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
decoration: const BoxDecoration(
border: Border(bottom: BorderSide(color: InouTheme.border)),
),
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(name, style: InouTheme.body.copyWith(fontWeight: FontWeight.w500)),
const SizedBox(width: 8),
Text(dose, style: InouTheme.small),
],
),
const SizedBox(height: 2),
Text(endDate, style: InouTheme.small.copyWith(color: InouTheme.textSubtle)),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: isActive ? InouTheme.successLight : InouTheme.bg,
borderRadius: BorderRadius.circular(4),
),
child: Text(
status,
style: InouTheme.small.copyWith(
color: isActive ? InouTheme.success : InouTheme.textMuted,
fontWeight: FontWeight.w500,
),
),
),
],
),
);
}
}