Commit 068524a795c0582b497d3a75a08dc626795edcfe

Authored by 常守达
1 parent 7b773e28

fix(ui):验收修复

... ... @@ -97,13 +97,12 @@ class HomeController extends GetxController {
final result = await Get.find<ClickEventApi>().getClickEvent();
return switch (result) {
AppSuccess(:final data) =>
data.list?.any(
(item) =>
EventConst.keys.contains(item.eventName) &&
(item.clickCnt ?? 0) > 0,
) ??
false,
AppSuccess(:final data) => data.list?.any(
(item) =>
EventConst.keys.contains(item.eventName) &&
(item.clickCnt ?? 0) > 0,
) ??
false,
AppFailure() => false,
};
}
... ... @@ -156,8 +155,12 @@ class HomeController extends GetxController {
}
}
void openTrend(TrendType type, {DateTime? date, ReportPeriod? period}) {
void openTrend(TrendType type,
{DateTime? date, ReportPeriod? period, bool fromTodayTab = true}) {
Get.find<TrendController>().selectType(type, date: date, period: period);
if (fromTodayTab) {
Get.find<TrendController>().selectSelf();
}
changeTab(trendTabIndex);
}
... ... @@ -268,7 +271,7 @@ class HomeController extends GetxController {
final type = params['type'];
final trendType = type == null ? null : getTrendType(type);
if (trendType != null) {
openTrend(trendType);
openTrend(trendType, fromTodayTab: false);
} else {
changeTab(trendTabIndex);
}
... ...
... ... @@ -132,28 +132,9 @@ class TodayTabBody extends StatelessWidget {
slivers: [
SliverToBoxAdapter(
child: Container(
height: 323 + pinnedHeaderHeight,
width: Get.width,
height: Get.width / 375 * (323 + pinnedHeaderHeight),
padding: EdgeInsets.only(bottom: 32),
// decoration: const BoxDecoration(
// gradient: LinearGradient(
// begin: Alignment.topCenter,
// end: Alignment.bottomCenter,
// colors: [
// Color(0xFFC5B0FF),
// Color(0xFFF5F2FF),
// ],
// ),
// ),
// decoration: BoxDecoration(
// image: DecorationImage(
// image: AssetImage(
// controller.v2StressScore.value?.stateBg() ??
// 'assets/images/today/bg_stress_state_0.png',
// ),
// fit: BoxFit.cover,
// alignment: Alignment.topCenter,
// ),
// ),
child: Container(
// margin: EdgeInsets.only(top: pinnedHeaderHeight),
child: Column(
... ... @@ -164,7 +145,8 @@ class TodayTabBody extends StatelessWidget {
controller.v2StressScore.value?.stateBg() ??
'assets/images/today/bg_stress_state_0.png',
fit: BoxFit.cover,
width: double.infinity,
width: Get.width,
height: Get.width / 375 * 345,
)),
),
Obx(
... ...
... ... @@ -62,13 +62,11 @@ class HrvMeasurementBottomSheet extends StatelessWidget {
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: Container(
margin: EdgeInsets.only(left: 16, right: 16),
padding: const EdgeInsets.only(top: 16, bottom: 32),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 342),
child: const _HrvInstructionCard(),
),
child: const _HrvInstructionCard(),
),
),
),
... ...
... ... @@ -287,9 +287,9 @@ class _FaqLink extends StatelessWidget {
style: TextStyle(
color: context.colors.textSecondary,
fontSize: 12,
height: 17 / 12,
fontWeight: FontWeight.w400,
decoration: TextDecoration.underline,
decorationColor: context.colors.textSecondary,
),
),
),
... ...
... ... @@ -322,6 +322,7 @@ class _FaqLink extends StatelessWidget {
height: 17 / 12,
fontWeight: FontWeight.w400,
decoration: TextDecoration.underline,
decorationColor: context.colors.textSecondary,
),
),
),
... ...
... ... @@ -323,6 +323,7 @@ class _FaqLink extends StatelessWidget {
height: 17 / 12,
fontWeight: FontWeight.w400,
decoration: TextDecoration.underline,
decorationColor: context.colors.textSecondary,
),
),
),
... ...
... ... @@ -60,8 +60,18 @@ class TodayHrvChartCard extends StatelessWidget {
const SizedBox(height: 14),
SizedBox(
height: 174,
child: controller.isLoadingToday.value
? Center(
child: Stack(
children: [
_HrvLineChart(
spots: hrvSpots,
stateMap: hrvStateMap,
maxSeconds: _maxChartSeconds(),
getColorByState: _getColorByState,
getTooltipChildren: _getTooltipChildren,
timeLabel: _timeLabel,
),
if (controller.isLoadingToday.value)
Center(
child: Lottie.asset(
'assets/lottie/loading.json',
width: 130,
... ... @@ -70,16 +80,12 @@ class TodayHrvChartCard extends StatelessWidget {
animate: true,
),
)
: hrvSpots.isEmpty
? _EmptyChart(text: context.l10n.noDataAvailableForToday)
: _HrvLineChart(
spots: hrvSpots,
stateMap: hrvStateMap,
maxSeconds: _maxChartSeconds(),
getColorByState: _getColorByState,
getTooltipChildren: _getTooltipChildren,
timeLabel: _timeLabel,
),
else if (hrvSpots.isEmpty)
_EmptyChart(text: context.l10n.noDataAvailableForToday)
else
SizedBox()
],
),
),
Padding(
padding: const EdgeInsets.only(bottom: 14, top: 16),
... ... @@ -127,23 +133,13 @@ class TodayHrvChartCard extends StatelessWidget {
),
SizedBox(
height: 190,
child: controller.isLoadingToday.value
? Center(
child: Lottie.asset(
'assets/lottie/loading.json',
width: 130,
height: 90,
repeat: true,
animate: true,
),
)
: _buildStressChart(
context,
sleepList,
stressPoints,
selectedDate,
vipInfo?.isVip == true,
),
child: _buildStressChart(
context,
sleepList,
stressPoints,
selectedDate,
vipInfo?.isVip == true,
),
),
],
),
... ... @@ -255,20 +251,31 @@ class TodayHrvChartCard extends StatelessWidget {
}
return Stack(
children: [
if (stressPoints.isEmpty)
_StressBarChart(
sleepList: sleepList,
stressPoints: stressPoints,
selectedDate: selectedDate,
isVip: isVip,
onTapPurchase: onTapPurchase,
maxSeconds: _maxChartSeconds(),
getColorByState: _getColorByState,
getTooltip: _getTooltip,
timeLabel: _timeLabel,
),
if (controller.isLoadingToday.value)
Center(
child: Lottie.asset(
'assets/lottie/loading.json',
width: 130,
height: 90,
repeat: true,
animate: true,
),
)
else if (stressPoints.isEmpty)
_EmptyChart(text: context.l10n.noPressureDataAvailableAtThisTime)
else
_StressBarChart(
sleepList: sleepList,
stressPoints: stressPoints,
selectedDate: selectedDate,
isVip: isVip,
onTapPurchase: onTapPurchase,
maxSeconds: _maxChartSeconds(),
getColorByState: _getColorByState,
getTooltip: _getTooltip,
timeLabel: _timeLabel,
),
SizedBox(),
if (isVip != true) ...[
GestureDetector(
onTap: onTapPurchase,
... ...
... ... @@ -26,6 +26,9 @@ class TodayHrvNumberCard extends StatelessWidget {
var otherHrv = (sameDay
? context.l10n.todaySAverageHrv
: context.l10n.averageHrvForTheDay);
var hrvAvg = controller.v2HealthData.value?.hrvAvg ?? 0;
return Row(
children: [
// HRV
... ... @@ -34,7 +37,7 @@ class TodayHrvNumberCard extends StatelessWidget {
label: controller.isFriend
? context.l10n.reportOtherPossessiveTitle(otherHrv)
: otherHrv,
value: controller.v2HealthData.value?.hrvAvg?.toString() ?? '-',
value: hrvAvg > 0 ? hrvAvg.toString() : '-',
unit: 'ms',
),
),
... ...
... ... @@ -300,14 +300,15 @@ class TodayActivityCard extends StatelessWidget {
),
children: [
TextSpan(text: activityStr),
TextSpan(
text: ' ${l10n.reportUnitKcal}',
style: TextStyle(
color: context.colors.textSecondary,
fontSize: 12,
fontWeight: FontWeight.w400,
if (activityStr != '-')
TextSpan(
text: ' ${l10n.reportUnitKcal}',
style: TextStyle(
color: context.colors.textSecondary,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
],
),
),
... ...
... ... @@ -39,118 +39,128 @@ class MembershipOfferView extends GetView<MembershipOfferController> {
controller.unlock();
},
),
child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
padding: EdgeInsets.fromLTRB(
0,
MediaQuery.paddingOf(context).top + 4,
0,
0,
),
child: Stack(
children: [
IgnorePointer(
child: Lottie.asset('assets/lottie/scatter_flowers.json'),
),
Column(
mainAxisSize: MainAxisSize.max,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
SizedBox(height: 48),
OnboardingTitleText(l10n.onboardingMemberTitle),
const SizedBox(height: 12),
OnboardingBodyText(
l10n.onboardingMemberBody,
fontSize: 14,
),
],
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
padding: EdgeInsets.fromLTRB(
0,
MediaQuery.paddingOf(context).top + 4,
0,
0,
),
child: Stack(
children: [
IgnorePointer(
child: Lottie.asset(
'assets/lottie/scatter_flowers.json',
width: Get.width),
),
),
const SizedBox(height: 12),
Image.asset(
'assets/images/user_onboarding/ic_membership_box.png'),
const SizedBox(height: 4),
Obx(() {
final yearInfo = controller.yearlyProductAppleInfo.value;
final yearProduct = controller.yearlyProduct.value;
return yearInfo == null || yearProduct == null
? const SizedBox(child: CircularProgressIndicator())
: Column(
Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
children: [
Text(
'原价${_originDisplayPrice(yearInfo)}/年',
textAlign: TextAlign.center,
style: const TextStyle(
color: Color(0xFFB0B0B6),
fontSize: 16,
decoration: TextDecoration.lineThrough,
decorationColor: Color(0xFFB0B0B6),
letterSpacing: 0,
),
),
const SizedBox(height: 6),
Text(
_displayPrice(yearInfo),
textAlign: TextAlign.center,
style: TextStyle(
color: context.colors.primary,
fontSize: 28,
fontWeight: FontWeight.w600,
letterSpacing: 0,
),
),
const SizedBox(height: 6),
Text(
_planSubtitle(yearProduct, yearInfo),
textAlign: TextAlign.center,
style: const TextStyle(
color: Color(0xFFB0B0B6),
fontSize: 12,
letterSpacing: 0,
),
SizedBox(height: 48),
OnboardingTitleText(l10n.onboardingMemberTitle),
const SizedBox(height: 12),
OnboardingBodyText(
l10n.onboardingMemberBody,
fontSize: 14,
),
],
);
}),
const SizedBox(height: 58),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
var arguments = {};
if (controller.isFromOnboard) {
arguments['from'] = 'onboarding';
} else {
arguments['channel_type'] =
Get.arguments?['channel_type'] ?? '';
}
Get.offNamed(Routes.PURCHASE, arguments: arguments);
},
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 24,
),
child: Text(
l10n.onboardingMemberAllOptions,
textAlign: TextAlign.center,
style: TextStyle(
color: context.colors.textPrimary,
fontSize: 12,
decoration: TextDecoration.underline,
decorationColor: context.colors.textPrimary,
letterSpacing: 0,
),
),
),
const SizedBox(height: 12),
Image.asset(
'assets/images/user_onboarding/ic_membership_box.png'),
const SizedBox(height: 4),
Obx(() {
final yearInfo =
controller.yearlyProductAppleInfo.value;
final yearProduct = controller.yearlyProduct.value;
return yearInfo == null || yearProduct == null
? const SizedBox(
child: CircularProgressIndicator())
: Column(
children: [
Text(
'原价${_originDisplayPrice(yearInfo)}/年',
textAlign: TextAlign.center,
style: const TextStyle(
color: Color(0xFF78787D),
fontSize: 16,
decoration:
TextDecoration.lineThrough,
decorationColor: Color(0xFF78787D),
letterSpacing: 0,
),
),
const SizedBox(height: 6),
Text(
_displayPrice(yearInfo),
textAlign: TextAlign.center,
style: TextStyle(
color: context.colors.primary,
fontSize: 28,
fontWeight: FontWeight.w600,
letterSpacing: 0,
),
),
const SizedBox(height: 6),
Text(
_planSubtitle(yearProduct, yearInfo),
textAlign: TextAlign.center,
style: const TextStyle(
color: Color(0xFF78787D),
fontSize: 12,
letterSpacing: 0,
),
),
],
);
}),
],
),
],
),
),
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
var arguments = {};
if (controller.isFromOnboard) {
arguments['from'] = 'onboarding';
} else {
arguments['channel_type'] =
Get.arguments?['channel_type'] ?? '';
}
Get.offNamed(Routes.PURCHASE, arguments: arguments);
},
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 12,
horizontal: 24,
),
child: Text(
l10n.onboardingMemberAllOptions,
textAlign: TextAlign.center,
style: TextStyle(
color: context.colors.textPrimary,
fontSize: 12,
decoration: TextDecoration.underline,
decorationColor: context.colors.textPrimary,
letterSpacing: 0,
),
],
),
),
],
),
),
const SizedBox(height: 140),
],
),
));
}
... ...
... ... @@ -269,6 +269,7 @@ class _FaqLink extends StatelessWidget {
fontSize: 12,
height: 17 / 12,
decoration: TextDecoration.underline,
decorationColor: context.colors.textSecondary,
),
),
),
... ...
... ... @@ -139,8 +139,9 @@ class _IntroPage extends StatelessWidget {
const SizedBox(height: 12),
Image.asset(
'assets/images/user_onboarding/bg_onboarding_watch.png',
height: 384,
fit: BoxFit.fitHeight,
height: Get.width / 375 * 384,
width: Get.width,
fit: BoxFit.cover,
),
const SizedBox(height: 12),
OnboardingEmRichText(
... ... @@ -284,13 +285,15 @@ class _HrvIntroPageState extends State<_HrvIntroPage>
_isRepeat
? Lottie.asset(
'assets/lottie/onboarding_hrv_repeat.json',
height: 210,
height: Get.width / 375 * 210,
width: Get.width,
fit: BoxFit.cover,
repeat: true,
)
: Lottie.asset(
'assets/lottie/onboarding_hrv_once.json',
height: 210,
height: Get.width / 375 * 210,
width: Get.width,
fit: BoxFit.cover,
controller: _controller,
onLoaded: (composition) {
... ... @@ -301,7 +304,7 @@ class _HrvIntroPageState extends State<_HrvIntroPage>
),
const SizedBox(height: 31),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(horizontal: 48),
child: Text(
l10n.onboardingHrvDescription,
textAlign: TextAlign.center,
... ... @@ -457,62 +460,87 @@ class _HrvResearchPageState extends State<_HrvResearchPage> {
),
];
return OnboardingPageScrollBody(
horizontalPadding: 16,
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: OnboardingTitleText(
l10n.onboardingResearchTitle,
maxWidth: 320,
final topPadding = MediaQuery.paddingOf(context).top + 4;
return LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
physics: const ClampingScrollPhysics(),
padding: EdgeInsets.fromLTRB(16, topPadding, 16, 140),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight - topPadding - 140,
),
),
const SizedBox(height: 24),
// ── Pager 区域 ──
NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollStartNotification &&
notification.dragDetails != null) {
// 用户开始拖拽,暂停自动播放
if (!_userScrolling) {
setState(() => _userScrolling = true);
_stopAutoScroll();
}
} else if (notification is ScrollEndNotification) {
// 手势结束,恢复自动播放
if (_userScrolling) {
setState(() => _userScrolling = false);
_startAutoScroll();
}
}
return false;
},
child: SizedBox(
height: 361,
child: PageView.builder(
controller: _pageController,
itemCount: _virtualCount,
onPageChanged: (_) => setState(() {}),
itemBuilder: (context, virtualIndex) {
final realIndex = virtualIndex % _realCount;
return pages[realIndex];
},
child: IntrinsicHeight(
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: OnboardingTitleText(
l10n.onboardingResearchTitle,
maxWidth: 320,
),
),
SizedBox(
height: 16,
),
Expanded(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
NotificationListener<ScrollNotification>(
onNotification: (notification) {
if (notification is ScrollStartNotification &&
notification.dragDetails != null) {
// 用户开始拖拽,暂停自动播放
if (!_userScrolling) {
setState(() => _userScrolling = true);
_stopAutoScroll();
}
} else if (notification
is ScrollEndNotification) {
// 手势结束,恢复自动播放
if (_userScrolling) {
setState(() => _userScrolling = false);
_startAutoScroll();
}
}
return false;
},
child: SizedBox(
height: 361,
child: PageView.builder(
controller: _pageController,
itemCount: _virtualCount,
onPageChanged: (_) => setState(() {}),
itemBuilder: (context, virtualIndex) {
final realIndex = virtualIndex % _realCount;
return pages[realIndex];
},
),
),
),
const SizedBox(height: 20),
AnimatedBuilder(
animation: _pageController,
builder: (context, _) {
return OnboardingPageIndicators(
activeIndex: _activeRealIndex,
count: _realCount,
);
},
),
],
),
),
),
],
),
),
),
const SizedBox(height: 20),
AnimatedBuilder(
animation: _pageController,
builder: (context, _) {
return OnboardingPageIndicators(
activeIndex: _activeRealIndex,
count: _realCount,
);
},
),
],
),
);
},
);
}
}
... ... @@ -564,8 +592,9 @@ class HealthPermissionPage extends StatelessWidget {
const SizedBox(height: 28),
Image.asset(
'assets/images/permission/apple_open_health_kit_cn.png',
height: 295,
fit: BoxFit.fitHeight,
height: (Get.width - 80) / 295 * 360,
width: Get.width - 80,
fit: BoxFit.cover,
),
const SizedBox(height: 16),
Padding(
... ...
... ... @@ -20,16 +20,30 @@ class ResearchCard extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
height: 320,
width: 171,
width: 172,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(32),
image: DecorationImage(image: AssetImage(image), fit: BoxFit.fill),
),
padding: const EdgeInsets.fromLTRB(12, 24, 12, 28),
child: Stack(
children: [
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
width: 172,
height: 212,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(32),
image: DecorationImage(
image: AssetImage(image), fit: BoxFit.cover),
),
),
),
Positioned(
top: 0,
left: 0,
right: 0,
... ...