|
|
|
|
@ -74,7 +74,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
|
|
|
|
|
return SingleChildScrollView(
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
spacing: 16.h,
|
|
|
|
|
children: [
|
|
|
|
|
_headerCard(
|
|
|
|
|
context,
|
|
|
|
|
@ -85,16 +85,8 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
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),
|
|
|
|
|
);
|
|
|
|
|
@ -120,66 +112,72 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
),
|
|
|
|
|
padding: EdgeInsets.all(16.h),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
spacing: 8.h,
|
|
|
|
|
children: [
|
|
|
|
|
Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
children: [
|
|
|
|
|
Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
children: [
|
|
|
|
|
title.toText28(isBold: true, color: AppColors.textColor, letterSpacing: -1),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
SizedBox(height: 8.h),
|
|
|
|
|
(latestDate != null
|
|
|
|
|
? ('Result of ${latestDate.toString().split(' ').first}'.needTranslation)
|
|
|
|
|
: ('Result of --'.needTranslation))
|
|
|
|
|
.toText11(weight: FontWeight.w500, color: AppColors.greyTextColor),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
Row(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
|
children: [
|
|
|
|
|
Container(
|
|
|
|
|
padding: EdgeInsets.all(10.h),
|
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
|
color: scheme.iconBg,
|
|
|
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
|
|
|
Expanded(
|
|
|
|
|
child: Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
children: [
|
|
|
|
|
Flexible(
|
|
|
|
|
child: valueText.toText28(
|
|
|
|
|
isBold: true,
|
|
|
|
|
color: scheme.iconFg,
|
|
|
|
|
letterSpacing: -2,
|
|
|
|
|
),
|
|
|
|
|
child: Utils.buildSvgWithAssets(
|
|
|
|
|
icon: icon,
|
|
|
|
|
width: 20.w,
|
|
|
|
|
height: 20.h,
|
|
|
|
|
iconColor: scheme.iconFg,
|
|
|
|
|
fit: BoxFit.contain,
|
|
|
|
|
),
|
|
|
|
|
SizedBox(width: 4.h),
|
|
|
|
|
if (status != null)
|
|
|
|
|
Column(
|
|
|
|
|
spacing: 6.h,
|
|
|
|
|
children: [
|
|
|
|
|
status.toText10(weight: FontWeight.w500, color: AppColors.greyTextColor),
|
|
|
|
|
Utils.buildSvgWithAssets(
|
|
|
|
|
icon: AppAssets.lab_result_indicator,
|
|
|
|
|
width: 21,
|
|
|
|
|
height: 23,
|
|
|
|
|
iconColor: scheme.iconFg,
|
|
|
|
|
),
|
|
|
|
|
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),
|
|
|
|
|
if (args.low != null || args.high != null)
|
|
|
|
|
Text(
|
|
|
|
|
_referenceText(context),
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
fontSize: 12.f,
|
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
|
fontFamily: 'Poppins',
|
|
|
|
|
color: AppColors.greyTextColor,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
softWrap: true,
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
@ -208,21 +206,10 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
padding: EdgeInsets.all(16.h),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
spacing: 8.h,
|
|
|
|
|
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),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
'What is this result?'.needTranslation.toText16(weight: FontWeight.w600, color: AppColors.textColor),
|
|
|
|
|
_descriptionText(context).toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
@ -235,34 +222,63 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
borderRadius: 24.h,
|
|
|
|
|
hasShadow: true,
|
|
|
|
|
),
|
|
|
|
|
padding: EdgeInsets.all(16.h),
|
|
|
|
|
height: _isGraphVisible
|
|
|
|
|
? 260.h
|
|
|
|
|
: (history.length < 3)
|
|
|
|
|
? (history.length * 64) + 80.h
|
|
|
|
|
: 260.h,
|
|
|
|
|
padding: EdgeInsets.all(15.h),
|
|
|
|
|
child: Column(
|
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
|
|
|
|
children: [
|
|
|
|
|
Row(
|
|
|
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
|
|
|
children: [
|
|
|
|
|
'History flowchart'.needTranslation.toText16(weight: FontWeight.w600),
|
|
|
|
|
Text(
|
|
|
|
|
_isGraphVisible ? 'History flowchart'.needTranslation : 'History'.needTranslation,
|
|
|
|
|
style: TextStyle(
|
|
|
|
|
fontSize: 16,
|
|
|
|
|
fontFamily: 'Poppins',
|
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
|
color: AppColors.textColor,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
Row(
|
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
|
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(() {
|
|
|
|
|
Container(
|
|
|
|
|
width: 24.h,
|
|
|
|
|
height: 24.h,
|
|
|
|
|
alignment: Alignment.center,
|
|
|
|
|
child: InkWell(
|
|
|
|
|
onTap: () {
|
|
|
|
|
setState(() {
|
|
|
|
|
_isGraphVisible = !_isGraphVisible;
|
|
|
|
|
});
|
|
|
|
|
}),
|
|
|
|
|
SizedBox(width: 10.w),
|
|
|
|
|
Utils.buildSvgWithAssets(icon: AppAssets.calendarGrey, width: 18.w, height: 18.h, iconColor: AppColors.greyTextColor),
|
|
|
|
|
},
|
|
|
|
|
child: Utils.buildSvgWithAssets(
|
|
|
|
|
icon: _isGraphVisible ? AppAssets.ic_list : AppAssets.ic_graph,
|
|
|
|
|
width: 24.h,
|
|
|
|
|
height: 24.h,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
// SizedBox(width: 16.h),
|
|
|
|
|
// Container(
|
|
|
|
|
// width: 24.h,
|
|
|
|
|
// height: 24.h,
|
|
|
|
|
// alignment: Alignment.center,
|
|
|
|
|
// child: Utils.buildSvgWithAssets(
|
|
|
|
|
// icon: AppAssets.ic_date_filter,
|
|
|
|
|
// width: 24.h,
|
|
|
|
|
// height: 24.h,
|
|
|
|
|
// ),
|
|
|
|
|
// ),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
SizedBox(height: 12.h),
|
|
|
|
|
).paddingOnly(bottom: _isGraphVisible ? 16.h : 24.h),
|
|
|
|
|
|
|
|
|
|
if (history.isEmpty)
|
|
|
|
|
Utils.getNoDataWidget(context, noDataText: 'No history available'.needTranslation, isSmallWidget: true)
|
|
|
|
|
else if (_isGraphVisible)
|
|
|
|
|
@ -277,44 +293,60 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
Widget _buildHistoryGraph(List<DataPoint> history) {
|
|
|
|
|
final minY = _minY(history);
|
|
|
|
|
final maxY = _maxY(history);
|
|
|
|
|
final scheme = VitalSignUiModel.scheme(status: _statusForLatest(null), label: args.title);
|
|
|
|
|
|
|
|
|
|
return CustomGraph(
|
|
|
|
|
dataPoints: history,
|
|
|
|
|
makeGraphBasedOnActualValue: true,
|
|
|
|
|
leftLabelReservedSize: 40,
|
|
|
|
|
showGridLines: true,
|
|
|
|
|
showShadow: true,
|
|
|
|
|
leftLabelInterval: _leftInterval(history),
|
|
|
|
|
maxY: maxY,
|
|
|
|
|
minY: minY,
|
|
|
|
|
maxX: history.length.toDouble() - .75,
|
|
|
|
|
horizontalInterval: .1,
|
|
|
|
|
horizontalInterval: _leftInterval(history),
|
|
|
|
|
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);
|
|
|
|
|
// Show labels at interval points
|
|
|
|
|
if (args.high != null && (value - args.high!).abs() < 0.1) {
|
|
|
|
|
return _axisLabel('High');
|
|
|
|
|
}
|
|
|
|
|
if (args.low != null && v == args.low) {
|
|
|
|
|
return _axisLabel('Low'.needTranslation);
|
|
|
|
|
if (args.low != null && (value - args.low!).abs() < 0.1) {
|
|
|
|
|
return _axisLabel('Low');
|
|
|
|
|
}
|
|
|
|
|
return const SizedBox.shrink();
|
|
|
|
|
// Show numeric labels at regular intervals
|
|
|
|
|
return _axisLabel(value.toStringAsFixed(0));
|
|
|
|
|
},
|
|
|
|
|
getDrawingHorizontalLine: (value) {
|
|
|
|
|
value = double.parse(value.toStringAsFixed(1));
|
|
|
|
|
if ((args.high != null && value == args.high) || (args.low != null && value == args.low)) {
|
|
|
|
|
// Draw reference lines for high/low bounds
|
|
|
|
|
if (args.high != null && (value - args.high!).abs() < 0.1) {
|
|
|
|
|
return FlLine(
|
|
|
|
|
color: AppColors.bgGreenColor.withValues(alpha: 0.6),
|
|
|
|
|
color: AppColors.bgGreenColor.withOpacity(0.2),
|
|
|
|
|
strokeWidth: 1,
|
|
|
|
|
dashArray: [5, 5],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return const FlLine(color: Colors.transparent, strokeWidth: 1);
|
|
|
|
|
if (args.low != null && (value - args.low!).abs() < 0.1) {
|
|
|
|
|
return FlLine(
|
|
|
|
|
color: AppColors.bgGreenColor.withOpacity(0.2),
|
|
|
|
|
strokeWidth: 1,
|
|
|
|
|
dashArray: [5, 5],
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
// Draw grid lines at intervals
|
|
|
|
|
return FlLine(
|
|
|
|
|
color: AppColors.bgGreenColor.withOpacity(0.2),
|
|
|
|
|
strokeWidth: 1,
|
|
|
|
|
dashArray: [5, 5],
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
graphColor: AppColors.blackColor,
|
|
|
|
|
graphShadowColor: Colors.transparent,
|
|
|
|
|
graphGridColor: AppColors.graphGridColor.withValues(alpha: .4),
|
|
|
|
|
graphColor: AppColors.bgGreenColor,
|
|
|
|
|
graphShadowColor: AppColors.lightGreenColor.withOpacity(.4),
|
|
|
|
|
graphGridColor: scheme.iconFg,
|
|
|
|
|
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) return _bottomLabel(data[value.toInt()].label, isLast: true);
|
|
|
|
|
if (value == ((data.length - 1) / 2)) return _bottomLabel(data[value.toInt()].label);
|
|
|
|
|
return const SizedBox.shrink();
|
|
|
|
|
},
|
|
|
|
|
@ -325,6 +357,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Widget _buildHistoryList(BuildContext context, List<DataPoint> history) {
|
|
|
|
|
final items = history.reversed.toList();
|
|
|
|
|
final height = items.length < 3 ? items.length * 64.0 : 180.h;
|
|
|
|
|
@ -393,7 +426,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
HorizontalRangeAnnotation(
|
|
|
|
|
y1: minY,
|
|
|
|
|
y2: args.low!,
|
|
|
|
|
color: AppColors.highAndLow.withValues(alpha: 0.05),
|
|
|
|
|
color: AppColors.highAndLow.withOpacity(0.05),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
@ -403,7 +436,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
HorizontalRangeAnnotation(
|
|
|
|
|
y1: args.low!,
|
|
|
|
|
y2: args.high!,
|
|
|
|
|
color: AppColors.bgGreenColor.withValues(alpha: 0.05),
|
|
|
|
|
color: AppColors.bgGreenColor.withOpacity(0.05),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
@ -413,7 +446,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
HorizontalRangeAnnotation(
|
|
|
|
|
y1: args.high!,
|
|
|
|
|
y2: maxY,
|
|
|
|
|
color: AppColors.criticalLowAndHigh.withValues(alpha: 0.05),
|
|
|
|
|
color: AppColors.criticalLowAndHigh.withOpacity(0.05),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
@ -452,6 +485,9 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
|
|
|
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
|
|
|
|
|
|
|
|
double index = 0;
|
|
|
|
|
for (final v in sorted) {
|
|
|
|
|
final mv = metricValue(v);
|
|
|
|
|
@ -459,7 +495,7 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
if (mv == 0) continue;
|
|
|
|
|
|
|
|
|
|
final dt = v.vitalSignDate ?? DateTime.now();
|
|
|
|
|
final label = '${dt.day}/${dt.month}';
|
|
|
|
|
final label = '${monthNames[dt.month - 1]}, ${dt.year}';
|
|
|
|
|
|
|
|
|
|
points.add(
|
|
|
|
|
DataPoint(
|
|
|
|
|
@ -603,18 +639,14 @@ class _VitalSignDetailsPageState extends State<VitalSignDetailsPage> {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Widget _bottomLabel(String label) {
|
|
|
|
|
Widget _bottomLabel(String label, {bool isLast = false}) {
|
|
|
|
|
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,
|
|
|
|
|
),
|
|
|
|
|
padding: EdgeInsets.only(
|
|
|
|
|
top: 8.0,
|
|
|
|
|
right: isLast ? 16.h : 0,
|
|
|
|
|
),
|
|
|
|
|
child: label.toText8(fontWeight: FontWeight.w500),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|