Add unified View Inputs screen for saved runs

Shows all profile and behavioral inputs in a single scrolling list
instead of paginated screens. Displays formatted values for all fields.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-02-21 19:50:32 -08:00
parent fb494a349c
commit 8bb1cc1e61
3 changed files with 338 additions and 4 deletions

View file

@ -5,6 +5,7 @@ import '../theme.dart';
import 'about_screen.dart';
import 'baseline_screen.dart';
import 'compare_runs_screen.dart';
import 'view_inputs_screen.dart';
class SavedRunDetailScreen extends StatefulWidget {
final SavedRun savedRun;
@ -459,13 +460,12 @@ class _SavedRunDetailScreenState extends State<SavedRunDetailScreen> {
}
void _viewInputs() {
// Navigate to read-only baseline screen with saved data
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => BaselineScreen(
readOnly: true,
initialProfile: _savedRun.profile,
builder: (_) => ViewInputsScreen(
profile: _savedRun.profile,
behaviors: _savedRun.behaviors,
),
),
);

View file

@ -7,4 +7,5 @@ export 'onboarding_screen.dart';
export 'results_screen.dart';
export 'saved_run_detail_screen.dart';
export 'saved_runs_screen.dart';
export 'view_inputs_screen.dart';
export 'welcome_screen.dart';

View file

@ -0,0 +1,333 @@
import 'package:flutter/material.dart';
import '../models/models.dart';
import '../theme.dart';
class ViewInputsScreen extends StatelessWidget {
final UserProfile profile;
final BehavioralInputs behaviors;
const ViewInputsScreen({
super.key,
required this.profile,
required this.behaviors,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Saved Inputs'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
),
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Profile Section
_buildSectionHeader(context, 'Profile'),
const SizedBox(height: 12),
_buildInputRow(context, 'Age', '${profile.age} years'),
_buildInputRow(context, 'Sex', _formatSex(profile.sex)),
_buildInputRow(context, 'Country', profile.country),
_buildInputRow(context, 'Height', _formatHeight(profile.heightCm)),
_buildInputRow(context, 'Weight', _formatWeight(profile.weightKg)),
_buildInputRow(context, 'BMI', profile.bmi.toStringAsFixed(1)),
if (profile.diagnoses.isNotEmpty)
_buildInputRow(
context,
'Conditions',
profile.diagnoses.map(_formatDiagnosis).join(', '),
),
const SizedBox(height: 24),
// Habits Section
_buildSectionHeader(context, 'Habits'),
const SizedBox(height: 12),
_buildInputRow(
context,
'Smoking',
_formatSmoking(behaviors.smoking, behaviors.cigarettesPerDay),
),
_buildInputRow(
context,
'Alcohol',
_formatAlcohol(behaviors.alcohol),
),
_buildInputRow(
context,
'Sleep',
_formatSleep(behaviors.sleepHours, behaviors.sleepConsistent),
),
_buildInputRow(
context,
'Physical Activity',
_formatActivity(behaviors.activity),
),
const SizedBox(height: 24),
// Lifestyle Section
_buildSectionHeader(context, 'Lifestyle'),
const SizedBox(height: 12),
_buildInputRow(
context,
'Diet Quality',
_formatDiet(behaviors.diet),
),
_buildInputRow(
context,
'Processed Food',
_formatProcessedFood(behaviors.processedFood),
),
_buildInputRow(
context,
'Drug Use',
_formatDrugUse(behaviors.drugUse),
),
_buildInputRow(
context,
'Social Connection',
_formatSocial(behaviors.social),
),
_buildInputRow(
context,
'Stress Level',
_formatStress(behaviors.stress),
),
_buildInputRow(
context,
'Driving',
_formatDriving(behaviors.driving),
),
_buildInputRow(
context,
'Work Hours',
_formatWorkHours(behaviors.workHours),
),
const SizedBox(height: 32),
],
),
),
),
);
}
Widget _buildSectionHeader(BuildContext context, String title) {
return Text(
title,
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.w600,
),
);
}
Widget _buildInputRow(BuildContext context, String label, String value) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
decoration: BoxDecoration(
color: AppColors.surfaceVariant,
borderRadius: BorderRadius.circular(12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
label,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: AppColors.textSecondary,
),
),
Flexible(
child: Text(
value,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.right,
),
),
],
),
),
);
}
String _formatSex(Sex sex) {
switch (sex) {
case Sex.male:
return 'Male';
case Sex.female:
return 'Female';
}
}
String _formatHeight(double cm) {
final feet = (cm / 30.48).floor();
final inches = ((cm / 2.54) % 12).round();
return "$feet'$inches\" (${cm.round()} cm)";
}
String _formatWeight(double kg) {
final lbs = (kg * 2.20462).round();
return '$lbs lbs (${kg.round()} kg)';
}
String _formatDiagnosis(Diagnosis d) {
switch (d) {
case Diagnosis.cardiovascular:
return 'Cardiovascular';
case Diagnosis.diabetes:
return 'Diabetes';
case Diagnosis.cancer:
return 'Cancer';
case Diagnosis.copd:
return 'COPD';
case Diagnosis.hypertension:
return 'Hypertension';
}
}
String _formatSmoking(SmokingStatus status, int cigarettesPerDay) {
switch (status) {
case SmokingStatus.never:
return 'Never smoked';
case SmokingStatus.former:
return 'Former smoker';
case SmokingStatus.current:
return 'Current ($cigarettesPerDay/day)';
}
}
String _formatAlcohol(AlcoholLevel level) {
switch (level) {
case AlcoholLevel.none:
return 'None';
case AlcoholLevel.light:
return 'Light (1-7/week)';
case AlcoholLevel.moderate:
return 'Moderate (8-14/week)';
case AlcoholLevel.heavy:
return 'Heavy (15-21/week)';
case AlcoholLevel.veryHeavy:
return 'Very Heavy (21+/week)';
}
}
String _formatSleep(double hours, bool consistent) {
final hoursStr = hours == hours.roundToDouble()
? hours.toInt().toString()
: hours.toString();
final consistentStr = consistent ? ', consistent' : ', inconsistent';
return '$hoursStr hours$consistentStr';
}
String _formatActivity(ActivityLevel level) {
switch (level) {
case ActivityLevel.sedentary:
return 'Sedentary';
case ActivityLevel.light:
return 'Light';
case ActivityLevel.moderate:
return 'Moderate';
case ActivityLevel.high:
return 'High';
}
}
String _formatDiet(DietQuality diet) {
switch (diet) {
case DietQuality.poor:
return 'Poor';
case DietQuality.fair:
return 'Fair';
case DietQuality.good:
return 'Good';
case DietQuality.excellent:
return 'Excellent';
}
}
String _formatProcessedFood(ProcessedFoodLevel level) {
switch (level) {
case ProcessedFoodLevel.daily:
return 'Daily';
case ProcessedFoodLevel.frequent:
return 'Frequent';
case ProcessedFoodLevel.occasional:
return 'Occasional';
case ProcessedFoodLevel.rarely:
return 'Rarely';
}
}
String _formatDrugUse(DrugUse use) {
switch (use) {
case DrugUse.none:
return 'None';
case DrugUse.occasional:
return 'Occasional';
case DrugUse.regular:
return 'Regular';
case DrugUse.daily:
return 'Daily';
}
}
String _formatSocial(SocialConnection level) {
switch (level) {
case SocialConnection.isolated:
return 'Isolated';
case SocialConnection.limited:
return 'Limited';
case SocialConnection.moderate:
return 'Moderate';
case SocialConnection.strong:
return 'Strong';
}
}
String _formatStress(StressLevel level) {
switch (level) {
case StressLevel.low:
return 'Low';
case StressLevel.moderate:
return 'Moderate';
case StressLevel.high:
return 'High';
case StressLevel.chronic:
return 'Chronic';
}
}
String _formatDriving(DrivingExposure level) {
switch (level) {
case DrivingExposure.low:
return 'Low (<50 mi/week)';
case DrivingExposure.moderate:
return 'Moderate (50-150 mi/week)';
case DrivingExposure.high:
return 'High (150-300 mi/week)';
case DrivingExposure.veryHigh:
return 'Very High (300+ mi/week)';
}
}
String _formatWorkHours(WorkHoursLevel level) {
switch (level) {
case WorkHoursLevel.normal:
return 'Normal (<40/week)';
case WorkHoursLevel.elevated:
return 'Elevated (40-55/week)';
case WorkHoursLevel.high:
return 'High (55-70/week)';
case WorkHoursLevel.extreme:
return 'Extreme (70+/week)';
}
}
}