|
...
|
...
|
@@ -13,6 +13,7 @@ class TodaySleepCard extends StatelessWidget { |
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
final l10n = context.l10n;
|
|
|
|
return GestureDetector(
|
|
|
|
onTap: controller.toTrendSleepPage,
|
|
|
|
child: Obx(() {
|
|
...
|
...
|
@@ -25,10 +26,10 @@ class TodaySleepCard extends StatelessWidget { |
|
|
|
? context.colors.textPrimary
|
|
|
|
: healthData?.sleepStateColor(context);
|
|
|
|
String sleepStateStr =
|
|
|
|
sleepDuration == 0 ? '--' : healthData?.sleepStateStr() ?? '--';
|
|
|
|
sleepDuration == 0 ? '-' : healthData?.sleepStateStr() ?? '-';
|
|
|
|
String sleepAverageHeartRate =
|
|
|
|
(healthData?.hrAvg == null || healthData?.hrAvg == 0)
|
|
|
|
? '--'
|
|
|
|
? '-'
|
|
|
|
: '${healthData?.hrAvg}';
|
|
|
|
double sleepDurationProgress = sleepDuration == 0
|
|
|
|
? -1
|
|
...
|
...
|
@@ -39,36 +40,103 @@ class TodaySleepCard extends StatelessWidget { |
|
|
|
return _TodaySummaryCard(
|
|
|
|
iconAsset: 'assets/images/common/ic_sleep_stroke.png',
|
|
|
|
iconColor: context.colors.primary,
|
|
|
|
title: context.l10n.sleep,
|
|
|
|
actionText: context.l10n.viewSleepReport,
|
|
|
|
title: l10n.sleep,
|
|
|
|
actionText: l10n.viewSleepReport,
|
|
|
|
metrics: [
|
|
|
|
_MetricData(
|
|
|
|
label: context.l10n.duration,
|
|
|
|
labelColor: context.colors.primary,
|
|
|
|
valueSpans: [
|
|
|
|
_MetricValueSpan(
|
|
|
|
sleepHours > 0 ? '$sleepHours' : '--', l10n.reportUnitHour),
|
|
|
|
_MetricValueSpan(sleepMinutes > 0 ? '$sleepMinutes' : '--',
|
|
|
|
l10n.reportUnitMinute),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
_MetricData(
|
|
|
|
label: context.l10n.quality,
|
|
|
|
_MetricBlock(
|
|
|
|
label: l10n.duration,
|
|
|
|
labelColor: context.colors.primary,
|
|
|
|
value: (sleepDuration > 0)
|
|
|
|
? Text.rich(
|
|
|
|
TextSpan(
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textPrimary,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 19 / 16,
|
|
|
|
),
|
|
|
|
children: [
|
|
|
|
if (sleepHours > 0) ...[
|
|
|
|
TextSpan(text: '$sleepHours'),
|
|
|
|
TextSpan(
|
|
|
|
text: ' ${l10n.reportUnitHour} ',
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textSecondary,
|
|
|
|
fontSize: 12,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
if (sleepMinutes > 0) ...[
|
|
|
|
TextSpan(text: '$sleepMinutes'),
|
|
|
|
TextSpan(
|
|
|
|
text: ' ${l10n.reportUnitMinute}',
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textSecondary,
|
|
|
|
fontSize: 12,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
]
|
|
|
|
],
|
|
|
|
),
|
|
|
|
)
|
|
|
|
: Text(
|
|
|
|
'-',
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textPrimary,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 19 / 16,
|
|
|
|
),
|
|
|
|
)),
|
|
|
|
_MetricBlock(
|
|
|
|
label: l10n.quality,
|
|
|
|
labelColor: const Color(0xFF7B9BFB),
|
|
|
|
valueSpans: [
|
|
|
|
_MetricValueSpan(
|
|
|
|
sleepStateStr,
|
|
|
|
'',
|
|
|
|
valueColor: sleepStateColor,
|
|
|
|
value: Text(
|
|
|
|
sleepStateStr,
|
|
|
|
style: TextStyle(
|
|
|
|
color: sleepStateColor,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 19 / 16,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
_MetricData(
|
|
|
|
label: context.l10n.averageHeartRate,
|
|
|
|
_MetricBlock(
|
|
|
|
label: l10n.averageHeartRate,
|
|
|
|
labelColor: context.colors.textSecondary,
|
|
|
|
valueSpans: [
|
|
|
|
_MetricValueSpan(sleepAverageHeartRate, 'bpm'),
|
|
|
|
],
|
|
|
|
value: sleepDuration > 0 && ((healthData?.hrAvg ?? 0) >= 0)
|
|
|
|
? Text.rich(
|
|
|
|
TextSpan(
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textPrimary,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 19 / 16,
|
|
|
|
),
|
|
|
|
children: [
|
|
|
|
TextSpan(text: sleepAverageHeartRate),
|
|
|
|
TextSpan(
|
|
|
|
text: ' bpm',
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textSecondary,
|
|
|
|
fontSize: 12,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
)
|
|
|
|
: Text(
|
|
|
|
'-',
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textPrimary,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 19 / 16,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
rightWidget: Container(
|
|
...
|
...
|
@@ -104,6 +172,7 @@ class TodayActivityCard extends StatelessWidget { |
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
final l10n = context.l10n;
|
|
|
|
return GestureDetector(
|
|
|
|
onTap: controller.toTrendActivityPage,
|
|
|
|
child: Obx(() {
|
|
...
|
...
|
@@ -115,30 +184,100 @@ class TodayActivityCard extends StatelessWidget { |
|
|
|
|
|
|
|
var activityStr = (activity > 0 && (activityTarget?.move ?? 0) > 0)
|
|
|
|
? '$activity'
|
|
|
|
: '--';
|
|
|
|
: '-';
|
|
|
|
// exercise 单位为秒,转换为小时+分钟
|
|
|
|
List<_MetricValueSpan> exerciseSpans;
|
|
|
|
Widget exerciseWidget;
|
|
|
|
if (exercise > 0 && (activityTarget?.exercise ?? 0) > 0) {
|
|
|
|
final exHours = exercise ~/ 3600;
|
|
|
|
final exMinutes = (exercise % 3600) ~/ 60;
|
|
|
|
exerciseSpans = [
|
|
|
|
if (exHours > 0) _MetricValueSpan('$exHours', l10n.reportUnitHour),
|
|
|
|
if (exMinutes > 0 || exHours == 0)
|
|
|
|
_MetricValueSpan(
|
|
|
|
exMinutes > 0 ? '$exMinutes' : '0', l10n.reportUnitMinute),
|
|
|
|
];
|
|
|
|
exerciseWidget = Text.rich(
|
|
|
|
TextSpan(
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textPrimary,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 19 / 16,
|
|
|
|
),
|
|
|
|
children: [
|
|
|
|
if (exHours > 0) ...[
|
|
|
|
TextSpan(text: '$exHours'),
|
|
|
|
TextSpan(
|
|
|
|
text: ' ${l10n.reportUnitHour} ',
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textSecondary,
|
|
|
|
fontSize: 12,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
if (exMinutes > 0 || exHours == 0) ...[
|
|
|
|
TextSpan(text: exMinutes > 0 ? '$exMinutes' : '0'),
|
|
|
|
TextSpan(
|
|
|
|
text: ' ${l10n.reportUnitMinute}',
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textSecondary,
|
|
|
|
fontSize: 12,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
exerciseSpans = [_MetricValueSpan('--', l10n.reportUnitMinute)];
|
|
|
|
exerciseWidget = Text.rich(
|
|
|
|
TextSpan(
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textPrimary,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 19 / 16,
|
|
|
|
),
|
|
|
|
children: [
|
|
|
|
const TextSpan(text: '-'),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// stand 单位为秒,转换为小时+分钟
|
|
|
|
List<_MetricValueSpan> standSpans;
|
|
|
|
Widget standWidget;
|
|
|
|
if (stand > 0 && (activityTarget?.stand ?? 0) > 0) {
|
|
|
|
standSpans = [
|
|
|
|
_MetricValueSpan('$stand', l10n.reportUnitHour),
|
|
|
|
];
|
|
|
|
standWidget = Text.rich(
|
|
|
|
TextSpan(
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textPrimary,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 19 / 16,
|
|
|
|
),
|
|
|
|
children: [
|
|
|
|
TextSpan(text: '$stand'),
|
|
|
|
TextSpan(
|
|
|
|
text: ' ${l10n.reportUnitHour}',
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textSecondary,
|
|
|
|
fontSize: 12,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
standSpans = [_MetricValueSpan('--', l10n.reportUnitHour)];
|
|
|
|
standWidget = Text.rich(
|
|
|
|
TextSpan(
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textPrimary,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 19 / 16,
|
|
|
|
),
|
|
|
|
children: [
|
|
|
|
const TextSpan(text: '-'),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
double activityProgress =
|
|
|
|
(activity > 0 && (activityTarget?.move ?? 0) > 0)
|
|
...
|
...
|
@@ -155,25 +294,43 @@ class TodayActivityCard extends StatelessWidget { |
|
|
|
return _TodaySummaryCard(
|
|
|
|
iconAsset: 'assets/images/common/ic_exercise.png',
|
|
|
|
iconColor: context.colors.warning,
|
|
|
|
title: context.l10n.fitness,
|
|
|
|
actionText: context.l10n.viewFitnessReport,
|
|
|
|
title: l10n.fitness,
|
|
|
|
actionText: l10n.viewFitnessReport,
|
|
|
|
metrics: [
|
|
|
|
_MetricData(
|
|
|
|
label: context.l10n.event,
|
|
|
|
_MetricBlock(
|
|
|
|
label: l10n.event,
|
|
|
|
labelColor: context.colors.warning,
|
|
|
|
valueSpans: [
|
|
|
|
_MetricValueSpan(activityStr, l10n.reportUnitKcal),
|
|
|
|
],
|
|
|
|
value: Text.rich(
|
|
|
|
TextSpan(
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textPrimary,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 19 / 16,
|
|
|
|
),
|
|
|
|
children: [
|
|
|
|
TextSpan(text: activityStr),
|
|
|
|
TextSpan(
|
|
|
|
text: ' ${l10n.reportUnitKcal}',
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textSecondary,
|
|
|
|
fontSize: 12,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
_MetricData(
|
|
|
|
label: context.l10n.exercise,
|
|
|
|
_MetricBlock(
|
|
|
|
label: l10n.exercise,
|
|
|
|
labelColor: context.colors.chartGreen,
|
|
|
|
valueSpans: exerciseSpans,
|
|
|
|
value: exerciseWidget,
|
|
|
|
),
|
|
|
|
_MetricData(
|
|
|
|
label: context.l10n.standing,
|
|
|
|
_MetricBlock(
|
|
|
|
label: l10n.standing,
|
|
|
|
labelColor: const Color(0xFF7B9BFB),
|
|
|
|
valueSpans: standSpans,
|
|
|
|
value: standWidget,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
rightWidget: Container(
|
|
...
|
...
|
@@ -238,7 +395,7 @@ class _TodaySummaryCard extends StatelessWidget { |
|
|
|
final Color iconColor;
|
|
|
|
final String title;
|
|
|
|
final String actionText;
|
|
|
|
final List<_MetricData> metrics;
|
|
|
|
final List<Widget> metrics;
|
|
|
|
|
|
|
|
final Widget rightWidget;
|
|
|
|
|
|
...
|
...
|
@@ -299,8 +456,7 @@ class _TodaySummaryCard extends StatelessWidget { |
|
|
|
child: Row(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
for (final metric in metrics)
|
|
|
|
Expanded(child: _MetricBlock(metric: metric)),
|
|
|
|
for (final metric in metrics) Expanded(child: metric),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
...
|
...
|
@@ -314,9 +470,15 @@ class _TodaySummaryCard extends StatelessWidget { |
|
|
|
}
|
|
|
|
|
|
|
|
class _MetricBlock extends StatelessWidget {
|
|
|
|
const _MetricBlock({required this.metric});
|
|
|
|
const _MetricBlock({
|
|
|
|
required this.label,
|
|
|
|
required this.labelColor,
|
|
|
|
required this.value,
|
|
|
|
});
|
|
|
|
|
|
|
|
final _MetricData metric;
|
|
|
|
final String label;
|
|
|
|
final Color labelColor;
|
|
|
|
final Widget value;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
...
|
...
|
@@ -326,11 +488,11 @@ class _MetricBlock extends StatelessWidget { |
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
|
|
metric.label,
|
|
|
|
label,
|
|
|
|
maxLines: 1,
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
style: TextStyle(
|
|
|
|
color: metric.labelColor,
|
|
|
|
color: labelColor,
|
|
|
|
fontSize: 12,
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
height: 17 / 12,
|
|
...
|
...
|
@@ -340,78 +502,10 @@ class _MetricBlock extends StatelessWidget { |
|
|
|
FittedBox(
|
|
|
|
fit: BoxFit.scaleDown,
|
|
|
|
alignment: Alignment.centerLeft,
|
|
|
|
child: Row(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
|
|
children: [
|
|
|
|
for (final span in metric.valueSpans) _MetricValue(value: span),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
child: value,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} |
|
|
|
|
|
|
|
class _MetricValue extends StatelessWidget {
|
|
|
|
const _MetricValue({required this.value});
|
|
|
|
|
|
|
|
final _MetricValueSpan value;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Row(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
|
|
children: [
|
|
|
|
Text(
|
|
|
|
value.value,
|
|
|
|
style: TextStyle(
|
|
|
|
color: value.valueColor ?? context.colors.textPrimary,
|
|
|
|
fontSize: 16,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 19 / 16,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
if (value.unit.isNotEmpty)
|
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsets.only(left: 1, bottom: 1),
|
|
|
|
child: Text(
|
|
|
|
value.unit,
|
|
|
|
style: TextStyle(
|
|
|
|
color: context.colors.textSecondary,
|
|
|
|
fontSize: 12,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
height: 17 / 12,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
if (value.unit.isNotEmpty) const SizedBox(width: 4),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _MetricData {
|
|
|
|
const _MetricData({
|
|
|
|
required this.label,
|
|
|
|
required this.labelColor,
|
|
|
|
required this.valueSpans,
|
|
|
|
});
|
|
|
|
|
|
|
|
final String label;
|
|
|
|
final Color labelColor;
|
|
|
|
final List<_MetricValueSpan> valueSpans;
|
|
|
|
}
|
|
|
|
|
|
|
|
class _MetricValueSpan {
|
|
|
|
const _MetricValueSpan(
|
|
|
|
this.value,
|
|
|
|
this.unit, {
|
|
|
|
this.valueColor,
|
|
|
|
});
|
|
|
|
|
|
|
|
final String value;
|
|
|
|
final String unit;
|
|
|
|
final Color? valueColor;
|
|
|
|
} |
...
|
...
|
|