Commit cba16e330b34d7a4791140f3a60d1ab19a9b80dd

Authored by 常守达
1 parent 747d84d7

fix(today):看板问题修复

... ... @@ -202,7 +202,7 @@ class _ProfileHeader extends StatelessWidget {
),
const SizedBox(height: 1),
Text(
'ID:${user?.id ?? 0}',
'ID:${user?.uniqueCode ?? ''}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
... ...
... ... @@ -239,12 +239,7 @@ class TodayTabBody extends StatelessWidget {
controller,
));
}),
Obx(() => controller.showHrvAdBanner.value
? TodayHrvAdBanner(controller: controller)
: const SizedBox.shrink()),
Obx(() => controller.showPartnerAdBanner.value
? TodayPartnerAdBanner(controller: controller)
: const SizedBox.shrink()),
TodayAdCarousel(controller: controller),
TodayHrvNumberCard(controller: controller),
const SizedBox(height: 12),
TodayHrvChartCard(controller: controller),
... ... @@ -286,7 +281,8 @@ class TodayTabBody extends StatelessWidget {
return GestureDetector(
onTap: showHrvMeasurementBottomSheet,
child: Container(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
margin: EdgeInsets.only(top: 6),
padding: const EdgeInsets.fromLTRB(20, 20, 20, 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
... ...
... ... @@ -99,7 +99,7 @@ class DfTabBar extends StatelessWidget {
height: 24,
gaplessPlayback: true,
),
const SizedBox(height: 2),
const SizedBox(height: 0.5),
Text(
tabLabels[i],
style: TextStyle(
... ...
import 'dart:async';
import 'package:doublefeel_flutter/app/routes/app_pages.dart';
import 'package:doublefeel_flutter/core/services/thinking_data_service.dart';
import 'package:doublefeel_flutter/core/theme/app_theme.dart';
... ... @@ -8,9 +9,14 @@ import 'package:get/get.dart';
import '../../controllers/today_controller.dart';
class TodayHrvAdBanner extends StatelessWidget {
const TodayHrvAdBanner({super.key, required this.controller});
const TodayHrvAdBanner({
super.key,
required this.controller,
this.showMargin = true,
});
final TodayController controller;
final bool showMargin;
@override
Widget build(BuildContext context) {
... ... @@ -29,7 +35,7 @@ class TodayHrvAdBanner extends StatelessWidget {
subtitle: context.l10n.stayOnTopOfYourHealthFluctuations,
right: Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
decoration: const BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
),
... ... @@ -42,6 +48,7 @@ class TodayHrvAdBanner extends StatelessWidget {
),
leftImagePath: 'assets/images/today/ic_banner_watch_hrv_theme.png',
onClose: controller.dismissHrvAdBanner,
showMargin: showMargin,
),
);
});
... ... @@ -49,9 +56,14 @@ class TodayHrvAdBanner extends StatelessWidget {
}
class TodayPartnerAdBanner extends StatelessWidget {
const TodayPartnerAdBanner({super.key, required this.controller});
const TodayPartnerAdBanner({
super.key,
required this.controller,
this.showMargin = true,
});
final TodayController controller;
final bool showMargin;
@override
Widget build(BuildContext context) {
... ... @@ -92,6 +104,7 @@ class TodayPartnerAdBanner extends StatelessWidget {
),
leftImagePath: 'assets/images/today/ic_banner_add_contact.png',
onClose: controller.dismissPartnerAdBanner,
showMargin: showMargin,
),
);
});
... ... @@ -105,6 +118,7 @@ class _TodayGuideBanner extends StatelessWidget {
required this.onClose,
this.right,
required this.leftImagePath,
this.showMargin = true,
});
final String title;
... ... @@ -112,6 +126,7 @@ class _TodayGuideBanner extends StatelessWidget {
final VoidCallback onClose;
final Widget? right;
final String leftImagePath;
final bool showMargin;
@override
Widget build(BuildContext context) {
... ... @@ -119,7 +134,7 @@ class _TodayGuideBanner extends StatelessWidget {
return Container(
height: 65,
margin: const EdgeInsets.only(bottom: 12),
margin: showMargin ? const EdgeInsets.only(bottom: 12) : EdgeInsets.zero,
decoration: BoxDecoration(
color: colors.brandBackgroundLight,
borderRadius: BorderRadius.circular(16),
... ... @@ -188,3 +203,137 @@ class _TodayGuideBanner extends StatelessWidget {
);
}
}
class TodayAdCarousel extends StatelessWidget {
const TodayAdCarousel({super.key, required this.controller});
final TodayController controller;
@override
Widget build(BuildContext context) {
return Obx(() {
final showHrv = controller.showHrvAdBanner.value;
final showPartner = controller.showPartnerAdBanner.value &&
!controller.userStateService.isBound;
final activeBanners = <Widget>[];
if (showHrv) {
activeBanners.add(
TodayHrvAdBanner(
controller: controller,
showMargin: false,
),
);
}
if (showPartner) {
activeBanners.add(
TodayPartnerAdBanner(
controller: controller,
showMargin: false,
),
);
}
if (activeBanners.isEmpty) {
return const SizedBox.shrink();
}
if (activeBanners.length == 1) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: activeBanners.first,
);
}
return Container(
height: 65,
margin: const EdgeInsets.only(bottom: 12),
child: _BannerSlider(items: activeBanners),
);
});
}
}
class _BannerSlider extends StatefulWidget {
const _BannerSlider({required this.items});
final List<Widget> items;
@override
State<_BannerSlider> createState() => _BannerSliderState();
}
class _BannerSliderState extends State<_BannerSlider> {
late PageController _pageController;
Timer? _autoScrollTimer;
bool _userScrolling = false;
int get _realCount => widget.items.length;
int get _virtualCount => _realCount * 10000;
int get _initialPage => _virtualCount ~/ 2;
@override
void initState() {
super.initState();
_pageController = PageController(initialPage: _initialPage);
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
_startAutoScroll();
}
});
}
void _startAutoScroll() {
_autoScrollTimer?.cancel();
_autoScrollTimer = Timer.periodic(const Duration(seconds: 4), (_) {
if (!mounted || _userScrolling) return;
if (_pageController.hasClients) {
final next = (_pageController.page?.round() ?? _initialPage) + 1;
_pageController.animateToPage(
next,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
}
});
}
void _stopAutoScroll() {
_autoScrollTimer?.cancel();
_autoScrollTimer = null;
}
@override
void didUpdateWidget(covariant _BannerSlider oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.items.length != oldWidget.items.length) {
_stopAutoScroll();
_pageController.dispose();
_pageController = PageController(initialPage: _initialPage);
_startAutoScroll();
}
}
@override
void dispose() {
_stopAutoScroll();
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Listener(
onPointerDown: (_) => _userScrolling = true,
onPointerUp: (_) => _userScrolling = false,
onPointerCancel: (_) => _userScrolling = false,
child: PageView.builder(
controller: _pageController,
itemCount: _virtualCount,
itemBuilder: (context, index) {
final realIndex = index % _realCount;
return widget.items[realIndex];
},
),
);
}
}
... ...
... ... @@ -7,6 +7,7 @@ import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:lottie/lottie.dart';
import '../../controllers/today_controller.dart';
import 'hrv_principle_explanation_bottom_sheet.dart';
... ... @@ -47,7 +48,7 @@ class TodayHrvChartCard extends StatelessWidget {
};
return Container(
padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
padding: const EdgeInsets.fromLTRB(20, 16, 20, 16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
... ... @@ -59,16 +60,26 @@ class TodayHrvChartCard extends StatelessWidget {
const SizedBox(height: 14),
SizedBox(
height: 174,
child: hrvSpots.isEmpty
? _EmptyChart(text: context.l10n.noDataAvailableForToday)
: _HrvLineChart(
spots: hrvSpots,
stateMap: hrvStateMap,
maxSeconds: _maxChartSeconds(),
getColorByState: _getColorByState,
getTooltipChildren: _getTooltipChildren,
timeLabel: _timeLabel,
),
child: controller.isLoadingToday.value
? Center(
child: Lottie.asset(
'assets/lottie/loading.json',
width: 130,
height: 90,
repeat: true,
animate: true,
),
)
: hrvSpots.isEmpty
? _EmptyChart(text: context.l10n.noDataAvailableForToday)
: _HrvLineChart(
spots: hrvSpots,
stateMap: hrvStateMap,
maxSeconds: _maxChartSeconds(),
getColorByState: _getColorByState,
getTooltipChildren: _getTooltipChildren,
timeLabel: _timeLabel,
),
),
Padding(
padding: const EdgeInsets.only(bottom: 14, top: 16),
... ... @@ -116,13 +127,23 @@ class TodayHrvChartCard extends StatelessWidget {
),
SizedBox(
height: 190,
child: _buildStressChart(
context,
sleepList,
stressPoints,
selectedDate,
vipInfo?.isVip == true,
),
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,
),
),
],
),
... ... @@ -139,14 +160,14 @@ class TodayHrvChartCard extends StatelessWidget {
Builder(builder: (context) {
final s = (sameDay
? context.l10n.todaySHrvTrend
: context.l10n.averageHrvForTheDay);
: context.l10n.hrvTrendForThatDay);
return Text(
(controller.isFriend
? context.l10n.reportOtherPossessiveTitle(s)
: s),
style: TextStyle(
color: context.colors.textPrimary,
fontSize: 16,
fontSize: 18,
fontWeight: FontWeight.w600,
),
);
... ... @@ -363,7 +384,7 @@ class TodayHrvChartCard extends StatelessWidget {
List<TextSpan>? _getTooltip(BarChartRodData rod, int x, int? state) {
return [
TextSpan(
text: '○ ${_getStatusByState(state)}',
text: '○ ${_getStatusByState(state)} ${_formatNumber(rod.toY)}%',
style: TextStyle(
color: _getColorByState(state),
fontWeight: FontWeight.bold,
... ... @@ -377,8 +398,7 @@ class TodayHrvChartCard extends StatelessWidget {
),
),
TextSpan(
text:
'♥ ${l10n.pressure} ${_formatNumber(rod.toY)} · ${_formatHour(x.toDouble())}',
text: '♥ ${l10n.pressure} ${_formatHour(x.toDouble())}',
style: const TextStyle(
color: Color(0xFF78787D),
fontWeight: FontWeight.bold,
... ... @@ -510,13 +530,18 @@ class _HrvLineChartState extends State<_HrvLineChart> {
showTitles: true,
reservedSize: 20,
interval: 6 * 3600,
getTitlesWidget: (val, _) {
getTitlesWidget: (val, meta) {
final label = widget.timeLabel(val);
if (label == null) return const SizedBox.shrink();
return Text(
label,
style: TextStyle(
fontSize: 10, color: context.colors.textTertiary),
return SideTitleWidget(
meta: meta,
fitInside: SideTitleFitInsideData.fromTitleMeta(meta,
distanceFromEdge: 0),
child: Text(
label,
style: TextStyle(
fontSize: 10, color: context.colors.textTertiary),
),
);
},
),
... ... @@ -679,12 +704,11 @@ class _StressBarChartState extends State<_StressBarChart> {
const rightAxisWidth = 22.0;
const slotSec = 6 * 60;
final slotCount = (widget.maxSeconds / slotSec).ceil().clamp(1, 9999);
const double groupsSpace = 0.5; // 设置间隔为 1 像素
const double groupsSpace = 0; // 设置间隔为 1 像素
final barWidth = (constraints.maxWidth -
rightAxisWidth -
(groupsSpace * (slotCount - 1))) /
slotCount;
// 根据 V2SleepTimeRange 计算多段睡眠区间的 Positioned widgets
final sleepWidgets = <Widget>[];
... ... @@ -866,7 +890,7 @@ class _StressBarChartState extends State<_StressBarChart> {
sideTitles: SideTitles(
showTitles: true,
reservedSize: 22,
interval: 60,
interval: 6 * 3600,
getTitlesWidget: (val, meta) {
final label = widget.timeLabel(val);
if (label == null) {
... ... @@ -874,6 +898,9 @@ class _StressBarChartState extends State<_StressBarChart> {
}
return SideTitleWidget(
meta: meta,
fitInside: SideTitleFitInsideData.fromTitleMeta(
meta,
distanceFromEdge: 0),
child: Text(
label,
style: TextStyle(
... ... @@ -932,7 +959,9 @@ class _StressBarChartState extends State<_StressBarChart> {
bottom: 5,
),
getTooltipItem: (group, groupIndex, rod, rodIndex) {
final state = slotStateMap[group.x]?.state;
final entry = slotStateMap[group.x];
if (entry == null) return null;
final state = entry.state;
return BarTooltipItem(
'',
const TextStyle(
... ...
... ... @@ -48,8 +48,22 @@ class LoginController extends GetxController {
AppleSignInModel? appleSignInModel;
void toggleTermsChecked() {
termsChecked.value = !termsChecked.value;
Future<void> toggleTermsChecked() async {
final newValue = !termsChecked.value;
termsChecked.value = newValue;
try {
final local = Get.find<LocalStorage>();
await local.setTermsAgreed(newValue);
if (newValue && !_userStateService.sdksInitialized) {
await _userStateService.initializeSdks();
}
if (needTrackWelcomepage) {
ta.track('enter_doublefeel_welcome_page');
needTrackWelcomepage = false;
}
} catch (e) {
AppLogger.e(e.toString());
}
}
void onPhoneLoginPressed() {
... ... @@ -104,6 +118,10 @@ class LoginController extends GetxController {
@override
void onInit() {
super.onInit();
LocalStorage.open().then((local) {
termsChecked.value = local.termsAgreed;
});
phoneController.addListener(() {
phoneInput.value = phoneController.text;
});
... ... @@ -124,9 +142,15 @@ class LoginController extends GetxController {
@override
void onReady() {
super.onReady();
ta.track('enter_doublefeel_welcome_page');
if (_userStateService.sdksInitialized) {
ta.track('enter_doublefeel_welcome_page');
} else {
needTrackWelcomepage = true;
}
}
bool needTrackWelcomepage = false;
String get cleanPhone => phoneInput.value.replaceAll(' ', '');
bool get canRequestCode =>
... ...
... ... @@ -21,10 +21,14 @@ class UserStateService {
final PushService _pushService;
final ThinkingDataService _thinkingDataService;
bool sdksInitialized = false;
/// Called after the user accepts terms (cold start, not logged in yet).
Future<void> initializeSdks() async {
if (sdksInitialized) return;
await _thinkingDataService.init();
await _imService.init();
sdksInitialized = true;
}
Future<void> onLogin() async {
... ...
... ... @@ -561,5 +561,6 @@
},
"freeRedemptionOffer": "Free Redemption Offer",
"cellPhoneNumber": "Cell phone number",
"todayOnWeeklyCalendar": "today"
"todayOnWeeklyCalendar": "today",
"hrvTrendForThatDay": "HRV Trend for That Day"
}
\ No newline at end of file
... ...
... ... @@ -849,5 +849,6 @@
},
"freeRedemptionOffer": "免费兑换优惠",
"cellPhoneNumber": "手机号",
"todayOnWeeklyCalendar": "今"
"todayOnWeeklyCalendar": "今",
"hrvTrendForThatDay": "该日HRV趋势"
}
\ No newline at end of file
... ...
... ... @@ -3326,6 +3326,12 @@ abstract class AppLocalizations {
/// In zh, this message translates to:
/// **'今'**
String get todayOnWeeklyCalendar;
/// No description provided for @hrvTrendForThatDay.
///
/// In zh, this message translates to:
/// **'该日HRV趋势'**
String get hrvTrendForThatDay;
}
class _AppLocalizationsDelegate
... ...
... ... @@ -1853,4 +1853,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get todayOnWeeklyCalendar => 'today';
@override
String get hrvTrendForThatDay => 'HRV Trend for That Day';
}
... ...
... ... @@ -1754,4 +1754,7 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get todayOnWeeklyCalendar => '今';
@override
String get hrvTrendForThatDay => '该日HRV趋势';
}
... ...