Commit ef503062f3aeb733cffffe82b0c7602b7ea14c7b

Authored by 刘宏哲
1 parent 57589833

feat(app): update ui

... ... @@ -423,9 +423,9 @@ class _MonthEnergyTrendCardState extends State<_MonthEnergyTrendCard> {
@override
Widget build(BuildContext context) {
final hasData = widget.report.hasData;
final hasChartData = _hasChartData;
final maxY = _maxY;
final comparisonPercent = widget.report.activeEnergyComparisonPercent;
final hasChartReference = _hasChartReference;
return Container(
height: 344,
padding: const EdgeInsets.fromLTRB(20, 20, 14, 40),
... ... @@ -541,16 +541,19 @@ class _MonthEnergyTrendCardState extends State<_MonthEnergyTrendCard> {
},
),
),
if (!hasChartReference)
Positioned.fill(
if (!hasChartData)
Positioned(
left: ActivityBurnTrendPlotFrame.horizontalInset,
right: ActivityBurnTrendPlotFrame.rightAxisWidth,
top: _chartTopInset,
bottom: ActivityBurnTrendPlotFrame.bottomTitleHeight,
child: Center(
child: Text(
context.l10n.reportWaitingForData,
style: const TextStyle(
color: ActivityBurnMonthReportView._h3,
color: Color(0xFFA1A0A5),
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
... ... @@ -573,9 +576,8 @@ class _MonthEnergyTrendCardState extends State<_MonthEnergyTrendCard> {
.toDouble();
}
bool get _hasChartReference =>
widget.report.days.any((day) => (day.activeEnergy?.value ?? 0) > 0) ||
widget.report.activeEnergyGoal > 0;
bool get _hasChartData =>
widget.report.days.any((day) => (day.activeEnergy?.value ?? 0) > 0);
String _comparisonText(BuildContext context, int? percent) {
if (percent == null || !widget.report.hasData) {
... ... @@ -713,9 +715,7 @@ class _MonthEnergyTrendCardState extends State<_MonthEnergyTrendCard> {
interval: 5,
getTitlesWidget: (value, meta) {
final day = value.toInt() + 1;
if (day != 1 &&
day % 5 != 0 &&
day != widget.report.monthEnd.day) {
if (!_shouldShowBottomTitle(day)) {
return const SizedBox.shrink();
}
return Padding(
... ... @@ -762,6 +762,13 @@ class _MonthEnergyTrendCardState extends State<_MonthEnergyTrendCard> {
reportWeekdayLabel(date.weekday),
);
}
bool _shouldShowBottomTitle(int day) {
final lastDay = widget.report.monthEnd.day;
if (day == 1 || day == lastDay) return true;
if (day == lastDay - 1 && lastDay % 5 == 1) return false;
return day % 5 == 0;
}
}
double _barGroupsSpace(double plotWidth, int count, double barWidth) {
... ...
... ... @@ -400,9 +400,10 @@ class _EnergyTrendCardState extends State<_EnergyTrendCard> {
@override
Widget build(BuildContext context) {
final hasData = widget.report.hasData;
final hasChartData = _hasChartData;
final maxY = _maxY;
final comparisonPercent = widget.report.activeEnergyComparisonPercent;
final hasChartReference = _hasChartReference;
return Container(
height: 344,
padding: const EdgeInsets.fromLTRB(20, 20, 14, 32),
... ... @@ -427,7 +428,7 @@ class _EnergyTrendCardState extends State<_EnergyTrendCard> {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
widget.report.hasData
hasData
? widget.report.averageDailyActiveEnergy.toString()
: '-',
style: const TextStyle(
... ... @@ -518,8 +519,10 @@ class _EnergyTrendCardState extends State<_EnergyTrendCard> {
},
),
),
if (!hasChartReference)
Positioned.fill(
if (!hasChartData)
Positioned(
left: ActivityBurnTrendPlotFrame.horizontalInset,
right: ActivityBurnTrendPlotFrame.rightAxisWidth,
top: _chartTopInset,
bottom: ActivityBurnTrendPlotFrame.bottomTitleHeight,
child: Center(
... ... @@ -528,6 +531,7 @@ class _EnergyTrendCardState extends State<_EnergyTrendCard> {
style: const TextStyle(
color: Color(0xFFA1A0A5),
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
... ... @@ -550,9 +554,8 @@ class _EnergyTrendCardState extends State<_EnergyTrendCard> {
.toDouble();
}
bool get _hasChartReference =>
widget.report.days.any((day) => (day.activeEnergy?.value ?? 0) > 0) ||
widget.report.activeEnergyGoal > 0;
bool get _hasChartData =>
widget.report.days.any((day) => (day.activeEnergy?.value ?? 0) > 0);
String _comparisonText(BuildContext context, int? percent) {
if (percent == null || !widget.report.hasData) {
... ...
... ... @@ -25,12 +25,13 @@ class SleepHeartRateCard extends StatelessWidget {
static const _red = Color(0xFFFF5279);
static const _chartLeftInset = 0.0;
static const _chartRightInset = 20.0;
static const _chartTopInset = 16.0;
@override
Widget build(BuildContext context) {
return Container(
height: 358,
padding: EdgeInsets.fromLTRB(20, 20.h, 20, 16),
padding: EdgeInsets.fromLTRB(20, 20.h, 20, 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
... ... @@ -66,6 +67,7 @@ class SleepHeartRateCard extends StatelessWidget {
children: [
if (hasChartData) ...[
Positioned.fill(
top: _chartTopInset,
bottom: 20,
child: LayoutBuilder(
builder: (context, constraints) => LineChart(
... ... @@ -77,7 +79,13 @@ class SleepHeartRateCard extends StatelessWidget {
),
),
),
const Positioned.fill(
top: _chartTopInset,
bottom: 20,
child: _HeartRateBoundaryGridLine(),
),
Positioned.fill(
top: _chartTopInset,
bottom: 20,
child: _HeartRateAxisLabels(
range: _HeartRateYAxisRange.fromPoints(chartPoints),
... ... @@ -86,7 +94,42 @@ class SleepHeartRateCard extends StatelessWidget {
Positioned.fill(
child: _HeartRateTimeLabels(axis: xAxis),
),
] else
] else ...[
Positioned.fill(
top: _chartTopInset,
bottom: 20,
child: LayoutBuilder(
builder: (context, constraints) => LineChart(
_chartData(
xAxis: xAxis,
chartPoints: chartPoints,
chartWidth: constraints.maxWidth,
),
),
),
),
const Positioned.fill(
top: _chartTopInset,
bottom: 20,
child: _HeartRateBoundaryGridLine(
drawTop: true,
),
),
Positioned.fill(
top: _chartTopInset,
bottom: 20,
child: _HeartRateAxisLabels(
range: _HeartRateYAxisRange.fromPoints(
chartPoints,
),
),
),
Positioned.fill(
child: _HeartRateTimeLabels(
axis: xAxis,
tickCount: 4,
),
),
Positioned.fill(
bottom: 20,
child: Center(
... ... @@ -100,6 +143,7 @@ class SleepHeartRateCard extends StatelessWidget {
),
),
),
],
],
);
},
... ... @@ -138,7 +182,7 @@ class SleepHeartRateCard extends StatelessWidget {
gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: _HeartRateYAxisRange.interval,
horizontalInterval: yRange.interval,
getDrawingHorizontalLine: (_) => const FlLine(
color: _grid,
strokeWidth: 1,
... ... @@ -208,10 +252,21 @@ class SleepHeartRateCard extends StatelessWidget {
_HeartRateXAxis get _heartRateXAxis {
final points = _visibleDatePoints;
if (points.isEmpty) {
final start = _chartDayStart;
final sleepStart = summary.sleepStart;
final sleepEnd = summary.sleepEnd;
if (sleepStart != null &&
sleepEnd != null &&
sleepEnd.isAfter(sleepStart)) {
return _HeartRateXAxis(
start: sleepStart,
end: sleepEnd,
);
}
final start = _chartDayStart.add(const Duration(hours: 22));
return _HeartRateXAxis(
start: start,
end: start.add(const Duration(hours: 1)),
end: start.add(const Duration(hours: 8, minutes: 59)),
);
}
... ... @@ -274,6 +329,65 @@ class SleepHeartRateCard extends StatelessWidget {
}
}
class _HeartRateBoundaryGridLine extends StatelessWidget {
const _HeartRateBoundaryGridLine({
this.drawTop = false,
});
final bool drawTop;
@override
Widget build(BuildContext context) {
return IgnorePointer(
child: CustomPaint(
painter: _HeartRateBoundaryGridLinePainter(drawTop: drawTop),
),
);
}
}
class _HeartRateBoundaryGridLinePainter extends CustomPainter {
const _HeartRateBoundaryGridLinePainter({
required this.drawTop,
});
final bool drawTop;
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = SleepHeartRateCard._grid
..strokeWidth = 1;
final right = size.width;
_drawDashedLine(canvas, paint, y: size.height - 0.5, width: right);
if (drawTop) {
_drawDashedLine(canvas, paint, y: 0.5, width: right);
}
}
void _drawDashedLine(
Canvas canvas,
Paint paint, {
required double y,
required double width,
}) {
const dashWidth = 2.0;
const dashSpace = 2.0;
var x = 0.0;
while (x < width) {
final end = (x + dashWidth).clamp(0.0, width);
canvas.drawLine(Offset(x, y), Offset(end, y), paint);
x += dashWidth + dashSpace;
}
}
@override
bool shouldRepaint(covariant _HeartRateBoundaryGridLinePainter oldDelegate) {
return drawTop != oldDelegate.drawTop;
}
}
class _HeartRateXAxis {
const _HeartRateXAxis({
required this.start,
... ... @@ -325,18 +439,23 @@ class _HeartRateAxisLabels extends StatelessWidget {
class _HeartRateTimeLabels extends StatelessWidget {
const _HeartRateTimeLabels({
required this.axis,
this.tickCount = 3,
});
final _HeartRateXAxis axis;
final int tickCount;
@override
Widget build(BuildContext context) {
final totalMinutes = axis.end.difference(axis.start).inMinutes;
final ticks = <DateTime>[
axis.start,
axis.start.add(Duration(minutes: totalMinutes ~/ 2)),
axis.end,
];
final count = tickCount < 2 ? 2 : tickCount;
final ticks = List.generate(count, (index) {
if (index == 0) return axis.start;
if (index == count - 1) return axis.end;
return axis.start.add(
Duration(minutes: (totalMinutes * index / (count - 1)).round()),
);
});
return IgnorePointer(
child: LayoutBuilder(
... ... @@ -371,7 +490,8 @@ class _HeartRateTimeLabels extends StatelessWidget {
final plotWidth =
(plotRight - plotLeft).clamp(0, double.infinity).toDouble();
final tickX = plotLeft + plotWidth * progress;
final left = tickX - labelWidth / 2;
final isFirstTick = index == 0;
final left = isFirstTick ? tickX : tickX - labelWidth / 2;
return Positioned(
left: left,
... ... @@ -379,7 +499,7 @@ class _HeartRateTimeLabels extends StatelessWidget {
width: labelWidth,
child: _TimeLabel(
_tickLabel(tick),
textAlign: TextAlign.center,
textAlign: isFirstTick ? TextAlign.left : TextAlign.center,
),
);
}
... ... @@ -417,12 +537,12 @@ class _HeartRateYAxisRange {
const _HeartRateYAxisRange({
required this.minY,
required this.maxY,
this.interval = 20.0,
});
static const interval = 20.0;
final double minY;
final double maxY;
final double interval;
double get span => maxY - minY <= 0 ? interval : maxY - minY;
... ... @@ -439,19 +559,24 @@ class _HeartRateYAxisRange {
factory _HeartRateYAxisRange.fromPoints(List<SleepHeartRatePoint> points) {
if (points.isEmpty) {
return const _HeartRateYAxisRange(minY: 50, maxY: 80);
return const _HeartRateYAxisRange(
minY: 50,
maxY: 80,
interval: 10,
);
}
final values = points.map((point) => point.bpm);
final minBpm = values.reduce((min, bpm) => bpm < min ? bpm : min);
final maxBpm = values.reduce((max, bpm) => bpm > max ? bpm : max);
final minY = minBpm;
var maxY = (maxBpm / 5).ceil() * 5.0;
if (minY == maxY) maxY += interval;
final minY = (minBpm / 10).floor() * 10.0;
var maxY = (maxBpm / 10).ceil() * 10.0;
if (minY == maxY) maxY += 10;
return _HeartRateYAxisRange(
minY: minY,
maxY: maxY,
interval: 10,
);
}
}
... ...
... ... @@ -700,6 +700,7 @@ class _BedtimeChart extends StatelessWidget {
maxY: axis.maxHour.toDouble(),
minY: axis.minHour.toDouble(),
interval: 1,
topInset: 16,
rightLabel: _timeAxisLabel,
child: LineChart(_chartData(axis)),
),
... ... @@ -1175,6 +1176,7 @@ class _ChartPlotFrame extends StatelessWidget {
required this.interval,
required this.rightLabel,
this.minY = 0,
this.topInset = 0,
this.referenceLines = const [],
});
... ... @@ -1186,6 +1188,7 @@ class _ChartPlotFrame extends StatelessWidget {
final double minY;
final double maxY;
final double interval;
final double topInset;
final String Function(double value) rightLabel;
final List<_ChartReferenceLine> referenceLines;
... ... @@ -1195,6 +1198,7 @@ class _ChartPlotFrame extends StatelessWidget {
clipBehavior: Clip.none,
children: [
Positioned.fill(
top: topInset,
bottom: bottomTitleHeight,
child: CustomPaint(
painter: _HorizontalDashedGridPainter(
... ... @@ -1205,6 +1209,7 @@ class _ChartPlotFrame extends StatelessWidget {
),
),
Positioned.fill(
top: topInset,
bottom: bottomTitleHeight,
child: CustomPaint(
painter: _ReferenceLinePainter(
... ... @@ -1215,13 +1220,15 @@ class _ChartPlotFrame extends StatelessWidget {
),
),
Padding(
padding: const EdgeInsets.only(
padding: EdgeInsets.only(
top: topInset,
left: horizontalInset,
right: rightAxisWidth,
),
child: child,
),
Positioned.fill(
top: topInset,
bottom: bottomTitleHeight,
child: _RightAxisLabels(
minY: minY,
... ...