From cc255e9c9ab93df54e5d0ab2e3d4b33710935f7c Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Wed, 7 Jan 2026 15:57:32 +0300 Subject: [PATCH] vital sign detail page --- .../vital_sign/vital_sign_details_page.dart | 620 ++++++++++++++++++ .../vital_sign/vital_sign_page.dart | 80 ++- 2 files changed, 693 insertions(+), 7 deletions(-) create mode 100644 lib/presentation/vital_sign/vital_sign_details_page.dart diff --git a/lib/presentation/vital_sign/vital_sign_details_page.dart b/lib/presentation/vital_sign/vital_sign_details_page.dart new file mode 100644 index 0000000..fbbea64 --- /dev/null +++ b/lib/presentation/vital_sign/vital_sign_details_page.dart @@ -0,0 +1,620 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/common_models/data_points.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/vital_sign_respo_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital_sign_ui_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/graph/custom_graph.dart'; +import 'package:provider/provider.dart'; + +/// Which vital sign is being shown in the details screen. +enum VitalSignMetric { + bmi, + height, + weight, + bloodPressure, + temperature, + heartRate, + respiratoryRate, +} + +class VitalSignDetailsArgs { + final VitalSignMetric metric; + final String title; + final String icon; + final String unit; + + /// Optional bounds used for graph shading and labels. + final double? low; + final double? high; + + const VitalSignDetailsArgs({ + required this.metric, + required this.title, + required this.icon, + required this.unit, + this.low, + this.high, + }); +} + +class VitalSignDetailsPage extends StatefulWidget { + final VitalSignDetailsArgs args; + + const VitalSignDetailsPage({super.key, required this.args}); + + @override + State createState() => _VitalSignDetailsPageState(); +} + +class _VitalSignDetailsPageState extends State { + bool _isGraphVisible = true; + + VitalSignDetailsArgs get args => widget.args; + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: 'Vital Sign Details'.needTranslation, + child: Consumer( + builder: (context, viewModel, child) { + final latest = viewModel.vitalSignList.isNotEmpty ? viewModel.vitalSignList.first : null; + + final history = _buildSeries(viewModel.vitalSignList, args); + final latestValueText = _latestValueText(latest); + final status = _statusForLatest(latest); + final scheme = VitalSignUiModel.scheme(status: status, label: args.title); + + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _headerCard( + context, + title: args.title, + icon: args.icon, + valueText: latestValueText, + status: status, + scheme: scheme, + latestDate: latest?.vitalSignDate, + ), + SizedBox(height: 16.h), + + _whatIsThisResultCard(context), + SizedBox(height: 16.h), + + _historyCard(context, history: history), + SizedBox(height: 16.h), + + _nextStepsCard(context), + SizedBox(height: 32.h), + ], + ).paddingAll(24.h), + ); + }, + ), + ); + } + + Widget _headerCard( + BuildContext context, { + required String title, + required String icon, + required String valueText, + required String? status, + required VitalSignUiModel scheme, + required DateTime? latestDate, + }) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + padding: EdgeInsets.all(10.h), + decoration: BoxDecoration( + color: scheme.iconBg, + borderRadius: BorderRadius.circular(12.r), + ), + child: Utils.buildSvgWithAssets( + icon: icon, + width: 20.w, + height: 20.h, + iconColor: scheme.iconFg, + fit: BoxFit.contain, + ), + ), + SizedBox(width: 10.w), + title.toText18(isBold: true, weight: FontWeight.w600), + ], + ), + if (status != null) + Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 6.h), + decoration: BoxDecoration( + color: scheme.chipBg, + borderRadius: BorderRadius.circular(100.r), + ), + child: status.toText11( + color: scheme.chipFg, + weight: FontWeight.w500, + ), + ), + ], + ), + SizedBox(height: 10.h), + ( + latestDate != null + ? ('Result of ${latestDate.toString().split(' ').first}'.needTranslation) + : ('Result of --'.needTranslation) + ).toText11(weight: FontWeight.w500, color: AppColors.greyTextColor), + SizedBox(height: 12.h), + + valueText.toText28(isBold: true, color: AppColors.textColor, letterSpacing: -2), + + if (args.low != null || args.high != null) ...[ + SizedBox(height: 8.h), + Text( + _referenceText(context), + style: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ) + ] + ], + ), + ); + } + + String _referenceText(BuildContext context) { + if (args.low != null && args.high != null) { + return 'Reference range: ${args.low} – ${args.high} ${args.unit}'.needTranslation; + } + if (args.low != null) { + return 'Reference range: ≥ ${args.low} ${args.unit}'.needTranslation; + } + if (args.high != null) { + return 'Reference range: ≤ ${args.high} ${args.unit}'.needTranslation; + } + return ''; + } + + Widget _whatIsThisResultCard(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 'What is this result?'.needTranslation.toText16(weight: FontWeight.w600), + SizedBox(height: 8.h), + _descriptionText(context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, maxLine: 10), + SizedBox(height: 12.h), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.bulb, width: 16.w, height: 16.h, iconColor: AppColors.greyTextColor), + SizedBox(width: 6.w), + Expanded( + child: 'This information is for monitoring and not a diagnosis.'.needTranslation + .toText11(color: AppColors.greyTextColor, weight: FontWeight.w500, maxLine: 3), + ), + ], + ) + ], + ), + ); + } + + Widget _historyCard(BuildContext context, {required List history}) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 'History flowchart'.needTranslation.toText16(weight: FontWeight.w600), + Row( + children: [ + // toggle graph/list similar to lab result details + Utils.buildSvgWithAssets( + icon: _isGraphVisible ? AppAssets.graphIcon : AppAssets.listIcon, + width: 18.w, + height: 18.h, + iconColor: AppColors.greyTextColor, + ).onPress(() { + setState(() { + _isGraphVisible = !_isGraphVisible; + }); + }), + SizedBox(width: 10.w), + Utils.buildSvgWithAssets(icon: AppAssets.calendarGrey, width: 18.w, height: 18.h, iconColor: AppColors.greyTextColor), + ], + ), + ], + ), + SizedBox(height: 12.h), + if (history.isEmpty) + Utils.getNoDataWidget(context, noDataText: 'No history available'.needTranslation, isSmallWidget: true) + else if (_isGraphVisible) + _buildHistoryGraph(history) + else + _buildHistoryList(context, history), + ], + ), + ); + } + + Widget _buildHistoryGraph(List history) { + final minY = _minY(history); + final maxY = _maxY(history); + return CustomGraph( + dataPoints: history, + makeGraphBasedOnActualValue: true, + leftLabelReservedSize: 40, + showGridLines: true, + leftLabelInterval: _leftInterval(history), + maxY: maxY, + minY: minY, + maxX: history.length.toDouble() - .75, + horizontalInterval: .1, + leftLabelFormatter: (value) { + // Match the lab screen behavior: only show High/Low labels. + final v = double.parse(value.toStringAsFixed(1)); + if (args.high != null && v == args.high) { + return _axisLabel('High'.needTranslation); + } + if (args.low != null && v == args.low) { + return _axisLabel('Low'.needTranslation); + } + return const SizedBox.shrink(); + }, + getDrawingHorizontalLine: (value) { + value = double.parse(value.toStringAsFixed(1)); + if ((args.high != null && value == args.high) || (args.low != null && value == args.low)) { + return FlLine( + color: AppColors.bgGreenColor.withValues(alpha: 0.6), + strokeWidth: 1, + ); + } + return const FlLine(color: Colors.transparent, strokeWidth: 1); + }, + graphColor: AppColors.blackColor, + graphShadowColor: Colors.transparent, + graphGridColor: AppColors.graphGridColor.withValues(alpha: .4), + bottomLabelFormatter: (value, data) { + if (data.isEmpty) return const SizedBox.shrink(); + if (value == 0) return _bottomLabel(data[value.toInt()].label); + if (value == data.length - 1) return _bottomLabel(data[value.toInt()].label); + if (value == ((data.length - 1) / 2)) return _bottomLabel(data[value.toInt()].label); + return const SizedBox.shrink(); + }, + rangeAnnotations: _rangeAnnotations(history), + minX: (history.length == 1) ? null : -.2, + scrollDirection: Axis.horizontal, + height: 180.h, + ); + } + + Widget _buildHistoryList(BuildContext context, List history) { + final items = history.reversed.toList(); + final height = items.length < 3 ? items.length * 64.0 : 180.h; + return SizedBox( + height: height, + child: ListView.separated( + padding: EdgeInsets.zero, + itemCount: items.length, + separatorBuilder: (_, __) => Divider( + color: AppColors.borderOnlyColor.withValues(alpha: 0.1), + height: 1, + ), + itemBuilder: (context, index) { + final dp = items[index]; + return Padding( + padding: EdgeInsets.symmetric(vertical: 12.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + dp.displayTime.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + ('${dp.actualValue} ${dp.unitOfMeasurement ?? ''}').toText12( + color: AppColors.textColor, + fontWeight: FontWeight.w600, + ), + ], + ), + ); + }, + ), + ); + } + + double _minY(List points) { + // IMPORTANT: y-axis uses actual numeric values (from actualValue). + final values = points.map((e) => double.tryParse(e.actualValue) ?? 0).toList(); + final min = values.reduce((a, b) => a < b ? a : b); + final double boundLow = args.low ?? min; + return (min < boundLow ? min : boundLow) - 1; + } + + double _maxY(List points) { + // IMPORTANT: y-axis uses actual numeric values (from actualValue). + final values = points.map((e) => double.tryParse(e.actualValue) ?? 0).toList(); + final max = values.reduce((a, b) => a > b ? a : b); + final double boundHigh = args.high ?? max; + return (max > boundHigh ? max : boundHigh) + 1; + } + + double _leftInterval(List points) { + // Keep it stable; graph will mostly show just two labels. + final range = (_maxY(points) - _minY(points)).abs(); + if (range <= 0) return 1; + return (range / 4).clamp(1, 20); + } + + RangeAnnotations? _rangeAnnotations(List points) { + if (args.low == null && args.high == null) return null; + + final minY = _minY(points); + final maxY = _maxY(points); + + final List ranges = []; + + if (args.low != null) { + ranges.add( + HorizontalRangeAnnotation( + y1: minY, + y2: args.low!, + color: AppColors.highAndLow.withValues(alpha: 0.05), + ), + ); + } + + if (args.low != null && args.high != null) { + ranges.add( + HorizontalRangeAnnotation( + y1: args.low!, + y2: args.high!, + color: AppColors.bgGreenColor.withValues(alpha: 0.05), + ), + ); + } + + if (args.high != null) { + ranges.add( + HorizontalRangeAnnotation( + y1: args.high!, + y2: maxY, + color: AppColors.criticalLowAndHigh.withValues(alpha: 0.05), + ), + ); + } + + return RangeAnnotations(horizontalRangeAnnotations: ranges); + } + + List _buildSeries(List vitals, VitalSignDetailsArgs args) { + final List points = []; + + // Build a chronological series (oldest -> newest), skipping null/zero values. + final sorted = List.from(vitals); + sorted.sort((a, b) { + final ad = a.vitalSignDate ?? DateTime.fromMillisecondsSinceEpoch(0); + final bd = b.vitalSignDate ?? DateTime.fromMillisecondsSinceEpoch(0); + return ad.compareTo(bd); + }); + + double? metricValue(VitalSignResModel v) { + switch (args.metric) { + case VitalSignMetric.bmi: + return _toDouble(v.bodyMassIndex); + case VitalSignMetric.height: + return _toDouble(v.heightCm); + case VitalSignMetric.weight: + return _toDouble(v.weightKg); + case VitalSignMetric.temperature: + return _toDouble(v.temperatureCelcius); + case VitalSignMetric.heartRate: + return _toDouble(v.heartRate ?? v.pulseBeatPerMinute); + case VitalSignMetric.respiratoryRate: + return _toDouble(v.respirationBeatPerMinute); + case VitalSignMetric.bloodPressure: + // Graph only systolic for now (simple single-series). + return _toDouble(v.bloodPressureHigher); + } + } + + double index = 0; + for (final v in sorted) { + final mv = metricValue(v); + if (mv == null) continue; + if (mv == 0) continue; + + final dt = v.vitalSignDate ?? DateTime.now(); + final label = '${dt.day}/${dt.month}'; + + points.add( + DataPoint( + value: index, + label: label, + actualValue: mv.toStringAsFixed(0), + time: dt, + displayTime: '${dt.day}/${dt.month}/${dt.year}', + unitOfMeasurement: args.unit, + ), + ); + index += 1; + } + + return points; + } + + double? _toDouble(dynamic v) { + if (v == null) return null; + if (v is num) return v.toDouble(); + return double.tryParse(v.toString()); + } + + String _latestValueText(VitalSignResModel? latest) { + if (latest == null) return '--'; + + switch (args.metric) { + case VitalSignMetric.bmi: + final v = _toDouble(latest.bodyMassIndex); + return v == null ? '--' : v.toStringAsFixed(0); + case VitalSignMetric.height: + final v = _toDouble(latest.heightCm); + return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + case VitalSignMetric.weight: + final v = _toDouble(latest.weightKg); + return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + case VitalSignMetric.temperature: + final v = _toDouble(latest.temperatureCelcius); + return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + case VitalSignMetric.heartRate: + final v = _toDouble(latest.heartRate ?? latest.pulseBeatPerMinute); + return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + case VitalSignMetric.respiratoryRate: + final v = _toDouble(latest.respirationBeatPerMinute); + return v == null ? '--' : '${v.toStringAsFixed(0)} ${args.unit}'; + case VitalSignMetric.bloodPressure: + final s = _toDouble(latest.bloodPressureHigher); + final d = _toDouble(latest.bloodPressureLower); + if (s == null || d == null) return '--'; + return '${s.toStringAsFixed(0)}/${d.toStringAsFixed(0)}'; + } + } + + String? _statusForLatest(VitalSignResModel? latest) { + if (latest == null) return null; + + switch (args.metric) { + case VitalSignMetric.bmi: + return VitalSignUiModel.bmiStatus(latest.bodyMassIndex); + case VitalSignMetric.bloodPressure: + return VitalSignUiModel.bloodPressureStatus(systolic: latest.bloodPressureHigher, diastolic: latest.bloodPressureLower); + case VitalSignMetric.height: + return null; + case VitalSignMetric.weight: + return latest.weightKg != null ? 'Normal' : null; + case VitalSignMetric.temperature: + return null; + case VitalSignMetric.heartRate: + return (latest.heartRate ?? latest.pulseBeatPerMinute) != null ? 'Normal' : null; + case VitalSignMetric.respiratoryRate: + return latest.respirationBeatPerMinute != null ? 'Normal' : null; + } + } + + String _descriptionText(BuildContext context) { + switch (args.metric) { + case VitalSignMetric.bmi: + return 'BMI is a measurement based on height and weight that estimates body fat.'.needTranslation; + case VitalSignMetric.height: + return 'Height is measured in centimeters and is used to calculate BMI and dosage recommendations.'.needTranslation; + case VitalSignMetric.weight: + return 'Weight helps track overall health, nutrition, and changes over time.'.needTranslation; + case VitalSignMetric.bloodPressure: + return 'Blood pressure reflects the force of blood against artery walls. It is shown as systolic/diastolic.'.needTranslation; + case VitalSignMetric.temperature: + return 'Body temperature reflects how hot your body is and may change with infection or inflammation.'.needTranslation; + case VitalSignMetric.heartRate: + return 'Heart rate refers to the number of heart beats per minute.'.needTranslation; + case VitalSignMetric.respiratoryRate: + return 'Respiratory rate is the number of breaths taken per minute.'.needTranslation; + } + } + + String _nextStepsText(BuildContext context) { + switch (args.metric) { + case VitalSignMetric.bmi: + return 'Maintain a balanced diet and regular activity. If your BMI is high or low, consider consulting your doctor.'.needTranslation; + case VitalSignMetric.height: + return 'No action is needed unless your measurement looks incorrect. Update it during your next visit.'.needTranslation; + case VitalSignMetric.weight: + return 'Monitor weight changes. Sudden gain or loss may require medical advice.'.needTranslation; + case VitalSignMetric.bloodPressure: + return 'Keep tracking your blood pressure. High or low readings should be discussed with your doctor.'.needTranslation; + case VitalSignMetric.temperature: + return 'If you have a persistent fever or symptoms, contact your healthcare provider.'.needTranslation; + case VitalSignMetric.heartRate: + return 'Track your heart rate trends. If you feel dizziness or chest pain, seek medical care.'.needTranslation; + case VitalSignMetric.respiratoryRate: + return 'If you notice shortness of breath or abnormal breathing, seek medical advice.'.needTranslation; + } + } + + Widget _nextStepsCard(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 'What should I do next?'.needTranslation.toText16(weight: FontWeight.w600), + SizedBox(height: 8.h), + _nextStepsText(context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500, maxLine: 10), + ], + ), + ); + } + + Widget _axisLabel(String value) { + return Text( + value, + style: TextStyle( + fontWeight: FontWeight.w600, + fontFamily: 'Poppins', + fontSize: 8.f, + color: AppColors.textColor, + ), + ); + } + + Widget _bottomLabel(String label) { + return Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + label, + style: TextStyle( + fontSize: 8.f, + fontFamily: 'Poppins', + fontWeight: FontWeight.w600, + color: AppColors.labelTextColor, + ), + ), + ); + } +} diff --git a/lib/presentation/vital_sign/vital_sign_page.dart b/lib/presentation/vital_sign/vital_sign_page.dart index bd25641..d8b6d7e 100644 --- a/lib/presentation/vital_sign/vital_sign_page.dart +++ b/lib/presentation/vital_sign/vital_sign_page.dart @@ -12,6 +12,8 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/vital_sign_ui_model.dart'; +import 'package:hmg_patient_app_new/presentation/vital_sign/vital_sign_details_page.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; class VitalSignPage extends StatefulWidget { @@ -22,6 +24,13 @@ class VitalSignPage extends StatefulWidget { } class _VitalSignPageState extends State { + void _openDetails(VitalSignDetailsArgs args) { + Navigator.of(context).push( + CustomPageRoute( + page: VitalSignDetailsPage(args: args), + ), + ); + } @override void initState() { @@ -65,7 +74,14 @@ class _VitalSignPageState extends State { value: latestVitalSign?.bodyMassIndex?.toString() ?? '--', unit: '', status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex), - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.bmi, + title: 'BMI', + icon: AppAssets.bmiVital, + unit: '', + ), + ), ), SizedBox(height: 16.h), @@ -76,7 +92,14 @@ class _VitalSignPageState extends State { value: latestVitalSign?.heightCm?.toString() ?? '--', unit: 'cm', status: null, - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.height, + title: 'Height', + icon: AppAssets.heightVital, + unit: 'cm', + ), + ), ), SizedBox(height: 16.h), @@ -87,7 +110,14 @@ class _VitalSignPageState extends State { value: latestVitalSign?.weightKg?.toString() ?? '--', unit: 'kg', status: (latestVitalSign?.weightKg != null) ? 'Normal' : null, - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.weight, + title: 'Weight', + icon: AppAssets.weightVital, + unit: 'kg', + ), + ), ), SizedBox(height: 16.h), @@ -105,7 +135,16 @@ class _VitalSignPageState extends State { systolic: latestVitalSign?.bloodPressureHigher, diastolic: latestVitalSign?.bloodPressureLower, ), - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.bloodPressure, + title: 'Blood Pressure', + icon: AppAssets.bloodPressure, + unit: 'mmHg', + low: 90, + high: 140, + ), + ), ), SizedBox(height: 16.h), @@ -116,7 +155,16 @@ class _VitalSignPageState extends State { value: latestVitalSign?.temperatureCelcius?.toString() ?? '--', unit: '°C', status: null, - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.temperature, + title: 'Temperature', + icon: AppAssets.temperature, + unit: '°C', + low: 36.1, + high: 37.2, + ), + ), ), ], ), @@ -182,7 +230,16 @@ class _VitalSignPageState extends State { value: latestVitalSign?.heartRate?.toString() ?? latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--', unit: 'bpm', status: 'Normal', - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.heartRate, + title: 'Heart Rate', + icon: AppAssets.heart, + unit: 'bpm', + low: 60, + high: 100, + ), + ), ), ), ], @@ -197,7 +254,16 @@ class _VitalSignPageState extends State { value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--', unit: 'bpm', status: 'Normal', - onTap: () {}, + onTap: () => _openDetails( + const VitalSignDetailsArgs( + metric: VitalSignMetric.respiratoryRate, + title: 'Respiratory rate', + icon: AppAssets.respRate, + unit: 'bpm', + low: 12, + high: 20, + ), + ), ), ], ),