|
|
|
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];
|
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} |
...
|
...
|
|