Commit e76e2067555a8fbe68301dd953d5cf0330695552

Authored by 常守达
1 parent 4967db0c

fix(today):图标tooltip,分享链接

... ... @@ -213,11 +213,11 @@ class TodayController extends GetMaterialController {
void changeDate(DateTime date, {DateTime? focused}) {
final normalizedDate = _clampDate(date);
// if (!DateUtils.isSameDay(date, lastSelectableDay.value) &&
// !_userStateService.isVip) {
// toPremiumPage('今日-查看非当日数据');
// return;
// }
if (!DateUtils.isSameDay(date, lastSelectableDay.value) &&
!_userStateService.isVip) {
toPremiumPage('今日-查看非当日数据');
return;
}
selectedDate.value = normalizedDate;
focusedDay.value = _clampDate(focused ?? normalizedDate);
}
... ... @@ -339,6 +339,7 @@ class TodayController extends GetMaterialController {
},
);
} else {
unawaited(loadDataForDate(selectedDate.value));
await checkUploadedStatus(firstTime, result);
}
} catch (e) {}
... ... @@ -453,7 +454,7 @@ class TodayController extends GetMaterialController {
hrvAnnotations.clear();
v2HealthData.value = null;
v2StressScore.value = null;
// v2StressScore.value = null;
}
toTrendHrvPage() {
... ... @@ -646,7 +647,7 @@ class TodayController extends GetMaterialController {
checkAddFriendVisible();
checkHrvAdBannerVisible();
checkHealthDataAuthCardVisible();
unawaited(loadDataForDate(selectedDate.value));
// unawaited(loadDataForDate(selectedDate.value));
}
}
... ... @@ -660,6 +661,6 @@ class TodayController extends GetMaterialController {
checkAddFriendVisible();
checkHrvAdBannerVisible();
checkHealthDataAuthCardVisible();
await loadDataForDate(selectedDate.value);
// await loadDataForDate(selectedDate.value);
}
}
... ...
... ... @@ -37,12 +37,9 @@ class MyTab extends GetView<MyController> {
return ListView(
physics: const ClampingScrollPhysics(),
padding: EdgeInsets.fromLTRB(
16,
59,
16,
150 + MediaQuery.paddingOf(context).bottom,
),
padding: EdgeInsets.fromLTRB(16, 59, 16, 0
// 150 + MediaQuery.paddingOf(context).bottom,
),
children: [
Padding(
padding: const EdgeInsets.only(left: 8),
... ... @@ -68,16 +65,16 @@ class MyTab extends GetView<MyController> {
title: context.l10n.help,
onTap: () => Get.toNamed(Routes.HELP),
),
const SizedBox(height: 12),
_SettingsRow(
title: 'Apple Health Upload 测试',
onTap: () {
controller.testAppleHealthUpload();
},
),
if (environmentConfig.isDebug) ...[
const SizedBox(height: 12),
_SettingsRow(
title: 'Apple Health Upload 测试',
onTap: () {
controller.testAppleHealthUpload();
},
),
const SizedBox(height: 12),
_SettingsRow(
title: 'Developer options',
onTap: () {
Get.toNamed(Routes.DEVELOPER_OPTIONS);
... ...
... ... @@ -141,7 +141,8 @@ class TodayTabBody extends StatelessWidget {
child: CircularProgressIndicator()),
)
: Image.asset(
controller.v2StressScore.value!.stateBg(),
controller.v2StressScore.value?.stateBg() ??
'assets/images/today/bg_stress_state_0.png',
fit: BoxFit.cover,
width: Get.width,
height: Get.width / 375 * 345,
... ...
... ... @@ -515,6 +515,15 @@ class _HrvLineChartState extends State<_HrvLineChart> {
Timer? _hrvTooltipTimer;
@override
void didUpdateWidget(covariant _HrvLineChart oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.spots != oldWidget.spots) {
_hrvTooltipTimer?.cancel();
_hrvShowingSpots = [];
}
}
@override
void dispose() {
_hrvTooltipTimer?.cancel();
super.dispose();
... ... @@ -614,6 +623,8 @@ class _HrvLineChartState extends State<_HrvLineChart> {
ShowingTooltipIndicators(_hrvShowingSpots),
],
lineTouchData: LineTouchData(
getTouchLineStart: (barData, spotIndex) => 0,
getTouchLineEnd: (barData, spotIndex) => 90,
handleBuiltInTouches: false,
touchSpotThreshold: 20.0,
touchCallback:
... ... @@ -673,6 +684,8 @@ class _HrvLineChartState extends State<_HrvLineChart> {
},
touchTooltipData: LineTouchTooltipData(
tooltipRoundedRadius: 8,
showOnTopOfTheChartBoxArea: true,
tooltipMargin: 6,
tooltipBorder: BorderSide.none,
maxContentWidth: 400,
fitInsideHorizontally: true,
... ... @@ -737,6 +750,18 @@ class _StressBarChartState extends State<_StressBarChart> {
Timer? _stressTooltipTimer;
@override
void didUpdateWidget(covariant _StressBarChart oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedDate != oldWidget.selectedDate ||
widget.stressPoints != oldWidget.stressPoints) {
_stressTooltipTimer?.cancel();
setState(() {
_stressTouchedGroupIndex = null;
});
}
}
@override
void dispose() {
_stressTooltipTimer?.cancel();
super.dispose();
... ... @@ -749,12 +774,14 @@ class _StressBarChartState extends State<_StressBarChart> {
// 右轴 reservedSize=22,不占用空间设为 0.0
const rightAxisWidth = 0.0;
const slotSec = 6 * 60;
final slotCount = (widget.maxSeconds / slotSec).ceil().clamp(1, 9999);
// 关键:由于循环条件是 sec <= maxSec,实际绘制的组数(groups.length)为总槽数 + 1
final slotCount = (widget.maxSeconds / slotSec).round() + 1;
const double groupsSpace = 0.25; // 设置间隔为 1 像素
final barWidth = (constraints.maxWidth -
rightAxisWidth -
(groupsSpace * (slotCount - 1))) /
slotCount;
// 因为 BarChart 外层 Container 有 padding: EdgeInsets.only(right: 3),所以其实际宽度减少了 3.0
final chartWidth = constraints.maxWidth - 3.0;
final barWidth =
(chartWidth - rightAxisWidth - (groupsSpace * (slotCount - 1))) /
slotCount;
// 根据 V2SleepTimeRange 计算多段睡眠区间的 Positioned widgets
final sleepWidgets = <Widget>[];
... ... @@ -873,6 +900,25 @@ class _StressBarChartState extends State<_StressBarChart> {
final gridBottom = chartHeight - 20.0;
final gridHeight = gridBottom - gridTop;
// 自定义指示线 (只在 Stack 层手绘一条垂直灰色指示线,与柱子对齐,高度到达顶部以与 tooltip 连上)
Positioned? customIndicator;
if (_stressTouchedGroupIndex != null) {
final stepWidth = barWidth + groupsSpace;
final centerOfBar =
_stressTouchedGroupIndex! * stepWidth + (barWidth / 2);
customIndicator = Positioned(
left: centerOfBar - 1.0, // 2px 宽度,居中
width: 2.0,
top: 16.0, // 向上延展以连接 tooltip 底部
bottom: chartHeight - gridBottom, // 修正:bottom 指的是距离 Stack 底部的像素值
child: IgnorePointer(
child: ColoredBox(
color: context.colors.textTertiary.withAlpha(125),
),
),
);
}
return Stack(
clipBehavior: Clip.none,
children: [
... ... @@ -1039,7 +1085,9 @@ class _StressBarChartState extends State<_StressBarChart> {
tooltipBorder: BorderSide.none,
maxContentWidth: 400,
fitInsideHorizontally: true,
fitInsideVertically: false,
fitInsideVertically:
false, // 允许 tooltip 超出 BarChart 顶边界(绘制在 18px 的 top margin 区域内)
tooltipMargin: 6, // 调整为和折线图一样的 6 像素空隙
getTooltipColor: (touchedSpot) => const Color(0xFFF3F3F3),
tooltipPadding: const EdgeInsets.only(
left: 12,
... ... @@ -1057,7 +1105,8 @@ class _StressBarChartState extends State<_StressBarChart> {
color: Colors.white,
fontWeight: FontWeight.bold,
),
children: widget.getTooltip(rod, group.x, state),
children: widget.getTooltip(
group.barRods[0], group.x, state),
textAlign: TextAlign.start,
);
},
... ... @@ -1066,6 +1115,8 @@ class _StressBarChartState extends State<_StressBarChart> {
),
),
),
if (customIndicator != null)
customIndicator, // 将指示线移动到 Container (BarChart) 之后绘制,防止被盖住
...sleepWidgets,
// 手绘 100、50、0 标签在对应的横线上方
Positioned(
... ... @@ -1126,7 +1177,8 @@ class _StressBarChartState extends State<_StressBarChart> {
final isTouched = groupIndex == _stressTouchedGroupIndex;
groups.add(BarChartGroupData(
x: sec,
showingTooltipIndicators: isTouched ? [0] : [],
barsSpace: 0,
showingTooltipIndicators: isTouched ? [1] : [],
barRods: [
BarChartRodData(
toY: entry != null ? entry.value.toDouble() : 0,
... ... @@ -1136,6 +1188,11 @@ class _StressBarChartState extends State<_StressBarChart> {
: Colors.transparent,
borderRadius: BorderRadius.circular(barWidth / 2),
),
BarChartRodData(
toY: 100, // 用于固定 tooltip 在顶部
width: 0, // 0 宽度,不占空间
color: Colors.transparent,
),
],
));
}
... ...
... ... @@ -551,7 +551,7 @@
"filming": "Filming",
"unlockTheProVersion": "Unlock the Pro Version",
"embarkOnAJourneyOfStressAwarenessAndWellnessSupport": "Embark on a Journey of Stress Awareness and Wellness Support",
"sharePartnerCodeTemplate": "Hey ❤️ I found an app called DoubleFeel!\nIt helps us care for each other — track stress and sleep, view HRV and physical status, and stay updated on each other's health in real-time.\nCome join me 👉 https://doublefeel.cn/\nMy friend ID: {inviteCode}",
"sharePartnerCodeTemplate": "Hey ❤️ I found an app called DoubleFeel!\nIt helps us care for each other — track stress and sleep, view HRV and physical status, and stay updated on each other's health in real-time.\nCome join me 👉 https://apps.apple.com/cn/app/doublefeel-%E5%8F%8C%E4%BA%BA%E6%83%85%E7%BB%AA%E5%85%B1%E4%BA%ABhrv%E5%8E%8B%E5%8A%9B%E6%B0%B4%E5%B9%B3%E8%87%AA%E6%B5%8B%E7%9D%A1%E7%9C%A0%E8%AE%B0%E5%BD%95/id6747254434\nMy friend ID: {inviteCode}",
"@sharePartnerCodeTemplate": {
"description": "Share partner code template",
"placeholders": {
... ... @@ -693,4 +693,4 @@
"feedbackSubmitSuccessTitle": "Feedback submitted successfully",
"feedbackSubmitSuccessMessage": "Thank you for your feedback. If further communication is needed, we will contact you via the email address you left as soon as possible. Please keep an eye on your inbox.",
"feedbackSubmitSuccessConfirm": "OK"
}
}
\ No newline at end of file
... ...
... ... @@ -930,7 +930,7 @@
"filming": "拍摄",
"unlockTheProVersion": "解锁专业版",
"embarkOnAJourneyOfStressAwarenessAndWellnessSupport": "开启压力预警与健康陪伴之旅",
"sharePartnerCodeTemplate": "嘿 ❤️ 我发现了一个叫 DoubleFeel 的 App!\n它可以帮我们互相关心 — 记录压力和睡眠、查看 HRV 与身体状态、及时了解彼此的健康状况。\n快来一起使用吧 👉 https://doublefeel.cn/\n我的好友id:{inviteCode}",
"sharePartnerCodeTemplate": "嘿 ❤️ 我发现了一个叫 DoubleFeel 的 App!\n它可以帮我们互相关心 — 记录压力和睡眠、查看 HRV 与身体状态、及时了解彼此的健康状况。\n快来一起使用吧 👉 https://apps.apple.com/cn/app/doublefeel-%E5%8F%8C%E4%BA%BA%E6%83%85%E7%BB%AA%E5%85%B1%E4%BA%ABhrv%E5%8E%8B%E5%8A%9B%E6%B0%B4%E5%B9%B3%E8%87%AA%E6%B5%8B%E7%9D%A1%E7%9C%A0%E8%AE%B0%E5%BD%95/id6747254434\n我的好友id:{inviteCode}",
"@sharePartnerCodeTemplate": {
"description": "分享好友邀请码文案模板",
"placeholders": {
... ...
... ... @@ -3408,7 +3408,7 @@ abstract class AppLocalizations {
/// 分享好友邀请码文案模板
///
/// In zh, this message translates to:
/// **'嘿 ❤️ 我发现了一个叫 DoubleFeel 的 App!\n它可以帮我们互相关心 — 记录压力和睡眠、查看 HRV 与身体状态、及时了解彼此的健康状况。\n快来一起使用吧 👉 https://doublefeel.cn/\n我的好友id:{inviteCode}'**
/// **'嘿 ❤️ 我发现了一个叫 DoubleFeel 的 App!\n它可以帮我们互相关心 — 记录压力和睡眠、查看 HRV 与身体状态、及时了解彼此的健康状况。\n快来一起使用吧 👉 https://apps.apple.com/cn/app/doublefeel-%E5%8F%8C%E4%BA%BA%E6%83%85%E7%BB%AA%E5%85%B1%E4%BA%ABhrv%E5%8E%8B%E5%8A%9B%E6%B0%B4%E5%B9%B3%E8%87%AA%E6%B5%8B%E7%9D%A1%E7%9C%A0%E8%AE%B0%E5%BD%95/id6747254434\n我的好友id:{inviteCode}'**
String sharePartnerCodeTemplate(String inviteCode);
/// No description provided for @bindPartnerIdNotExistTitle.
... ...
... ... @@ -1914,7 +1914,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String sharePartnerCodeTemplate(String inviteCode) {
return 'Hey ❤️ I found an app called DoubleFeel!\nIt helps us care for each other — track stress and sleep, view HRV and physical status, and stay updated on each other\'s health in real-time.\nCome join me 👉 https://doublefeel.cn/\nMy friend ID: $inviteCode';
return 'Hey ❤️ I found an app called DoubleFeel!\nIt helps us care for each other — track stress and sleep, view HRV and physical status, and stay updated on each other\'s health in real-time.\nCome join me 👉 https://apps.apple.com/cn/app/doublefeel-%E5%8F%8C%E4%BA%BA%E6%83%85%E7%BB%AA%E5%85%B1%E4%BA%ABhrv%E5%8E%8B%E5%8A%9B%E6%B0%B4%E5%B9%B3%E8%87%AA%E6%B5%8B%E7%9D%A1%E7%9C%A0%E8%AE%B0%E5%BD%95/id6747254434\nMy friend ID: $inviteCode';
}
@override
... ...
... ... @@ -1816,7 +1816,7 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String sharePartnerCodeTemplate(String inviteCode) {
return '嘿 ❤️ 我发现了一个叫 DoubleFeel 的 App!\n它可以帮我们互相关心 — 记录压力和睡眠、查看 HRV 与身体状态、及时了解彼此的健康状况。\n快来一起使用吧 👉 https://doublefeel.cn/\n我的好友id:$inviteCode';
return '嘿 ❤️ 我发现了一个叫 DoubleFeel 的 App!\n它可以帮我们互相关心 — 记录压力和睡眠、查看 HRV 与身体状态、及时了解彼此的健康状况。\n快来一起使用吧 👉 https://apps.apple.com/cn/app/doublefeel-%E5%8F%8C%E4%BA%BA%E6%83%85%E7%BB%AA%E5%85%B1%E4%BA%ABhrv%E5%8E%8B%E5%8A%9B%E6%B0%B4%E5%B9%B3%E8%87%AA%E6%B5%8B%E7%9D%A1%E7%9C%A0%E8%AE%B0%E5%BD%95/id6747254434\n我的好友id:$inviteCode';
}
@override
... ...