Showing
4 changed files
with
150 additions
and
58 deletions
| @@ -191,7 +191,6 @@ class FriendsTab extends GetView<FriendsController> { | @@ -191,7 +191,6 @@ class FriendsTab extends GetView<FriendsController> { | ||
| 191 | ? () => _showFriendsLimitReached(context) | 191 | ? () => _showFriendsLimitReached(context) |
| 192 | : _handleAddFriend, | 192 | : _handleAddFriend, |
| 193 | ), | 193 | ), |
| 194 | - if (isFull) const _FullFriendsTip(), | ||
| 195 | ], | 194 | ], |
| 196 | ), | 195 | ), |
| 197 | ), | 196 | ), |
| @@ -408,34 +407,12 @@ class _EmptyFriendsView extends StatelessWidget { | @@ -408,34 +407,12 @@ class _EmptyFriendsView extends StatelessWidget { | ||
| 408 | enabled: enabled, | 407 | enabled: enabled, |
| 409 | onTap: onTap, | 408 | onTap: onTap, |
| 410 | ), | 409 | ), |
| 411 | - if (!enabled) const _FullFriendsTip(), | ||
| 412 | ], | 410 | ], |
| 413 | ), | 411 | ), |
| 414 | ); | 412 | ); |
| 415 | } | 413 | } |
| 416 | } | 414 | } |
| 417 | 415 | ||
| 418 | -class _FullFriendsTip extends StatelessWidget { | ||
| 419 | - const _FullFriendsTip(); | ||
| 420 | - | ||
| 421 | - @override | ||
| 422 | - Widget build(BuildContext context) { | ||
| 423 | - return Padding( | ||
| 424 | - padding: const EdgeInsets.only(top: 10), | ||
| 425 | - child: Center( | ||
| 426 | - child: Text( | ||
| 427 | - context.l10n.friendsLimitReached, | ||
| 428 | - style: const TextStyle( | ||
| 429 | - color: Color(0xFFB0B0B6), | ||
| 430 | - fontSize: 12, | ||
| 431 | - fontWeight: FontWeight.w400, | ||
| 432 | - ), | ||
| 433 | - ), | ||
| 434 | - ), | ||
| 435 | - ); | ||
| 436 | - } | ||
| 437 | -} | ||
| 438 | - | ||
| 439 | class _AddFriendButton extends StatelessWidget { | 416 | class _AddFriendButton extends StatelessWidget { |
| 440 | const _AddFriendButton({ | 417 | const _AddFriendButton({ |
| 441 | required this.enabled, | 418 | required this.enabled, |
| @@ -455,39 +432,53 @@ class _AddFriendButton extends StatelessWidget { | @@ -455,39 +432,53 @@ class _AddFriendButton extends StatelessWidget { | ||
| 455 | child: GestureDetector( | 432 | child: GestureDetector( |
| 456 | onTap: onTap, | 433 | onTap: onTap, |
| 457 | behavior: HitTestBehavior.opaque, | 434 | behavior: HitTestBehavior.opaque, |
| 458 | - child: Opacity( | ||
| 459 | - opacity: enabled ? 1 : 0.4, | ||
| 460 | - child: Container( | 435 | + child: SizedBox( |
| 461 | width: 240, | 436 | width: 240, |
| 462 | height: 48, | 437 | height: 48, |
| 463 | - decoration: BoxDecoration( | ||
| 464 | - color: | ||
| 465 | - enabled ? const Color(0xFF845EEE) : const Color(0xFFD0D0D2), | ||
| 466 | - borderRadius: BorderRadius.circular(24), | ||
| 467 | - ), | ||
| 468 | - child: Row( | ||
| 469 | - mainAxisAlignment: MainAxisAlignment.center, | 438 | + child: Stack( |
| 470 | children: [ | 439 | children: [ |
| 471 | - Image.asset( | ||
| 472 | - 'assets/images/common/ic_add.png', | ||
| 473 | - width: 18, | ||
| 474 | - height: 18, | ||
| 475 | - color: Colors.white, | ||
| 476 | - ), | ||
| 477 | - const SizedBox(width: 6), | ||
| 478 | - Text( | ||
| 479 | - _buttonText(context), | ||
| 480 | - style: const TextStyle( | 440 | + Container( |
| 441 | + width: double.infinity, | ||
| 442 | + height: double.infinity, | ||
| 443 | + decoration: BoxDecoration( | ||
| 481 | color: Colors.white, | 444 | color: Colors.white, |
| 482 | - fontSize: 16, | ||
| 483 | - fontWeight: FontWeight.w600, | ||
| 484 | - height: 1.4, | 445 | + borderRadius: BorderRadius.circular(24), |
| 485 | ), | 446 | ), |
| 486 | ), | 447 | ), |
| 448 | + Opacity( | ||
| 449 | + opacity: enabled ? 1 : 0.4, | ||
| 450 | + child: Container( | ||
| 451 | + width: 240, | ||
| 452 | + height: 48, | ||
| 453 | + decoration: BoxDecoration( | ||
| 454 | + color: const Color(0xFF845EEE), | ||
| 455 | + borderRadius: BorderRadius.circular(24), | ||
| 456 | + ), | ||
| 457 | + child: Row( | ||
| 458 | + mainAxisAlignment: MainAxisAlignment.center, | ||
| 459 | + children: [ | ||
| 460 | + Image.asset( | ||
| 461 | + 'assets/images/common/ic_add.png', | ||
| 462 | + width: 18, | ||
| 463 | + height: 18, | ||
| 464 | + color: Colors.white, | ||
| 465 | + ), | ||
| 466 | + const SizedBox(width: 6), | ||
| 467 | + Text( | ||
| 468 | + _buttonText(context), | ||
| 469 | + style: const TextStyle( | ||
| 470 | + color: Colors.white, | ||
| 471 | + fontSize: 16, | ||
| 472 | + fontWeight: FontWeight.w600, | ||
| 473 | + height: 1.4, | ||
| 474 | + ), | ||
| 475 | + ), | ||
| 476 | + ], | ||
| 477 | + ), | ||
| 478 | + ), | ||
| 479 | + ) | ||
| 487 | ], | 480 | ], |
| 488 | - ), | ||
| 489 | - ), | ||
| 490 | - ), | 481 | + )), |
| 491 | ), | 482 | ), |
| 492 | ); | 483 | ); |
| 493 | } | 484 | } |
| @@ -158,15 +158,30 @@ class _HealthTrendContentState extends State<HealthTrendContent> | @@ -158,15 +158,30 @@ class _HealthTrendContentState extends State<HealthTrendContent> | ||
| 158 | class _HealthTrendLoadingIndicator extends StatelessWidget { | 158 | class _HealthTrendLoadingIndicator extends StatelessWidget { |
| 159 | const _HealthTrendLoadingIndicator(); | 159 | const _HealthTrendLoadingIndicator(); |
| 160 | 160 | ||
| 161 | + static const _width = 130.0; | ||
| 162 | + static const _height = 90.0; | ||
| 163 | + static const _headerCenterOffset = Offset(0, -46); | ||
| 164 | + static const _visualCenterOffset = Offset(0, -8); | ||
| 165 | + | ||
| 161 | @override | 166 | @override |
| 162 | Widget build(BuildContext context) { | 167 | Widget build(BuildContext context) { |
| 163 | return Center( | 168 | return Center( |
| 164 | - child: Lottie.asset( | ||
| 165 | - 'assets/lottie/loading.json', | ||
| 166 | - width: 130, | ||
| 167 | - height: 90, | ||
| 168 | - repeat: true, | ||
| 169 | - animate: true, | 169 | + child: Transform.translate( |
| 170 | + offset: _headerCenterOffset, | ||
| 171 | + child: SizedBox( | ||
| 172 | + width: _width, | ||
| 173 | + height: _height, | ||
| 174 | + child: Transform.translate( | ||
| 175 | + offset: _visualCenterOffset, | ||
| 176 | + child: Lottie.asset( | ||
| 177 | + 'assets/lottie/loading.json', | ||
| 178 | + width: _width, | ||
| 179 | + height: _height, | ||
| 180 | + repeat: true, | ||
| 181 | + animate: true, | ||
| 182 | + ), | ||
| 183 | + ), | ||
| 184 | + ), | ||
| 170 | ), | 185 | ), |
| 171 | ); | 186 | ); |
| 172 | } | 187 | } |
| @@ -211,7 +226,9 @@ class _HrvTrendSectionState extends State<_HrvTrendSection> { | @@ -211,7 +226,9 @@ class _HrvTrendSectionState extends State<_HrvTrendSection> { | ||
| 211 | _dateWorker = ever(_logic.selectedDate, (date) { | 226 | _dateWorker = ever(_logic.selectedDate, (date) { |
| 212 | _scheduleQueryNotification(); | 227 | _scheduleQueryNotification(); |
| 213 | }); | 228 | }); |
| 214 | - _logic.loadReport(); | 229 | + if (widget.isVip) { |
| 230 | + _logic.loadReport(); | ||
| 231 | + } | ||
| 215 | } | 232 | } |
| 216 | 233 | ||
| 217 | @override | 234 | @override |
| @@ -221,6 +238,10 @@ class _HrvTrendSectionState extends State<_HrvTrendSection> { | @@ -221,6 +238,10 @@ class _HrvTrendSectionState extends State<_HrvTrendSection> { | ||
| 221 | _syncExternalQuery(); | 238 | _syncExternalQuery(); |
| 222 | } else if (oldWidget.refreshToken != widget.refreshToken && | 239 | } else if (oldWidget.refreshToken != widget.refreshToken && |
| 223 | widget.isSelected) { | 240 | widget.isSelected) { |
| 241 | + if (widget.isVip) { | ||
| 242 | + _logic.loadReport(); | ||
| 243 | + } | ||
| 244 | + } else if (!oldWidget.isVip && widget.isVip) { | ||
| 224 | _logic.loadReport(); | 245 | _logic.loadReport(); |
| 225 | } | 246 | } |
| 226 | } | 247 | } |
| @@ -229,7 +250,11 @@ class _HrvTrendSectionState extends State<_HrvTrendSection> { | @@ -229,7 +250,11 @@ class _HrvTrendSectionState extends State<_HrvTrendSection> { | ||
| 229 | _syncingExternalQuery = true; | 250 | _syncingExternalQuery = true; |
| 230 | _logic.targetUserId.value = widget.query.targetUserId; | 251 | _logic.targetUserId.value = widget.query.targetUserId; |
| 231 | try { | 252 | try { |
| 232 | - await _logic.selectQuery(widget.query.period, widget.query.date); | 253 | + if (widget.isVip) { |
| 254 | + await _logic.selectQuery(widget.query.period, widget.query.date); | ||
| 255 | + } else { | ||
| 256 | + _logic.initializeQuery(widget.query.period, widget.query.date); | ||
| 257 | + } | ||
| 233 | } finally { | 258 | } finally { |
| 234 | _syncingExternalQuery = false; | 259 | _syncingExternalQuery = false; |
| 235 | } | 260 | } |
| @@ -77,6 +77,8 @@ class WeeklyHrvReport implements HrvPeriodReport { | @@ -77,6 +77,8 @@ class WeeklyHrvReport implements HrvPeriodReport { | ||
| 77 | required this.days, | 77 | required this.days, |
| 78 | this.previousRelaxedDays, | 78 | this.previousRelaxedDays, |
| 79 | this.previousStressedDays, | 79 | this.previousStressedDays, |
| 80 | + this.relaxedDaysOverride, | ||
| 81 | + this.stressedDaysOverride, | ||
| 80 | this.distributionCounts, | 82 | this.distributionCounts, |
| 81 | this.minDayOverride, | 83 | this.minDayOverride, |
| 82 | this.maxDayOverride, | 84 | this.maxDayOverride, |
| @@ -89,6 +91,8 @@ class WeeklyHrvReport implements HrvPeriodReport { | @@ -89,6 +91,8 @@ class WeeklyHrvReport implements HrvPeriodReport { | ||
| 89 | final int? previousRelaxedDays; | 91 | final int? previousRelaxedDays; |
| 90 | @override | 92 | @override |
| 91 | final int? previousStressedDays; | 93 | final int? previousStressedDays; |
| 94 | + final int? relaxedDaysOverride; | ||
| 95 | + final int? stressedDaysOverride; | ||
| 92 | final Map<HrvStressLevel, int>? distributionCounts; | 96 | final Map<HrvStressLevel, int>? distributionCounts; |
| 93 | final HrvDayReport? minDayOverride; | 97 | final HrvDayReport? minDayOverride; |
| 94 | final HrvDayReport? maxDayOverride; | 98 | final HrvDayReport? maxDayOverride; |
| @@ -114,10 +118,12 @@ class WeeklyHrvReport implements HrvPeriodReport { | @@ -114,10 +118,12 @@ class WeeklyHrvReport implements HrvPeriodReport { | ||
| 114 | 118 | ||
| 115 | @override | 119 | @override |
| 116 | int get relaxedDays => | 120 | int get relaxedDays => |
| 121 | + relaxedDaysOverride ?? | ||
| 117 | countFor(HrvStressLevel.excellent) + countFor(HrvStressLevel.normal); | 122 | countFor(HrvStressLevel.excellent) + countFor(HrvStressLevel.normal); |
| 118 | 123 | ||
| 119 | @override | 124 | @override |
| 120 | int get stressedDays => | 125 | int get stressedDays => |
| 126 | + stressedDaysOverride ?? | ||
| 121 | countFor(HrvStressLevel.attention) + countFor(HrvStressLevel.overload); | 127 | countFor(HrvStressLevel.attention) + countFor(HrvStressLevel.overload); |
| 122 | 128 | ||
| 123 | @override | 129 | @override |
| @@ -144,6 +150,71 @@ class WeeklyHrvReport implements HrvPeriodReport { | @@ -144,6 +150,71 @@ class WeeklyHrvReport implements HrvPeriodReport { | ||
| 144 | HrvDayReport(date: weekStart.add(Duration(days: i))), | 150 | HrvDayReport(date: weekStart.add(Duration(days: i))), |
| 145 | ], | 151 | ], |
| 146 | ); | 152 | ); |
| 153 | + | ||
| 154 | + factory WeeklyHrvReport.premiumPreview(DateTime weekStart) { | ||
| 155 | + final start = DateTime(weekStart.year, weekStart.month, weekStart.day); | ||
| 156 | + final minDay = HrvDayReport( | ||
| 157 | + date: start.add(const Duration(days: 6)), | ||
| 158 | + averageHrv: 15, | ||
| 159 | + averageHeartRate: 71, | ||
| 160 | + level: HrvStressLevel.overload, | ||
| 161 | + ); | ||
| 162 | + final maxDay = HrvDayReport( | ||
| 163 | + date: start.add(const Duration(days: 1)), | ||
| 164 | + averageHrv: 90, | ||
| 165 | + averageHeartRate: 84, | ||
| 166 | + level: HrvStressLevel.excellent, | ||
| 167 | + ); | ||
| 168 | + return WeeklyHrvReport( | ||
| 169 | + weekStart: start, | ||
| 170 | + previousRelaxedDays: 5, | ||
| 171 | + previousStressedDays: 2, | ||
| 172 | + relaxedDaysOverride: 4, | ||
| 173 | + stressedDaysOverride: 1, | ||
| 174 | + distributionCounts: const { | ||
| 175 | + HrvStressLevel.excellent: 4, | ||
| 176 | + HrvStressLevel.normal: 1, | ||
| 177 | + HrvStressLevel.attention: 1, | ||
| 178 | + HrvStressLevel.overload: 1, | ||
| 179 | + }, | ||
| 180 | + minDayOverride: minDay, | ||
| 181 | + maxDayOverride: maxDay, | ||
| 182 | + days: [ | ||
| 183 | + HrvDayReport( | ||
| 184 | + date: start, | ||
| 185 | + averageHrv: 62, | ||
| 186 | + averageHeartRate: 86, | ||
| 187 | + level: HrvStressLevel.normal, | ||
| 188 | + ), | ||
| 189 | + maxDay, | ||
| 190 | + HrvDayReport( | ||
| 191 | + date: start.add(const Duration(days: 2)), | ||
| 192 | + averageHrv: 68, | ||
| 193 | + averageHeartRate: 88, | ||
| 194 | + level: HrvStressLevel.excellent, | ||
| 195 | + ), | ||
| 196 | + HrvDayReport( | ||
| 197 | + date: start.add(const Duration(days: 3)), | ||
| 198 | + averageHrv: 85, | ||
| 199 | + averageHeartRate: 85, | ||
| 200 | + level: HrvStressLevel.excellent, | ||
| 201 | + ), | ||
| 202 | + HrvDayReport( | ||
| 203 | + date: start.add(const Duration(days: 4)), | ||
| 204 | + averageHrv: 86, | ||
| 205 | + averageHeartRate: 87, | ||
| 206 | + level: HrvStressLevel.excellent, | ||
| 207 | + ), | ||
| 208 | + HrvDayReport( | ||
| 209 | + date: start.add(const Duration(days: 5)), | ||
| 210 | + averageHrv: 45, | ||
| 211 | + averageHeartRate: 70, | ||
| 212 | + level: HrvStressLevel.attention, | ||
| 213 | + ), | ||
| 214 | + minDay, | ||
| 215 | + ], | ||
| 216 | + ); | ||
| 217 | + } | ||
| 147 | } | 218 | } |
| 148 | 219 | ||
| 149 | class MonthlyHrvReport implements HrvPeriodReport { | 220 | class MonthlyHrvReport implements HrvPeriodReport { |
| @@ -9,6 +9,7 @@ import '../../report_common/widgets/report_date_picker_sheet.dart'; | @@ -9,6 +9,7 @@ import '../../report_common/widgets/report_date_picker_sheet.dart'; | ||
| 9 | import '../../report_common/widgets/report_date_switcher.dart'; | 9 | import '../../report_common/widgets/report_date_switcher.dart'; |
| 10 | import '../../report_common/widgets/report_loading_indicator.dart'; | 10 | import '../../report_common/widgets/report_loading_indicator.dart'; |
| 11 | import '../controllers/hrv_report_logic.dart'; | 11 | import '../controllers/hrv_report_logic.dart'; |
| 12 | +import '../models/hrv_report_models.dart'; | ||
| 12 | import '../widgets/hrv_week_report_view.dart'; | 13 | import '../widgets/hrv_week_report_view.dart'; |
| 13 | import '../widgets/hrv_year_report_view.dart'; | 14 | import '../widgets/hrv_year_report_view.dart'; |
| 14 | 15 | ||
| @@ -86,7 +87,11 @@ class HrvReportView extends StatelessWidget { | @@ -86,7 +87,11 @@ class HrvReportView extends StatelessWidget { | ||
| 86 | HrvMonthReportView(report: logic.monthlyReport.value) | 87 | HrvMonthReportView(report: logic.monthlyReport.value) |
| 87 | else | 88 | else |
| 88 | HrvWeekReportView( | 89 | HrvWeekReportView( |
| 89 | - report: logic.weeklyReport.value, | 90 | + report: isVip |
| 91 | + ? logic.weeklyReport.value | ||
| 92 | + : WeeklyHrvReport.premiumPreview( | ||
| 93 | + logic.weekStart, | ||
| 94 | + ), | ||
| 90 | showExample: !isVip, | 95 | showExample: !isVip, |
| 91 | onSubscribe: () => onSubscribe('解锁每日压力-HRV心率'), | 96 | onSubscribe: () => onSubscribe('解锁每日压力-HRV心率'), |
| 92 | ), | 97 | ), |
-
Please register or login to post a comment