Commit 845b2b8e03a3f6cc690a670e125d7cadd8a8f50e

Authored by 刘宏哲
1 parent c0004b57

feat(app): bug fixed

... ... @@ -65,7 +65,7 @@ class ApiActivityBurnReportDataSource implements ActivityBurnReportDataSource {
days: _periodReports(start, 7, data),
totalActiveEnergyOverride: data.totalMove?.round(),
totalExerciseMinutesOverride: _durationMinutes(data.totalExercise),
totalStandHoursOverride: _durationMinutes(data.totalStand),
totalStandHoursOverride: _hours(data.totalStand),
averageDailyActiveEnergyOverride: data.avgMove?.round(),
activeEnergyGoalOverride: data.activityTargetInfo?.move?.round(),
previousAverageDailyActiveEnergyOverride: data.qoqAvgMove?.round(),
... ... @@ -93,7 +93,7 @@ class ApiActivityBurnReportDataSource implements ActivityBurnReportDataSource {
days: _periodReports(start, end.day, data),
totalActiveEnergyOverride: data.totalMove?.round(),
totalExerciseMinutesOverride: _durationMinutes(data.totalExercise),
totalStandHoursOverride: _durationMinutes(data.totalStand),
totalStandHoursOverride: _hours(data.totalStand),
averageDailyActiveEnergyOverride: data.avgMove?.round(),
activeEnergyGoalOverride: data.activityTargetInfo?.move?.round(),
previousAverageDailyActiveEnergyOverride: data.qoqAvgMove?.round(),
... ... @@ -258,17 +258,19 @@ class ApiActivityBurnReportDataSource implements ActivityBurnReportDataSource {
);
}
ActivityBurnMetric? _standDurationMetric(num? seconds, num? target) {
if (seconds == null) return null;
ActivityBurnMetric? _standDurationMetric(num? hours, num? target) {
if (hours == null) return null;
return ActivityBurnMetric(
value: _secondsToWholeMinutes(seconds),
goal: _standTargetMinutes(target),
value: _hours(hours) ?? 0,
goal: _hours(target) ?? 0,
);
}
int? _durationMinutes(num? seconds) =>
seconds == null ? null : _secondsToWholeMinutes(seconds);
int? _hours(num? hours) => hours?.round();
int _secondsToWholeMinutes(num? seconds) {
if (seconds == null || seconds < 60) return 0;
return seconds ~/ 60;
... ... @@ -279,11 +281,6 @@ class ApiActivityBurnReportDataSource implements ActivityBurnReportDataSource {
return target >= 60 ? _secondsToWholeMinutes(target) : target.round();
}
int _standTargetMinutes(num? target) {
if (target == null || target <= 0) return 0;
return target >= 60 ? _secondsToWholeMinutes(target) : target.round() * 60;
}
DateTime? _parseDateTime(Object? value, {DateTime? fallbackDate}) {
if (value is num) {
if (value >= 1000000000) {
... ...
... ... @@ -32,3 +32,13 @@ ActivityBurnDurationText activityBurnDurationText(
unit: context.l10n.reportUnitMinute,
);
}
ActivityBurnDurationText activityBurnHoursText(
BuildContext context,
int? hours,
) {
return ActivityBurnDurationText(
value: hours?.toString() ?? '-',
unit: context.l10n.reportUnitHour,
);
}
... ...
... ... @@ -58,7 +58,7 @@ class _MonthlySummary extends StatelessWidget {
context,
hasData ? report.totalExerciseMinutes : null,
);
final stand = activityBurnDurationText(
final stand = activityBurnHoursText(
context,
hasData ? report.totalStandHours : null,
);
... ... @@ -525,14 +525,19 @@ class _MonthEnergyTrendCardState extends State<_MonthEnergyTrendCard> {
if (_touchedOffset != null)
Positioned.fill(
top: _chartTopInset,
child: ChartSelectionLineOverlay(
offset: _touchedOffset!,
color: ActivityBurnMonthReportView._h3,
bottomTitleHeight:
ActivityBurnTrendPlotFrame.bottomTitleHeight,
tooltipMargin: 6,
plotLeft: ActivityBurnTrendPlotFrame.horizontalInset,
plotRight: ActivityBurnTrendPlotFrame.rightAxisWidth,
child: LayoutBuilder(
builder: (context, constraints) {
return ChartSelectionLineOverlay(
offset: _touchedOffset!,
color: ActivityBurnMonthReportView._h3,
bottomTitleHeight:
ActivityBurnTrendPlotFrame.bottomTitleHeight,
tooltipMargin: 6,
plotLeft: ActivityBurnTrendPlotFrame.horizontalInset,
plotRight: ActivityBurnTrendPlotFrame.rightAxisWidth,
lineX: _lineXForIndex(constraints.maxWidth, 4),
);
},
),
),
if (!hasChartReference)
... ... @@ -590,6 +595,21 @@ class _MonthEnergyTrendCardState extends State<_MonthEnergyTrendCard> {
return (value / 5).ceil() * 5.0;
}
double? _lineXForIndex(double width, double barWidth) {
final index = _touchedIndex;
final count = widget.report.days.length;
if (index == null || index < 0 || index >= count || count == 0) {
return null;
}
final plotLeft = ActivityBurnTrendPlotFrame.horizontalInset;
final plotRight = ActivityBurnTrendPlotFrame.rightAxisWidth;
final plotWidth = width - plotLeft - plotRight;
if (plotWidth <= 0) return null;
if (count == 1) return plotLeft + plotWidth / 2;
final groupsSpace = _barGroupsSpace(plotWidth, count, barWidth);
return plotLeft + barWidth / 2 + index * (barWidth + groupsSpace);
}
BarChartData _chartData(double maxY, double plotWidth) {
final hasData = widget.report.hasData;
return BarChartData(
... ...
... ... @@ -22,7 +22,7 @@ class ActivityBurnSummaryCard extends StatelessWidget {
context,
report?.exerciseMinutes?.value,
);
final stand = activityBurnDurationText(
final stand = activityBurnHoursText(
context,
report?.standHours?.value,
);
... ...
... ... @@ -57,7 +57,7 @@ class _WeeklySummary extends StatelessWidget {
context,
report.hasData ? report.totalExerciseMinutes : null,
);
final stand = activityBurnDurationText(
final stand = activityBurnHoursText(
context,
report.hasData ? report.totalStandHours : null,
);
... ... @@ -503,14 +503,19 @@ class _EnergyTrendCardState extends State<_EnergyTrendCard> {
if (_touchedOffset != null)
Positioned.fill(
top: _chartTopInset,
child: ChartSelectionLineOverlay(
offset: _touchedOffset!,
color: ActivityBurnWeekReportView._h3,
bottomTitleHeight:
ActivityBurnTrendPlotFrame.bottomTitleHeight,
tooltipMargin: 6,
plotLeft: ActivityBurnTrendPlotFrame.horizontalInset,
plotRight: ActivityBurnTrendPlotFrame.rightAxisWidth,
child: LayoutBuilder(
builder: (context, constraints) {
return ChartSelectionLineOverlay(
offset: _touchedOffset!,
color: ActivityBurnWeekReportView._h3,
bottomTitleHeight:
ActivityBurnTrendPlotFrame.bottomTitleHeight,
tooltipMargin: 6,
plotLeft: ActivityBurnTrendPlotFrame.horizontalInset,
plotRight: ActivityBurnTrendPlotFrame.rightAxisWidth,
lineX: _lineXForIndex(constraints.maxWidth, 16),
);
},
),
),
if (!hasChartReference)
... ... @@ -568,6 +573,21 @@ class _EnergyTrendCardState extends State<_EnergyTrendCard> {
return (value / 5).ceil() * 5.0;
}
double? _lineXForIndex(double width, double barWidth) {
final index = _touchedIndex;
final count = widget.report.days.length;
if (index == null || index < 0 || index >= count || count == 0) {
return null;
}
final plotLeft = ActivityBurnTrendPlotFrame.horizontalInset;
final plotRight = ActivityBurnTrendPlotFrame.rightAxisWidth;
final plotWidth = width - plotLeft - plotRight;
if (plotWidth <= 0) return null;
if (count == 1) return plotLeft + plotWidth / 2;
final groupsSpace = _barGroupsSpace(plotWidth, count, barWidth);
return plotLeft + barWidth / 2 + index * (barWidth + groupsSpace);
}
BarChartData _chartData(double maxY, double plotWidth) {
final hasData = widget.report.hasData;
return BarChartData(
... ...
... ... @@ -2,6 +2,7 @@ import 'package:doublefeel_flutter/app/modules/hrv_report/models/hrv_report_mode
import 'package:doublefeel_flutter/app/modules/home/widgets/df_tab_bar.dart';
import 'package:doublefeel_flutter/app/routes/app_pages.dart';
import 'package:doublefeel_flutter/core/constants/intent_keys.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:doublefeel_flutter/r.dart';
... ... @@ -144,7 +145,9 @@ class FriendsTab extends GetView<FriendsController> {
selfHealthCard,
_EmptyFriendsView(
enabled: !isFull,
onTap: isFull ? null : _handleAddFriend,
onTap: isFull
? () => _showFriendsLimitReached(context)
: _handleAddFriend,
),
],
),
... ... @@ -205,7 +208,9 @@ class FriendsTab extends GetView<FriendsController> {
enabled: !isFull,
friendCount: friends.length,
maxFriends: FriendsController.maxFriends,
onTap: isFull ? null : _handleAddFriend,
onTap: isFull
? () => _showFriendsLimitReached(context)
: _handleAddFriend,
),
if (isFull) const _FullFriendsTip(),
],
... ... @@ -244,6 +249,10 @@ class FriendsTab extends GetView<FriendsController> {
await controller.loadFriends();
}
void _showFriendsLimitReached(BuildContext context) {
AppToast.show(context.l10n.friendsLimitReached);
}
void _openFriendHome(FriendHealthData friend) {
Get.toNamed(
Routes.FRIEND_HOME,
... ...
... ... @@ -201,7 +201,7 @@ class ApiHrvReportDataSource implements HrvReportDataSource {
Map<DateTime, HrvStressLevel> levelsByDate,
) {
final trend = trendsByDate[date];
final level = levelsByDate[date];
final level = _stressLevelFromApiId(trend?.state) ?? levelsByDate[date];
return HrvDayReport(
date: date,
averageHrv: trend?.hrvAverage?.toDouble(),
... ... @@ -234,7 +234,7 @@ class ApiHrvReportDataSource implements HrvReportDataSource {
date: date,
averageHrv: value.toDouble(),
averageHeartRate: trend?.hrAverage?.round(),
level: levelsByDate[date],
level: _stressLevelFromApiId(trend?.state) ?? levelsByDate[date],
);
}
... ...
... ... @@ -309,6 +309,23 @@ class YearlyHrvReport implements HrvPeriodReport {
List<HrvDayReport> daysForMonth(int month) =>
days.where((day) => day.date.month == month).toList();
List<HrvDayReport> levelDaysForMonth(int month) =>
daysForMonth(month).where((day) => day.level != null).toList();
int stressedDaysForMonth(int month) {
return levelDaysForMonth(month).where((day) {
return day.level == HrvStressLevel.attention ||
day.level == HrvStressLevel.overload;
}).length;
}
int relaxedDaysForMonth(int month) {
return levelDaysForMonth(month).where((day) {
return day.level == HrvStressLevel.excellent ||
day.level == HrvStressLevel.normal;
}).length;
}
double? averageForMonth(int month) {
final values = daysForMonth(month)
.map((day) => day.averageHrv)
... ... @@ -318,11 +335,80 @@ class YearlyHrvReport implements HrvPeriodReport {
return values.reduce((sum, value) => sum + value) / values.length;
}
HrvStressLevel? levelForMonth(int month) {
final levels = daysForMonth(month)
.where((day) => day.averageHrv != null)
.map((day) => day.level)
.whereType<HrvStressLevel>()
.toList();
if (levels.isEmpty) return null;
return levels.first;
}
List<int> get monthsWithAverage => [
for (var month = 1; month <= 12; month++)
if (averageForMonth(month) != null) month,
];
({List<int> mostStressed, List<int> leastStressed})
averageStressExtremeMonths() {
final values = [
for (final month in monthsWithAverage)
if (averageForMonth(month) case final average?)
MapEntry(month, average),
];
values.sort(_compareByStressDescending);
final mostStressed = <int>[];
final leastStressed = <int>[];
if (values.isEmpty) {
return (mostStressed: mostStressed, leastStressed: leastStressed);
}
if (values.length == 1) {
final entry = values.single;
if (_isStressedAverage(entry.value)) {
mostStressed.add(entry.key);
} else {
leastStressed.add(entry.key);
}
return (mostStressed: mostStressed, leastStressed: leastStressed);
}
mostStressed.add(values.first.key);
leastStressed.add(values.last.key);
if (values.length == 3) {
final middle = values[1];
if (_isStressedAverage(middle.value)) {
mostStressed.add(middle.key);
} else {
leastStressed.add(middle.key);
}
} else if (values.length >= 4) {
mostStressed.add(values[1].key);
leastStressed.add(values[values.length - 2].key);
}
mostStressed.sort();
leastStressed.sort();
return (mostStressed: mostStressed, leastStressed: leastStressed);
}
int _compareByStressDescending(
MapEntry<int, double> a,
MapEntry<int, double> b,
) {
final valueComparison = a.value.compareTo(b.value);
return valueComparison == 0 ? a.key.compareTo(b.key) : valueComparison;
}
bool _isStressedAverage(double average) {
final level = HrvStressLevel.fromAverageHrv(average);
return level == HrvStressLevel.attention ||
level == HrvStressLevel.overload;
}
List<int> monthsAtExtreme({required bool maximum}) {
final values = [
for (var month = 1; month <= 12; month++)
... ...
... ... @@ -167,6 +167,9 @@ class _HrvBarChartState extends State<_HrvBarChart> {
static const _plotLeft = _axisLabelWidth;
static const _plotRight = 0.0;
static const _bottomTitleHeight = 31.0;
static const _tooltipBackground = Color(0xFFF3F3F3);
static const _tooltipDateColor = Color(0xFF78787D);
static const _tooltipMetaColor = Color(0xFFB0B0B6);
int? _touchedIndex;
Offset? _touchedOffset;
... ... @@ -267,41 +270,17 @@ class _HrvBarChartState extends State<_HrvBarChart> {
}
},
touchTooltipData: BarTouchTooltipData(
getTooltipColor: (_) => const Color(0xFFF3F3F3),
getTooltipColor: (_) => _tooltipBackground,
tooltipRoundedRadius: 8,
tooltipBorder: BorderSide.none,
tooltipPadding: const EdgeInsets.fromLTRB(8, 7, 8, 6),
tooltipPadding: const EdgeInsets.fromLTRB(12, 8, 12, 4),
tooltipMargin: 2,
maxContentWidth: 128,
maxContentWidth: 107,
fitInsideHorizontally: true,
fitInsideVertically: false,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
final day = widget.report.days[group.x];
if (day.averageHrv == null) return null;
return BarTooltipItem(
'${reportMonthDay(day.date)}\n',
const TextStyle(color: Color(0xFF78787D), fontSize: 10),
children: [
TextSpan(
text: '${day.level?.label ?? ''}\n',
style: TextStyle(
color: Color(
day.level?.colorValue ?? 0xFFB0B0B6,
),
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
TextSpan(
text:
'${day.averageHrv?.round() ?? '-'}ms · ${day.averageHeartRate ?? '-'}bpm',
style: const TextStyle(
color: Color(0xFFB0B0B6),
fontSize: 10,
),
),
],
);
return _tooltipItem(context, day);
},
),
),
... ... @@ -350,6 +329,40 @@ class _HrvBarChartState extends State<_HrvBarChart> {
);
}
BarTooltipItem? _tooltipItem(BuildContext context, HrvDayReport day) {
if (day.averageHrv == null) return null;
return BarTooltipItem(
'${reportMonthDayWithWeekday(day.date)}\n',
const TextStyle(
color: _tooltipDateColor,
fontSize: 10,
fontWeight: FontWeight.w400,
height: 1.7,
),
children: [
TextSpan(
text: '${day.level?.label ?? ''}\n',
style: TextStyle(
color: Color(day.level?.colorValue ?? 0xFFB0B0B6),
fontSize: 14,
fontWeight: FontWeight.w600,
height: 1.55,
),
),
TextSpan(
text:
'${day.averageHrv?.round() ?? '-'}ms · ${day.averageHeartRate ?? '-'}bpm',
style: const TextStyle(
color: _tooltipMetaColor,
fontSize: 10,
fontWeight: FontWeight.w400,
height: 1.35,
),
),
],
);
}
double? _lineXForIndex(double width) {
final index = _touchedIndex;
if (index == null || widget.report.days.isEmpty) return null;
... ...
... ... @@ -34,19 +34,7 @@ class _YearTrendCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final monthsWithAverage = report.monthsWithAverage;
var minimumMonths = report.monthsAtExtreme(maximum: false);
var maximumMonths = report.monthsAtExtreme(maximum: true);
if (monthsWithAverage.length == 1) {
final month = monthsWithAverage.single;
final level = HrvStressLevel.fromAverageHrv(
report.averageForMonth(month)!,
);
final isStressed =
level == HrvStressLevel.attention || level == HrvStressLevel.overload;
minimumMonths = isStressed ? [month] : const [];
maximumMonths = isStressed ? const [] : [month];
}
final extremeMonths = report.averageStressExtremeMonths();
return _Card(
padding: const EdgeInsets.fromLTRB(20, 17, 20, 18),
child: Column(
... ... @@ -67,13 +55,13 @@ class _YearTrendCard extends StatelessWidget {
Expanded(
child: _MonthExtreme(
label: context.l10n.hrvMostStressed,
months: minimumMonths,
months: extremeMonths.mostStressed,
),
),
Expanded(
child: _MonthExtreme(
label: context.l10n.hrvLeastStressed,
months: maximumMonths,
months: extremeMonths.leastStressed,
),
),
],
... ... @@ -121,8 +109,7 @@ class _YearBarChartState extends State<_YearBarChart> {
BarChartRodData(
toY: widget.report.averageForMonth(month) ?? 0,
width: 8,
color: _colorForValue(
widget.report.averageForMonth(month)),
color: _colorForMonth(month),
borderRadius: const BorderRadius.vertical(
top: Radius.circular(5),
),
... ... @@ -201,8 +188,12 @@ class _YearBarChartState extends State<_YearBarChart> {
if (widget.report.averageForMonth(month) == null) {
return null;
}
final stressedDays = _stressedDaysForMonth(month);
final relaxedDays = _relaxedDaysForMonth(month);
final stressedDays = widget.report.stressedDaysForMonth(
month,
);
final relaxedDays = widget.report.relaxedDaysForMonth(
month,
);
return BarTooltipItem(
'${reportMonth(month)}\n',
const TextStyle(
... ... @@ -275,25 +266,17 @@ class _YearBarChartState extends State<_YearBarChart> {
);
}
Color _colorForMonth(int month) {
final level = widget.report.levelForMonth(month);
if (level != null) return Color(level.colorValue);
return _colorForValue(widget.report.averageForMonth(month));
}
Color _colorForValue(double? value) {
if (value == null) return Colors.transparent;
return Color(HrvStressLevel.fromAverageHrv(value).colorValue);
}
int _relaxedDaysForMonth(int month) {
return widget.report.daysForMonth(month).where((day) {
return day.level == HrvStressLevel.excellent ||
day.level == HrvStressLevel.normal;
}).length;
}
int _stressedDaysForMonth(int month) {
return widget.report.daysForMonth(month).where((day) {
return day.level == HrvStressLevel.attention ||
day.level == HrvStressLevel.overload;
}).length;
}
double? _lineXForMonth(double width) {
final month = _touchedMonth;
if (month == null) return null;
... ...
... ... @@ -13,4 +13,9 @@ String reportWeekdayLabel(int weekday) => switch (weekday) {
String reportMonthDay(DateTime date) =>
l10n.reportDateMonthDay(date.month, date.day);
String reportMonthDayWithWeekday(DateTime date) => l10n.reportDateWithWeekday(
reportMonthDay(date),
reportWeekdayLabel(date.weekday),
);
String reportMonth(int month) => l10n.reportDateMonth(month);
... ...
import 'package:doublefeel_flutter/r.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
... ... @@ -26,6 +27,39 @@ class SleepWeekReportView extends StatelessWidget {
static const h3 = Color(0xFFB0B0B6);
static const grid = Color(0xFFF3F3F3);
static bool get _usesAppleTextRenderer =>
defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.macOS;
static TextStyle get titleTextStyle {
const baseStyle = TextStyle(
color: h1,
fontSize: 16,
fontWeight: FontWeight.w600,
height: 1.2,
);
if (!_usesAppleTextRenderer) return baseStyle;
return baseStyle.copyWith(
fontFamily: 'PingFang SC',
fontFamilyFallback: const ['Heiti SC', 'Arial Unicode MS'],
);
}
static StrutStyle get titleStrutStyle {
if (!_usesAppleTextRenderer) {
return const StrutStyle(
fontSize: 16,
height: 1.2,
);
}
return const StrutStyle(
fontSize: 16,
fontFamily: 'PingFang SC',
fontFamilyFallback: ['Heiti SC', 'Arial Unicode MS'],
height: 1.2,
);
}
static int minutesFromEvening(DateTime time) {
final minutes = time.hour * 60 + time.minute;
return minutes < 12 * 60 ? minutes + 24 * 60 : minutes;
... ... @@ -411,12 +445,8 @@ class _TrendCard extends StatelessWidget {
children: [
Text(
title,
style: const TextStyle(
color: SleepWeekReportView.h1,
fontSize: 16,
fontWeight: FontWeight.w600,
height: 1.2,
),
strutStyle: SleepWeekReportView.titleStrutStyle,
style: SleepWeekReportView.titleTextStyle,
),
const SizedBox(height: 14),
Expanded(child: chart),
... ... @@ -914,12 +944,7 @@ BarTouchData _barTouchData({
BarTooltipItem _barTooltipItem(_ChartSummaryData data) {
return BarTooltipItem(
data.title,
TextStyle(
color: data.titleColor,
fontSize: 16,
fontWeight: FontWeight.w600,
height: 1.2,
),
SleepWeekReportView.titleTextStyle.copyWith(color: data.titleColor),
textAlign: TextAlign.start,
children: [
TextSpan(
... ... @@ -962,12 +987,7 @@ LineTouchTooltipData _lineTooltipData({
LineTooltipItem _lineTooltipItem(_ChartSummaryData data) {
return LineTooltipItem(
data.title,
TextStyle(
color: data.titleColor,
fontSize: 16,
fontWeight: FontWeight.w600,
height: 1.2,
),
SleepWeekReportView.titleTextStyle.copyWith(color: data.titleColor),
textAlign: TextAlign.start,
children: [
TextSpan(
... ...
... ... @@ -186,23 +186,27 @@ class HrvTrendList {
this.timeKey,
this.hrvAverage,
this.hrAverage,
this.state,
});
HrvTrendList.fromJson(dynamic json) {
timeKey = json['time_key'];
hrvAverage = _parseDouble(json['hrv_average']);
hrAverage = _parseDouble(json['hr_average']);
state = _parseInt(json['state']);
}
Object? timeKey;
double? hrvAverage;
double? hrAverage;
int? state;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['time_key'] = timeKey;
map['hrv_average'] = hrvAverage;
map['hr_average'] = hrAverage;
map['state'] = state;
return map;
}
}
... ... @@ -213,3 +217,10 @@ double? _parseDouble(Object? value) {
if (value is String) return double.tryParse(value);
return null;
}
int? _parseInt(Object? value) {
if (value == null) return null;
if (value is num) return value.toInt();
if (value is String) return int.tryParse(value);
return null;
}
... ...
... ... @@ -376,7 +376,7 @@
"sleepQualityNormalRange": "60–85 pts",
"sleepQualityExcellentRange": ">85 pts",
"friendsAddCloseContactDescription": "Add a close contact so someone else can look out for your health",
"friendsLimitReached": "Friend limit reached",
"friendsLimitReached": "You can add up to 10 friends",
"friendsAddCloseContact": "Add a close contact",
"friendsAddCloseContactWithCount": "Add a close contact ({count}/{max})",
"friendsMe": "Me",
... ...
... ... @@ -599,7 +599,7 @@
"sleepQualityNormalRange": "60~85分",
"sleepQualityExcellentRange": ">85分",
"friendsAddCloseContactDescription": "添加亲密联系人,多一个人关注你的健康",
"friendsLimitReached": "好友数量已达上限",
"friendsLimitReached": "最多只能添加10个好友哦",
"friendsAddCloseContact": "添加亲密联系人",
"friendsAddCloseContactWithCount": "添加亲密联系人({count}/{max})",
"@friendsAddCloseContactWithCount": {
... ...
... ... @@ -2358,7 +2358,7 @@ abstract class AppLocalizations {
/// No description provided for @friendsLimitReached.
///
/// In zh, this message translates to:
/// **'好友数量已达上限'**
/// **'最多只能添加10个好友哦'**
String get friendsLimitReached;
/// No description provided for @friendsAddCloseContact.
... ...
... ... @@ -1294,7 +1294,7 @@ class AppLocalizationsEn extends AppLocalizations {
'Add a close contact so someone else can look out for your health';
@override
String get friendsLimitReached => 'Friend limit reached';
String get friendsLimitReached => 'You can add up to 10 friends';
@override
String get friendsAddCloseContact => 'Add a close contact';
... ...
... ... @@ -1231,7 +1231,7 @@ class AppLocalizationsZh extends AppLocalizations {
String get friendsAddCloseContactDescription => '添加亲密联系人,多一个人关注你的健康';
@override
String get friendsLimitReached => '好友数量已达上限';
String get friendsLimitReached => '最多只能添加10个好友哦';
@override
String get friendsAddCloseContact => '添加亲密联系人';
... ...