vital sign detail page

pull/151/head
Sultan khan 1 week ago
parent d62324044a
commit cc255e9c9a

@ -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<VitalSignDetailsPage> createState() => _VitalSignDetailsPageState();
}
class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
bool _isGraphVisible = true;
VitalSignDetailsArgs get args => widget.args;
@override
Widget build(BuildContext context) {
return CollapsingListView(
title: 'Vital Sign Details'.needTranslation,
child: Consumer<HmgServicesViewModel>(
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<DataPoint> 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<DataPoint> 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<DataPoint> 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<DataPoint> 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<DataPoint> 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<DataPoint> 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<DataPoint> points) {
if (args.low == null && args.high == null) return null;
final minY = _minY(points);
final maxY = _maxY(points);
final List<HorizontalRangeAnnotation> 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<DataPoint> _buildSeries(List<VitalSignResModel> vitals, VitalSignDetailsArgs args) {
final List<DataPoint> points = [];
// Build a chronological series (oldest -> newest), skipping null/zero values.
final sorted = List<VitalSignResModel>.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,
),
),
);
}
}

@ -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/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.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/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'; import 'package:provider/provider.dart';
class VitalSignPage extends StatefulWidget { class VitalSignPage extends StatefulWidget {
@ -22,6 +24,13 @@ class VitalSignPage extends StatefulWidget {
} }
class _VitalSignPageState extends State<VitalSignPage> { class _VitalSignPageState extends State<VitalSignPage> {
void _openDetails(VitalSignDetailsArgs args) {
Navigator.of(context).push(
CustomPageRoute(
page: VitalSignDetailsPage(args: args),
),
);
}
@override @override
void initState() { void initState() {
@ -65,7 +74,14 @@ class _VitalSignPageState extends State<VitalSignPage> {
value: latestVitalSign?.bodyMassIndex?.toString() ?? '--', value: latestVitalSign?.bodyMassIndex?.toString() ?? '--',
unit: '', unit: '',
status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex), status: VitalSignUiModel.bmiStatus(latestVitalSign?.bodyMassIndex),
onTap: () {}, onTap: () => _openDetails(
const VitalSignDetailsArgs(
metric: VitalSignMetric.bmi,
title: 'BMI',
icon: AppAssets.bmiVital,
unit: '',
),
),
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
@ -76,7 +92,14 @@ class _VitalSignPageState extends State<VitalSignPage> {
value: latestVitalSign?.heightCm?.toString() ?? '--', value: latestVitalSign?.heightCm?.toString() ?? '--',
unit: 'cm', unit: 'cm',
status: null, status: null,
onTap: () {}, onTap: () => _openDetails(
const VitalSignDetailsArgs(
metric: VitalSignMetric.height,
title: 'Height',
icon: AppAssets.heightVital,
unit: 'cm',
),
),
), ),
SizedBox(height: 16.h), SizedBox(height: 16.h),
@ -87,7 +110,14 @@ class _VitalSignPageState extends State<VitalSignPage> {
value: latestVitalSign?.weightKg?.toString() ?? '--', value: latestVitalSign?.weightKg?.toString() ?? '--',
unit: 'kg', unit: 'kg',
status: (latestVitalSign?.weightKg != null) ? 'Normal' : null, 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), SizedBox(height: 16.h),
@ -105,7 +135,16 @@ class _VitalSignPageState extends State<VitalSignPage> {
systolic: latestVitalSign?.bloodPressureHigher, systolic: latestVitalSign?.bloodPressureHigher,
diastolic: latestVitalSign?.bloodPressureLower, 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), SizedBox(height: 16.h),
@ -116,7 +155,16 @@ class _VitalSignPageState extends State<VitalSignPage> {
value: latestVitalSign?.temperatureCelcius?.toString() ?? '--', value: latestVitalSign?.temperatureCelcius?.toString() ?? '--',
unit: '°C', unit: '°C',
status: null, 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<VitalSignPage> {
value: latestVitalSign?.heartRate?.toString() ?? latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--', value: latestVitalSign?.heartRate?.toString() ?? latestVitalSign?.pulseBeatPerMinute?.toString() ?? '--',
unit: 'bpm', unit: 'bpm',
status: 'Normal', 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<VitalSignPage> {
value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--', value: latestVitalSign?.respirationBeatPerMinute?.toString() ?? '--',
unit: 'bpm', unit: 'bpm',
status: 'Normal', status: 'Normal',
onTap: () {}, onTap: () => _openDetails(
const VitalSignDetailsArgs(
metric: VitalSignMetric.respiratoryRate,
title: 'Respiratory rate',
icon: AppAssets.respRate,
unit: 'bpm',
low: 12,
high: 20,
),
),
), ),
], ],
), ),

Loading…
Cancel
Save