Showing
13 changed files
with
302 additions
and
252 deletions
| @@ -97,13 +97,12 @@ class HomeController extends GetxController { | @@ -97,13 +97,12 @@ class HomeController extends GetxController { | ||
| 97 | 97 | ||
| 98 | final result = await Get.find<ClickEventApi>().getClickEvent(); | 98 | final result = await Get.find<ClickEventApi>().getClickEvent(); |
| 99 | return switch (result) { | 99 | return switch (result) { |
| 100 | - AppSuccess(:final data) => | ||
| 101 | - data.list?.any( | ||
| 102 | - (item) => | ||
| 103 | - EventConst.keys.contains(item.eventName) && | ||
| 104 | - (item.clickCnt ?? 0) > 0, | ||
| 105 | - ) ?? | ||
| 106 | - false, | 100 | + AppSuccess(:final data) => data.list?.any( |
| 101 | + (item) => | ||
| 102 | + EventConst.keys.contains(item.eventName) && | ||
| 103 | + (item.clickCnt ?? 0) > 0, | ||
| 104 | + ) ?? | ||
| 105 | + false, | ||
| 107 | AppFailure() => false, | 106 | AppFailure() => false, |
| 108 | }; | 107 | }; |
| 109 | } | 108 | } |
| @@ -156,8 +155,12 @@ class HomeController extends GetxController { | @@ -156,8 +155,12 @@ class HomeController extends GetxController { | ||
| 156 | } | 155 | } |
| 157 | } | 156 | } |
| 158 | 157 | ||
| 159 | - void openTrend(TrendType type, {DateTime? date, ReportPeriod? period}) { | 158 | + void openTrend(TrendType type, |
| 159 | + {DateTime? date, ReportPeriod? period, bool fromTodayTab = true}) { | ||
| 160 | Get.find<TrendController>().selectType(type, date: date, period: period); | 160 | Get.find<TrendController>().selectType(type, date: date, period: period); |
| 161 | + if (fromTodayTab) { | ||
| 162 | + Get.find<TrendController>().selectSelf(); | ||
| 163 | + } | ||
| 161 | changeTab(trendTabIndex); | 164 | changeTab(trendTabIndex); |
| 162 | } | 165 | } |
| 163 | 166 | ||
| @@ -268,7 +271,7 @@ class HomeController extends GetxController { | @@ -268,7 +271,7 @@ class HomeController extends GetxController { | ||
| 268 | final type = params['type']; | 271 | final type = params['type']; |
| 269 | final trendType = type == null ? null : getTrendType(type); | 272 | final trendType = type == null ? null : getTrendType(type); |
| 270 | if (trendType != null) { | 273 | if (trendType != null) { |
| 271 | - openTrend(trendType); | 274 | + openTrend(trendType, fromTodayTab: false); |
| 272 | } else { | 275 | } else { |
| 273 | changeTab(trendTabIndex); | 276 | changeTab(trendTabIndex); |
| 274 | } | 277 | } |
| @@ -132,28 +132,9 @@ class TodayTabBody extends StatelessWidget { | @@ -132,28 +132,9 @@ class TodayTabBody extends StatelessWidget { | ||
| 132 | slivers: [ | 132 | slivers: [ |
| 133 | SliverToBoxAdapter( | 133 | SliverToBoxAdapter( |
| 134 | child: Container( | 134 | child: Container( |
| 135 | - height: 323 + pinnedHeaderHeight, | 135 | + width: Get.width, |
| 136 | + height: Get.width / 375 * (323 + pinnedHeaderHeight), | ||
| 136 | padding: EdgeInsets.only(bottom: 32), | 137 | padding: EdgeInsets.only(bottom: 32), |
| 137 | - // decoration: const BoxDecoration( | ||
| 138 | - // gradient: LinearGradient( | ||
| 139 | - // begin: Alignment.topCenter, | ||
| 140 | - // end: Alignment.bottomCenter, | ||
| 141 | - // colors: [ | ||
| 142 | - // Color(0xFFC5B0FF), | ||
| 143 | - // Color(0xFFF5F2FF), | ||
| 144 | - // ], | ||
| 145 | - // ), | ||
| 146 | - // ), | ||
| 147 | - // decoration: BoxDecoration( | ||
| 148 | - // image: DecorationImage( | ||
| 149 | - // image: AssetImage( | ||
| 150 | - // controller.v2StressScore.value?.stateBg() ?? | ||
| 151 | - // 'assets/images/today/bg_stress_state_0.png', | ||
| 152 | - // ), | ||
| 153 | - // fit: BoxFit.cover, | ||
| 154 | - // alignment: Alignment.topCenter, | ||
| 155 | - // ), | ||
| 156 | - // ), | ||
| 157 | child: Container( | 138 | child: Container( |
| 158 | // margin: EdgeInsets.only(top: pinnedHeaderHeight), | 139 | // margin: EdgeInsets.only(top: pinnedHeaderHeight), |
| 159 | child: Column( | 140 | child: Column( |
| @@ -164,7 +145,8 @@ class TodayTabBody extends StatelessWidget { | @@ -164,7 +145,8 @@ class TodayTabBody extends StatelessWidget { | ||
| 164 | controller.v2StressScore.value?.stateBg() ?? | 145 | controller.v2StressScore.value?.stateBg() ?? |
| 165 | 'assets/images/today/bg_stress_state_0.png', | 146 | 'assets/images/today/bg_stress_state_0.png', |
| 166 | fit: BoxFit.cover, | 147 | fit: BoxFit.cover, |
| 167 | - width: double.infinity, | 148 | + width: Get.width, |
| 149 | + height: Get.width / 375 * 345, | ||
| 168 | )), | 150 | )), |
| 169 | ), | 151 | ), |
| 170 | Obx( | 152 | Obx( |
| @@ -62,13 +62,11 @@ class HrvMeasurementBottomSheet extends StatelessWidget { | @@ -62,13 +62,11 @@ class HrvMeasurementBottomSheet extends StatelessWidget { | ||
| 62 | ), | 62 | ), |
| 63 | ), | 63 | ), |
| 64 | Expanded( | 64 | Expanded( |
| 65 | - child: Padding( | ||
| 66 | - padding: const EdgeInsets.fromLTRB(16, 8, 16, 16), | 65 | + child: Container( |
| 66 | + margin: EdgeInsets.only(left: 16, right: 16), | ||
| 67 | + padding: const EdgeInsets.only(top: 16, bottom: 32), | ||
| 67 | child: Center( | 68 | child: Center( |
| 68 | - child: ConstrainedBox( | ||
| 69 | - constraints: const BoxConstraints(maxWidth: 342), | ||
| 70 | - child: const _HrvInstructionCard(), | ||
| 71 | - ), | 69 | + child: const _HrvInstructionCard(), |
| 72 | ), | 70 | ), |
| 73 | ), | 71 | ), |
| 74 | ), | 72 | ), |
| @@ -287,9 +287,9 @@ class _FaqLink extends StatelessWidget { | @@ -287,9 +287,9 @@ class _FaqLink extends StatelessWidget { | ||
| 287 | style: TextStyle( | 287 | style: TextStyle( |
| 288 | color: context.colors.textSecondary, | 288 | color: context.colors.textSecondary, |
| 289 | fontSize: 12, | 289 | fontSize: 12, |
| 290 | - height: 17 / 12, | ||
| 291 | fontWeight: FontWeight.w400, | 290 | fontWeight: FontWeight.w400, |
| 292 | decoration: TextDecoration.underline, | 291 | decoration: TextDecoration.underline, |
| 292 | + decorationColor: context.colors.textSecondary, | ||
| 293 | ), | 293 | ), |
| 294 | ), | 294 | ), |
| 295 | ), | 295 | ), |
| @@ -322,6 +322,7 @@ class _FaqLink extends StatelessWidget { | @@ -322,6 +322,7 @@ class _FaqLink extends StatelessWidget { | ||
| 322 | height: 17 / 12, | 322 | height: 17 / 12, |
| 323 | fontWeight: FontWeight.w400, | 323 | fontWeight: FontWeight.w400, |
| 324 | decoration: TextDecoration.underline, | 324 | decoration: TextDecoration.underline, |
| 325 | + decorationColor: context.colors.textSecondary, | ||
| 325 | ), | 326 | ), |
| 326 | ), | 327 | ), |
| 327 | ), | 328 | ), |
| @@ -323,6 +323,7 @@ class _FaqLink extends StatelessWidget { | @@ -323,6 +323,7 @@ class _FaqLink extends StatelessWidget { | ||
| 323 | height: 17 / 12, | 323 | height: 17 / 12, |
| 324 | fontWeight: FontWeight.w400, | 324 | fontWeight: FontWeight.w400, |
| 325 | decoration: TextDecoration.underline, | 325 | decoration: TextDecoration.underline, |
| 326 | + decorationColor: context.colors.textSecondary, | ||
| 326 | ), | 327 | ), |
| 327 | ), | 328 | ), |
| 328 | ), | 329 | ), |
| @@ -60,8 +60,18 @@ class TodayHrvChartCard extends StatelessWidget { | @@ -60,8 +60,18 @@ class TodayHrvChartCard extends StatelessWidget { | ||
| 60 | const SizedBox(height: 14), | 60 | const SizedBox(height: 14), |
| 61 | SizedBox( | 61 | SizedBox( |
| 62 | height: 174, | 62 | height: 174, |
| 63 | - child: controller.isLoadingToday.value | ||
| 64 | - ? Center( | 63 | + child: Stack( |
| 64 | + children: [ | ||
| 65 | + _HrvLineChart( | ||
| 66 | + spots: hrvSpots, | ||
| 67 | + stateMap: hrvStateMap, | ||
| 68 | + maxSeconds: _maxChartSeconds(), | ||
| 69 | + getColorByState: _getColorByState, | ||
| 70 | + getTooltipChildren: _getTooltipChildren, | ||
| 71 | + timeLabel: _timeLabel, | ||
| 72 | + ), | ||
| 73 | + if (controller.isLoadingToday.value) | ||
| 74 | + Center( | ||
| 65 | child: Lottie.asset( | 75 | child: Lottie.asset( |
| 66 | 'assets/lottie/loading.json', | 76 | 'assets/lottie/loading.json', |
| 67 | width: 130, | 77 | width: 130, |
| @@ -70,16 +80,12 @@ class TodayHrvChartCard extends StatelessWidget { | @@ -70,16 +80,12 @@ class TodayHrvChartCard extends StatelessWidget { | ||
| 70 | animate: true, | 80 | animate: true, |
| 71 | ), | 81 | ), |
| 72 | ) | 82 | ) |
| 73 | - : hrvSpots.isEmpty | ||
| 74 | - ? _EmptyChart(text: context.l10n.noDataAvailableForToday) | ||
| 75 | - : _HrvLineChart( | ||
| 76 | - spots: hrvSpots, | ||
| 77 | - stateMap: hrvStateMap, | ||
| 78 | - maxSeconds: _maxChartSeconds(), | ||
| 79 | - getColorByState: _getColorByState, | ||
| 80 | - getTooltipChildren: _getTooltipChildren, | ||
| 81 | - timeLabel: _timeLabel, | ||
| 82 | - ), | 83 | + else if (hrvSpots.isEmpty) |
| 84 | + _EmptyChart(text: context.l10n.noDataAvailableForToday) | ||
| 85 | + else | ||
| 86 | + SizedBox() | ||
| 87 | + ], | ||
| 88 | + ), | ||
| 83 | ), | 89 | ), |
| 84 | Padding( | 90 | Padding( |
| 85 | padding: const EdgeInsets.only(bottom: 14, top: 16), | 91 | padding: const EdgeInsets.only(bottom: 14, top: 16), |
| @@ -127,23 +133,13 @@ class TodayHrvChartCard extends StatelessWidget { | @@ -127,23 +133,13 @@ class TodayHrvChartCard extends StatelessWidget { | ||
| 127 | ), | 133 | ), |
| 128 | SizedBox( | 134 | SizedBox( |
| 129 | height: 190, | 135 | height: 190, |
| 130 | - child: controller.isLoadingToday.value | ||
| 131 | - ? Center( | ||
| 132 | - child: Lottie.asset( | ||
| 133 | - 'assets/lottie/loading.json', | ||
| 134 | - width: 130, | ||
| 135 | - height: 90, | ||
| 136 | - repeat: true, | ||
| 137 | - animate: true, | ||
| 138 | - ), | ||
| 139 | - ) | ||
| 140 | - : _buildStressChart( | ||
| 141 | - context, | ||
| 142 | - sleepList, | ||
| 143 | - stressPoints, | ||
| 144 | - selectedDate, | ||
| 145 | - vipInfo?.isVip == true, | ||
| 146 | - ), | 136 | + child: _buildStressChart( |
| 137 | + context, | ||
| 138 | + sleepList, | ||
| 139 | + stressPoints, | ||
| 140 | + selectedDate, | ||
| 141 | + vipInfo?.isVip == true, | ||
| 142 | + ), | ||
| 147 | ), | 143 | ), |
| 148 | ], | 144 | ], |
| 149 | ), | 145 | ), |
| @@ -255,20 +251,31 @@ class TodayHrvChartCard extends StatelessWidget { | @@ -255,20 +251,31 @@ class TodayHrvChartCard extends StatelessWidget { | ||
| 255 | } | 251 | } |
| 256 | return Stack( | 252 | return Stack( |
| 257 | children: [ | 253 | children: [ |
| 258 | - if (stressPoints.isEmpty) | 254 | + _StressBarChart( |
| 255 | + sleepList: sleepList, | ||
| 256 | + stressPoints: stressPoints, | ||
| 257 | + selectedDate: selectedDate, | ||
| 258 | + isVip: isVip, | ||
| 259 | + onTapPurchase: onTapPurchase, | ||
| 260 | + maxSeconds: _maxChartSeconds(), | ||
| 261 | + getColorByState: _getColorByState, | ||
| 262 | + getTooltip: _getTooltip, | ||
| 263 | + timeLabel: _timeLabel, | ||
| 264 | + ), | ||
| 265 | + if (controller.isLoadingToday.value) | ||
| 266 | + Center( | ||
| 267 | + child: Lottie.asset( | ||
| 268 | + 'assets/lottie/loading.json', | ||
| 269 | + width: 130, | ||
| 270 | + height: 90, | ||
| 271 | + repeat: true, | ||
| 272 | + animate: true, | ||
| 273 | + ), | ||
| 274 | + ) | ||
| 275 | + else if (stressPoints.isEmpty) | ||
| 259 | _EmptyChart(text: context.l10n.noPressureDataAvailableAtThisTime) | 276 | _EmptyChart(text: context.l10n.noPressureDataAvailableAtThisTime) |
| 260 | else | 277 | else |
| 261 | - _StressBarChart( | ||
| 262 | - sleepList: sleepList, | ||
| 263 | - stressPoints: stressPoints, | ||
| 264 | - selectedDate: selectedDate, | ||
| 265 | - isVip: isVip, | ||
| 266 | - onTapPurchase: onTapPurchase, | ||
| 267 | - maxSeconds: _maxChartSeconds(), | ||
| 268 | - getColorByState: _getColorByState, | ||
| 269 | - getTooltip: _getTooltip, | ||
| 270 | - timeLabel: _timeLabel, | ||
| 271 | - ), | 278 | + SizedBox(), |
| 272 | if (isVip != true) ...[ | 279 | if (isVip != true) ...[ |
| 273 | GestureDetector( | 280 | GestureDetector( |
| 274 | onTap: onTapPurchase, | 281 | onTap: onTapPurchase, |
| @@ -26,6 +26,9 @@ class TodayHrvNumberCard extends StatelessWidget { | @@ -26,6 +26,9 @@ class TodayHrvNumberCard extends StatelessWidget { | ||
| 26 | var otherHrv = (sameDay | 26 | var otherHrv = (sameDay |
| 27 | ? context.l10n.todaySAverageHrv | 27 | ? context.l10n.todaySAverageHrv |
| 28 | : context.l10n.averageHrvForTheDay); | 28 | : context.l10n.averageHrvForTheDay); |
| 29 | + | ||
| 30 | + var hrvAvg = controller.v2HealthData.value?.hrvAvg ?? 0; | ||
| 31 | + | ||
| 29 | return Row( | 32 | return Row( |
| 30 | children: [ | 33 | children: [ |
| 31 | // HRV | 34 | // HRV |
| @@ -34,7 +37,7 @@ class TodayHrvNumberCard extends StatelessWidget { | @@ -34,7 +37,7 @@ class TodayHrvNumberCard extends StatelessWidget { | ||
| 34 | label: controller.isFriend | 37 | label: controller.isFriend |
| 35 | ? context.l10n.reportOtherPossessiveTitle(otherHrv) | 38 | ? context.l10n.reportOtherPossessiveTitle(otherHrv) |
| 36 | : otherHrv, | 39 | : otherHrv, |
| 37 | - value: controller.v2HealthData.value?.hrvAvg?.toString() ?? '-', | 40 | + value: hrvAvg > 0 ? hrvAvg.toString() : '-', |
| 38 | unit: 'ms', | 41 | unit: 'ms', |
| 39 | ), | 42 | ), |
| 40 | ), | 43 | ), |
| @@ -300,14 +300,15 @@ class TodayActivityCard extends StatelessWidget { | @@ -300,14 +300,15 @@ class TodayActivityCard extends StatelessWidget { | ||
| 300 | ), | 300 | ), |
| 301 | children: [ | 301 | children: [ |
| 302 | TextSpan(text: activityStr), | 302 | TextSpan(text: activityStr), |
| 303 | - TextSpan( | ||
| 304 | - text: ' ${l10n.reportUnitKcal}', | ||
| 305 | - style: TextStyle( | ||
| 306 | - color: context.colors.textSecondary, | ||
| 307 | - fontSize: 12, | ||
| 308 | - fontWeight: FontWeight.w400, | 303 | + if (activityStr != '-') |
| 304 | + TextSpan( | ||
| 305 | + text: ' ${l10n.reportUnitKcal}', | ||
| 306 | + style: TextStyle( | ||
| 307 | + color: context.colors.textSecondary, | ||
| 308 | + fontSize: 12, | ||
| 309 | + fontWeight: FontWeight.w400, | ||
| 310 | + ), | ||
| 309 | ), | 311 | ), |
| 310 | - ), | ||
| 311 | ], | 312 | ], |
| 312 | ), | 313 | ), |
| 313 | ), | 314 | ), |
| @@ -39,118 +39,128 @@ class MembershipOfferView extends GetView<MembershipOfferController> { | @@ -39,118 +39,128 @@ class MembershipOfferView extends GetView<MembershipOfferController> { | ||
| 39 | controller.unlock(); | 39 | controller.unlock(); |
| 40 | }, | 40 | }, |
| 41 | ), | 41 | ), |
| 42 | - child: SingleChildScrollView( | ||
| 43 | - physics: const ClampingScrollPhysics(), | ||
| 44 | - padding: EdgeInsets.fromLTRB( | ||
| 45 | - 0, | ||
| 46 | - MediaQuery.paddingOf(context).top + 4, | ||
| 47 | - 0, | ||
| 48 | - 0, | ||
| 49 | - ), | ||
| 50 | - child: Stack( | ||
| 51 | - children: [ | ||
| 52 | - IgnorePointer( | ||
| 53 | - child: Lottie.asset('assets/lottie/scatter_flowers.json'), | ||
| 54 | - ), | ||
| 55 | - Column( | ||
| 56 | - mainAxisSize: MainAxisSize.max, | ||
| 57 | - children: [ | ||
| 58 | - Padding( | ||
| 59 | - padding: const EdgeInsets.symmetric(horizontal: 16), | ||
| 60 | - child: Column( | ||
| 61 | - children: [ | ||
| 62 | - SizedBox(height: 48), | ||
| 63 | - OnboardingTitleText(l10n.onboardingMemberTitle), | ||
| 64 | - const SizedBox(height: 12), | ||
| 65 | - OnboardingBodyText( | ||
| 66 | - l10n.onboardingMemberBody, | ||
| 67 | - fontSize: 14, | ||
| 68 | - ), | ||
| 69 | - ], | 42 | + child: Column( |
| 43 | + children: [ | ||
| 44 | + Expanded( | ||
| 45 | + child: SingleChildScrollView( | ||
| 46 | + physics: const ClampingScrollPhysics(), | ||
| 47 | + padding: EdgeInsets.fromLTRB( | ||
| 48 | + 0, | ||
| 49 | + MediaQuery.paddingOf(context).top + 4, | ||
| 50 | + 0, | ||
| 51 | + 0, | ||
| 52 | + ), | ||
| 53 | + child: Stack( | ||
| 54 | + children: [ | ||
| 55 | + IgnorePointer( | ||
| 56 | + child: Lottie.asset( | ||
| 57 | + 'assets/lottie/scatter_flowers.json', | ||
| 58 | + width: Get.width), | ||
| 70 | ), | 59 | ), |
| 71 | - ), | ||
| 72 | - const SizedBox(height: 12), | ||
| 73 | - Image.asset( | ||
| 74 | - 'assets/images/user_onboarding/ic_membership_box.png'), | ||
| 75 | - const SizedBox(height: 4), | ||
| 76 | - Obx(() { | ||
| 77 | - final yearInfo = controller.yearlyProductAppleInfo.value; | ||
| 78 | - final yearProduct = controller.yearlyProduct.value; | ||
| 79 | - | ||
| 80 | - return yearInfo == null || yearProduct == null | ||
| 81 | - ? const SizedBox(child: CircularProgressIndicator()) | ||
| 82 | - : Column( | 60 | + Column( |
| 61 | + children: [ | ||
| 62 | + Padding( | ||
| 63 | + padding: const EdgeInsets.symmetric(horizontal: 16), | ||
| 64 | + child: Column( | ||
| 83 | children: [ | 65 | children: [ |
| 84 | - Text( | ||
| 85 | - '原价${_originDisplayPrice(yearInfo)}/年', | ||
| 86 | - textAlign: TextAlign.center, | ||
| 87 | - style: const TextStyle( | ||
| 88 | - color: Color(0xFFB0B0B6), | ||
| 89 | - fontSize: 16, | ||
| 90 | - decoration: TextDecoration.lineThrough, | ||
| 91 | - decorationColor: Color(0xFFB0B0B6), | ||
| 92 | - letterSpacing: 0, | ||
| 93 | - ), | ||
| 94 | - ), | ||
| 95 | - const SizedBox(height: 6), | ||
| 96 | - Text( | ||
| 97 | - _displayPrice(yearInfo), | ||
| 98 | - textAlign: TextAlign.center, | ||
| 99 | - style: TextStyle( | ||
| 100 | - color: context.colors.primary, | ||
| 101 | - fontSize: 28, | ||
| 102 | - fontWeight: FontWeight.w600, | ||
| 103 | - letterSpacing: 0, | ||
| 104 | - ), | ||
| 105 | - ), | ||
| 106 | - const SizedBox(height: 6), | ||
| 107 | - Text( | ||
| 108 | - _planSubtitle(yearProduct, yearInfo), | ||
| 109 | - textAlign: TextAlign.center, | ||
| 110 | - style: const TextStyle( | ||
| 111 | - color: Color(0xFFB0B0B6), | ||
| 112 | - fontSize: 12, | ||
| 113 | - letterSpacing: 0, | ||
| 114 | - ), | 66 | + SizedBox(height: 48), |
| 67 | + OnboardingTitleText(l10n.onboardingMemberTitle), | ||
| 68 | + const SizedBox(height: 12), | ||
| 69 | + OnboardingBodyText( | ||
| 70 | + l10n.onboardingMemberBody, | ||
| 71 | + fontSize: 14, | ||
| 115 | ), | 72 | ), |
| 116 | ], | 73 | ], |
| 117 | - ); | ||
| 118 | - }), | ||
| 119 | - const SizedBox(height: 58), | ||
| 120 | - GestureDetector( | ||
| 121 | - behavior: HitTestBehavior.opaque, | ||
| 122 | - onTap: () { | ||
| 123 | - var arguments = {}; | ||
| 124 | - if (controller.isFromOnboard) { | ||
| 125 | - arguments['from'] = 'onboarding'; | ||
| 126 | - } else { | ||
| 127 | - arguments['channel_type'] = | ||
| 128 | - Get.arguments?['channel_type'] ?? ''; | ||
| 129 | - } | ||
| 130 | - Get.offNamed(Routes.PURCHASE, arguments: arguments); | ||
| 131 | - }, | ||
| 132 | - child: Padding( | ||
| 133 | - padding: const EdgeInsets.symmetric( | ||
| 134 | - vertical: 12, | ||
| 135 | - horizontal: 24, | ||
| 136 | - ), | ||
| 137 | - child: Text( | ||
| 138 | - l10n.onboardingMemberAllOptions, | ||
| 139 | - textAlign: TextAlign.center, | ||
| 140 | - style: TextStyle( | ||
| 141 | - color: context.colors.textPrimary, | ||
| 142 | - fontSize: 12, | ||
| 143 | - decoration: TextDecoration.underline, | ||
| 144 | - decorationColor: context.colors.textPrimary, | ||
| 145 | - letterSpacing: 0, | 74 | + ), |
| 146 | ), | 75 | ), |
| 147 | - ), | 76 | + const SizedBox(height: 12), |
| 77 | + Image.asset( | ||
| 78 | + 'assets/images/user_onboarding/ic_membership_box.png'), | ||
| 79 | + const SizedBox(height: 4), | ||
| 80 | + Obx(() { | ||
| 81 | + final yearInfo = | ||
| 82 | + controller.yearlyProductAppleInfo.value; | ||
| 83 | + final yearProduct = controller.yearlyProduct.value; | ||
| 84 | + | ||
| 85 | + return yearInfo == null || yearProduct == null | ||
| 86 | + ? const SizedBox( | ||
| 87 | + child: CircularProgressIndicator()) | ||
| 88 | + : Column( | ||
| 89 | + children: [ | ||
| 90 | + Text( | ||
| 91 | + '原价${_originDisplayPrice(yearInfo)}/年', | ||
| 92 | + textAlign: TextAlign.center, | ||
| 93 | + style: const TextStyle( | ||
| 94 | + color: Color(0xFF78787D), | ||
| 95 | + fontSize: 16, | ||
| 96 | + decoration: | ||
| 97 | + TextDecoration.lineThrough, | ||
| 98 | + decorationColor: Color(0xFF78787D), | ||
| 99 | + letterSpacing: 0, | ||
| 100 | + ), | ||
| 101 | + ), | ||
| 102 | + const SizedBox(height: 6), | ||
| 103 | + Text( | ||
| 104 | + _displayPrice(yearInfo), | ||
| 105 | + textAlign: TextAlign.center, | ||
| 106 | + style: TextStyle( | ||
| 107 | + color: context.colors.primary, | ||
| 108 | + fontSize: 28, | ||
| 109 | + fontWeight: FontWeight.w600, | ||
| 110 | + letterSpacing: 0, | ||
| 111 | + ), | ||
| 112 | + ), | ||
| 113 | + const SizedBox(height: 6), | ||
| 114 | + Text( | ||
| 115 | + _planSubtitle(yearProduct, yearInfo), | ||
| 116 | + textAlign: TextAlign.center, | ||
| 117 | + style: const TextStyle( | ||
| 118 | + color: Color(0xFF78787D), | ||
| 119 | + fontSize: 12, | ||
| 120 | + letterSpacing: 0, | ||
| 121 | + ), | ||
| 122 | + ), | ||
| 123 | + ], | ||
| 124 | + ); | ||
| 125 | + }), | ||
| 126 | + ], | ||
| 148 | ), | 127 | ), |
| 128 | + ], | ||
| 129 | + ), | ||
| 130 | + ), | ||
| 131 | + ), | ||
| 132 | + GestureDetector( | ||
| 133 | + behavior: HitTestBehavior.opaque, | ||
| 134 | + onTap: () { | ||
| 135 | + var arguments = {}; | ||
| 136 | + if (controller.isFromOnboard) { | ||
| 137 | + arguments['from'] = 'onboarding'; | ||
| 138 | + } else { | ||
| 139 | + arguments['channel_type'] = | ||
| 140 | + Get.arguments?['channel_type'] ?? ''; | ||
| 141 | + } | ||
| 142 | + Get.offNamed(Routes.PURCHASE, arguments: arguments); | ||
| 143 | + }, | ||
| 144 | + child: Padding( | ||
| 145 | + padding: const EdgeInsets.symmetric( | ||
| 146 | + vertical: 12, | ||
| 147 | + horizontal: 24, | ||
| 148 | + ), | ||
| 149 | + child: Text( | ||
| 150 | + l10n.onboardingMemberAllOptions, | ||
| 151 | + textAlign: TextAlign.center, | ||
| 152 | + style: TextStyle( | ||
| 153 | + color: context.colors.textPrimary, | ||
| 154 | + fontSize: 12, | ||
| 155 | + decoration: TextDecoration.underline, | ||
| 156 | + decorationColor: context.colors.textPrimary, | ||
| 157 | + letterSpacing: 0, | ||
| 149 | ), | 158 | ), |
| 150 | - ], | 159 | + ), |
| 151 | ), | 160 | ), |
| 152 | - ], | ||
| 153 | - ), | 161 | + ), |
| 162 | + const SizedBox(height: 140), | ||
| 163 | + ], | ||
| 154 | ), | 164 | ), |
| 155 | )); | 165 | )); |
| 156 | } | 166 | } |
| @@ -269,6 +269,7 @@ class _FaqLink extends StatelessWidget { | @@ -269,6 +269,7 @@ class _FaqLink extends StatelessWidget { | ||
| 269 | fontSize: 12, | 269 | fontSize: 12, |
| 270 | height: 17 / 12, | 270 | height: 17 / 12, |
| 271 | decoration: TextDecoration.underline, | 271 | decoration: TextDecoration.underline, |
| 272 | + decorationColor: context.colors.textSecondary, | ||
| 272 | ), | 273 | ), |
| 273 | ), | 274 | ), |
| 274 | ), | 275 | ), |
| @@ -139,8 +139,9 @@ class _IntroPage extends StatelessWidget { | @@ -139,8 +139,9 @@ class _IntroPage extends StatelessWidget { | ||
| 139 | const SizedBox(height: 12), | 139 | const SizedBox(height: 12), |
| 140 | Image.asset( | 140 | Image.asset( |
| 141 | 'assets/images/user_onboarding/bg_onboarding_watch.png', | 141 | 'assets/images/user_onboarding/bg_onboarding_watch.png', |
| 142 | - height: 384, | ||
| 143 | - fit: BoxFit.fitHeight, | 142 | + height: Get.width / 375 * 384, |
| 143 | + width: Get.width, | ||
| 144 | + fit: BoxFit.cover, | ||
| 144 | ), | 145 | ), |
| 145 | const SizedBox(height: 12), | 146 | const SizedBox(height: 12), |
| 146 | OnboardingEmRichText( | 147 | OnboardingEmRichText( |
| @@ -284,13 +285,15 @@ class _HrvIntroPageState extends State<_HrvIntroPage> | @@ -284,13 +285,15 @@ class _HrvIntroPageState extends State<_HrvIntroPage> | ||
| 284 | _isRepeat | 285 | _isRepeat |
| 285 | ? Lottie.asset( | 286 | ? Lottie.asset( |
| 286 | 'assets/lottie/onboarding_hrv_repeat.json', | 287 | 'assets/lottie/onboarding_hrv_repeat.json', |
| 287 | - height: 210, | 288 | + height: Get.width / 375 * 210, |
| 289 | + width: Get.width, | ||
| 288 | fit: BoxFit.cover, | 290 | fit: BoxFit.cover, |
| 289 | repeat: true, | 291 | repeat: true, |
| 290 | ) | 292 | ) |
| 291 | : Lottie.asset( | 293 | : Lottie.asset( |
| 292 | 'assets/lottie/onboarding_hrv_once.json', | 294 | 'assets/lottie/onboarding_hrv_once.json', |
| 293 | - height: 210, | 295 | + height: Get.width / 375 * 210, |
| 296 | + width: Get.width, | ||
| 294 | fit: BoxFit.cover, | 297 | fit: BoxFit.cover, |
| 295 | controller: _controller, | 298 | controller: _controller, |
| 296 | onLoaded: (composition) { | 299 | onLoaded: (composition) { |
| @@ -301,7 +304,7 @@ class _HrvIntroPageState extends State<_HrvIntroPage> | @@ -301,7 +304,7 @@ class _HrvIntroPageState extends State<_HrvIntroPage> | ||
| 301 | ), | 304 | ), |
| 302 | const SizedBox(height: 31), | 305 | const SizedBox(height: 31), |
| 303 | Padding( | 306 | Padding( |
| 304 | - padding: const EdgeInsets.symmetric(horizontal: 16), | 307 | + padding: const EdgeInsets.symmetric(horizontal: 48), |
| 305 | child: Text( | 308 | child: Text( |
| 306 | l10n.onboardingHrvDescription, | 309 | l10n.onboardingHrvDescription, |
| 307 | textAlign: TextAlign.center, | 310 | textAlign: TextAlign.center, |
| @@ -457,62 +460,87 @@ class _HrvResearchPageState extends State<_HrvResearchPage> { | @@ -457,62 +460,87 @@ class _HrvResearchPageState extends State<_HrvResearchPage> { | ||
| 457 | ), | 460 | ), |
| 458 | ]; | 461 | ]; |
| 459 | 462 | ||
| 460 | - return OnboardingPageScrollBody( | ||
| 461 | - horizontalPadding: 16, | ||
| 462 | - child: Column( | ||
| 463 | - children: [ | ||
| 464 | - Padding( | ||
| 465 | - padding: const EdgeInsets.symmetric(horizontal: 16), | ||
| 466 | - child: OnboardingTitleText( | ||
| 467 | - l10n.onboardingResearchTitle, | ||
| 468 | - maxWidth: 320, | 463 | + final topPadding = MediaQuery.paddingOf(context).top + 4; |
| 464 | + | ||
| 465 | + return LayoutBuilder( | ||
| 466 | + builder: (context, constraints) { | ||
| 467 | + return SingleChildScrollView( | ||
| 468 | + physics: const ClampingScrollPhysics(), | ||
| 469 | + padding: EdgeInsets.fromLTRB(16, topPadding, 16, 140), | ||
| 470 | + child: ConstrainedBox( | ||
| 471 | + constraints: BoxConstraints( | ||
| 472 | + minHeight: constraints.maxHeight - topPadding - 140, | ||
| 469 | ), | 473 | ), |
| 470 | - ), | ||
| 471 | - const SizedBox(height: 24), | ||
| 472 | - // ── Pager 区域 ── | ||
| 473 | - NotificationListener<ScrollNotification>( | ||
| 474 | - onNotification: (notification) { | ||
| 475 | - if (notification is ScrollStartNotification && | ||
| 476 | - notification.dragDetails != null) { | ||
| 477 | - // 用户开始拖拽,暂停自动播放 | ||
| 478 | - if (!_userScrolling) { | ||
| 479 | - setState(() => _userScrolling = true); | ||
| 480 | - _stopAutoScroll(); | ||
| 481 | - } | ||
| 482 | - } else if (notification is ScrollEndNotification) { | ||
| 483 | - // 手势结束,恢复自动播放 | ||
| 484 | - if (_userScrolling) { | ||
| 485 | - setState(() => _userScrolling = false); | ||
| 486 | - _startAutoScroll(); | ||
| 487 | - } | ||
| 488 | - } | ||
| 489 | - return false; | ||
| 490 | - }, | ||
| 491 | - child: SizedBox( | ||
| 492 | - height: 361, | ||
| 493 | - child: PageView.builder( | ||
| 494 | - controller: _pageController, | ||
| 495 | - itemCount: _virtualCount, | ||
| 496 | - onPageChanged: (_) => setState(() {}), | ||
| 497 | - itemBuilder: (context, virtualIndex) { | ||
| 498 | - final realIndex = virtualIndex % _realCount; | ||
| 499 | - return pages[realIndex]; | ||
| 500 | - }, | 474 | + child: IntrinsicHeight( |
| 475 | + child: Column( | ||
| 476 | + children: [ | ||
| 477 | + Padding( | ||
| 478 | + padding: const EdgeInsets.symmetric(horizontal: 16), | ||
| 479 | + child: OnboardingTitleText( | ||
| 480 | + l10n.onboardingResearchTitle, | ||
| 481 | + maxWidth: 320, | ||
| 482 | + ), | ||
| 483 | + ), | ||
| 484 | + SizedBox( | ||
| 485 | + height: 16, | ||
| 486 | + ), | ||
| 487 | + Expanded( | ||
| 488 | + child: Center( | ||
| 489 | + child: Column( | ||
| 490 | + mainAxisSize: MainAxisSize.min, | ||
| 491 | + children: [ | ||
| 492 | + NotificationListener<ScrollNotification>( | ||
| 493 | + onNotification: (notification) { | ||
| 494 | + if (notification is ScrollStartNotification && | ||
| 495 | + notification.dragDetails != null) { | ||
| 496 | + // 用户开始拖拽,暂停自动播放 | ||
| 497 | + if (!_userScrolling) { | ||
| 498 | + setState(() => _userScrolling = true); | ||
| 499 | + _stopAutoScroll(); | ||
| 500 | + } | ||
| 501 | + } else if (notification | ||
| 502 | + is ScrollEndNotification) { | ||
| 503 | + // 手势结束,恢复自动播放 | ||
| 504 | + if (_userScrolling) { | ||
| 505 | + setState(() => _userScrolling = false); | ||
| 506 | + _startAutoScroll(); | ||
| 507 | + } | ||
| 508 | + } | ||
| 509 | + return false; | ||
| 510 | + }, | ||
| 511 | + child: SizedBox( | ||
| 512 | + height: 361, | ||
| 513 | + child: PageView.builder( | ||
| 514 | + controller: _pageController, | ||
| 515 | + itemCount: _virtualCount, | ||
| 516 | + onPageChanged: (_) => setState(() {}), | ||
| 517 | + itemBuilder: (context, virtualIndex) { | ||
| 518 | + final realIndex = virtualIndex % _realCount; | ||
| 519 | + return pages[realIndex]; | ||
| 520 | + }, | ||
| 521 | + ), | ||
| 522 | + ), | ||
| 523 | + ), | ||
| 524 | + const SizedBox(height: 20), | ||
| 525 | + AnimatedBuilder( | ||
| 526 | + animation: _pageController, | ||
| 527 | + builder: (context, _) { | ||
| 528 | + return OnboardingPageIndicators( | ||
| 529 | + activeIndex: _activeRealIndex, | ||
| 530 | + count: _realCount, | ||
| 531 | + ); | ||
| 532 | + }, | ||
| 533 | + ), | ||
| 534 | + ], | ||
| 535 | + ), | ||
| 536 | + ), | ||
| 537 | + ), | ||
| 538 | + ], | ||
| 501 | ), | 539 | ), |
| 502 | ), | 540 | ), |
| 503 | ), | 541 | ), |
| 504 | - const SizedBox(height: 20), | ||
| 505 | - AnimatedBuilder( | ||
| 506 | - animation: _pageController, | ||
| 507 | - builder: (context, _) { | ||
| 508 | - return OnboardingPageIndicators( | ||
| 509 | - activeIndex: _activeRealIndex, | ||
| 510 | - count: _realCount, | ||
| 511 | - ); | ||
| 512 | - }, | ||
| 513 | - ), | ||
| 514 | - ], | ||
| 515 | - ), | 542 | + ); |
| 543 | + }, | ||
| 516 | ); | 544 | ); |
| 517 | } | 545 | } |
| 518 | } | 546 | } |
| @@ -564,8 +592,9 @@ class HealthPermissionPage extends StatelessWidget { | @@ -564,8 +592,9 @@ class HealthPermissionPage extends StatelessWidget { | ||
| 564 | const SizedBox(height: 28), | 592 | const SizedBox(height: 28), |
| 565 | Image.asset( | 593 | Image.asset( |
| 566 | 'assets/images/permission/apple_open_health_kit_cn.png', | 594 | 'assets/images/permission/apple_open_health_kit_cn.png', |
| 567 | - height: 295, | ||
| 568 | - fit: BoxFit.fitHeight, | 595 | + height: (Get.width - 80) / 295 * 360, |
| 596 | + width: Get.width - 80, | ||
| 597 | + fit: BoxFit.cover, | ||
| 569 | ), | 598 | ), |
| 570 | const SizedBox(height: 16), | 599 | const SizedBox(height: 16), |
| 571 | Padding( | 600 | Padding( |
| @@ -20,16 +20,30 @@ class ResearchCard extends StatelessWidget { | @@ -20,16 +20,30 @@ class ResearchCard extends StatelessWidget { | ||
| 20 | Widget build(BuildContext context) { | 20 | Widget build(BuildContext context) { |
| 21 | return Container( | 21 | return Container( |
| 22 | height: 320, | 22 | height: 320, |
| 23 | - width: 171, | 23 | + width: 172, |
| 24 | decoration: BoxDecoration( | 24 | decoration: BoxDecoration( |
| 25 | color: backgroundColor, | 25 | color: backgroundColor, |
| 26 | borderRadius: BorderRadius.circular(32), | 26 | borderRadius: BorderRadius.circular(32), |
| 27 | - image: DecorationImage(image: AssetImage(image), fit: BoxFit.fill), | ||
| 28 | ), | 27 | ), |
| 29 | padding: const EdgeInsets.fromLTRB(12, 24, 12, 28), | 28 | padding: const EdgeInsets.fromLTRB(12, 24, 12, 28), |
| 30 | child: Stack( | 29 | child: Stack( |
| 31 | children: [ | 30 | children: [ |
| 32 | Positioned( | 31 | Positioned( |
| 32 | + bottom: 0, | ||
| 33 | + left: 0, | ||
| 34 | + right: 0, | ||
| 35 | + child: Container( | ||
| 36 | + width: 172, | ||
| 37 | + height: 212, | ||
| 38 | + decoration: BoxDecoration( | ||
| 39 | + color: backgroundColor, | ||
| 40 | + borderRadius: BorderRadius.circular(32), | ||
| 41 | + image: DecorationImage( | ||
| 42 | + image: AssetImage(image), fit: BoxFit.cover), | ||
| 43 | + ), | ||
| 44 | + ), | ||
| 45 | + ), | ||
| 46 | + Positioned( | ||
| 33 | top: 0, | 47 | top: 0, |
| 34 | left: 0, | 48 | left: 0, |
| 35 | right: 0, | 49 | right: 0, |
-
Please register or login to post a comment