From 9a228cb3cef3cdc6d411339dcdd4e8f9307205eb Mon Sep 17 00:00:00 2001 From: Hirdaya Shrestha Date: Wed, 15 Jul 2026 15:38:05 +0545 Subject: [PATCH 1/9] feat: added customizable page indicator widget --- example/lib/main.dart | 154 +++++++----------------- example/pubspec.lock | 2 +- lib/coverflow_carousel.dart | 1 + lib/src/coverflow_page_indicator.dart | 90 ++++++++++++++ test/coverflow_page_indicator_test.dart | 113 +++++++++++++++++ 5 files changed, 247 insertions(+), 113 deletions(-) create mode 100644 lib/src/coverflow_page_indicator.dart create mode 100644 test/coverflow_page_indicator_test.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index fb80c13..c0b7800 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -53,6 +53,11 @@ class _CoverflowDemoScreenState extends State { double _shadowElevation = 8.0; double _cardCornerRadiusValue = 24.0; + // Page indicator settings + double _indicatorDotSize = 8.0; + double _indicatorDotSpacing = 12.0; + bool _indicatorUseThemeColor = true; + Axis _scrollDirection = Axis.horizontal; bool _useCustomWidth = false; double _carouselWidth = 340.0; @@ -301,9 +306,14 @@ class _CoverflowDemoScreenState extends State { const SizedBox(height: 24), // Dynamic Liquid Page Indicator - _CoverflowPageIndicator( + CoverflowPageIndicator( + controller: _controller, itemCount: _demoCards.length, - pageListenable: _controller.pageListenable, + activeColor: _indicatorUseThemeColor + ? Colors.pinkAccent + : Colors.white, + dotSize: _indicatorDotSize, + dotSpacing: _indicatorDotSpacing, onTap: (index) { _controller.animateTo(index); }, @@ -478,6 +488,36 @@ class _CoverflowDemoScreenState extends State { ), suffix: 'px', ), + const Divider( + height: 32, + color: Colors.white24, + ), + _GlassSlider( + title: 'Indicator Dot Size', + value: _indicatorDotSize, + min: 4.0, + max: 16.0, + onChanged: (val) => + setState(() => _indicatorDotSize = val), + suffix: 'px', + ), + _GlassSlider( + title: 'Indicator Dot Spacing', + value: _indicatorDotSpacing, + min: 4.0, + max: 24.0, + onChanged: (val) => setState( + () => _indicatorDotSpacing = val, + ), + suffix: 'px', + ), + _GlassSwitch( + title: 'Theme-colored Active Dot', + value: _indicatorUseThemeColor, + onChanged: (val) => setState( + () => _indicatorUseThemeColor = val, + ), + ), ] else if (_configTab == 1) ...[ // Tab 1: Motion settings _GlassSwitch( @@ -709,116 +749,6 @@ class _AmbientBackdrop extends StatelessWidget { } } -/// Liquid sliding active pill page indicator -class _CoverflowPageIndicator extends StatelessWidget { - final int itemCount; - final ValueNotifier pageListenable; - final void Function(int) onTap; - - const _CoverflowPageIndicator({ - required this.itemCount, - required this.pageListenable, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - const double dotSize = 8.0; - const double spacing = 12.0; - const double step = dotSize + spacing; - - return ValueListenableBuilder( - valueListenable: pageListenable, - builder: (context, page, _) { - final double t = page - page.floor(); - final int floor = page.floor(); - - final double activeLeft; - final double activeWidth; - - if (t < 0.5) { - activeLeft = (floor % itemCount) * step; - activeWidth = dotSize + (t / 0.5) * step; - } else { - activeLeft = (floor % itemCount) * step + ((t - 0.5) / 0.5) * step; - activeWidth = dotSize + (1.0 - (t - 0.5) / 0.5) * step; - } - - final int indexA = floor % itemCount; - final int indexB = (floor + 1) % itemCount; - - final double left; - final double width; - - if (indexB == 0 && t > 0.0) { - if (t < 0.5) { - left = indexA * step; - width = dotSize + (t / 0.5) * step; - } else { - left = 0; - width = dotSize + (1.0 - (t - 0.5) / 0.5) * step; - } - } else { - left = activeLeft; - width = activeWidth; - } - - return Container( - height: 24, - alignment: Alignment.center, - child: SizedBox( - width: itemCount * dotSize + (itemCount - 1) * spacing, - height: dotSize, - child: Stack( - clipBehavior: Clip.none, - children: [ - ...List.generate(itemCount, (i) { - return Positioned( - left: i * step, - top: 0, - width: dotSize, - height: dotSize, - child: GestureDetector( - onTap: () => onTap(i), - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.white.withValues(alpha: 0.15), - ), - ), - ), - ); - }), - Positioned( - left: left, - top: 0, - width: width, - height: dotSize, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(dotSize / 2), - gradient: const LinearGradient( - colors: [Colors.pinkAccent, Colors.purpleAccent], - ), - boxShadow: [ - BoxShadow( - color: Colors.pinkAccent.withValues(alpha: 0.4), - blurRadius: 8, - spreadRadius: 1, - ), - ], - ), - ), - ), - ], - ), - ), - ); - }, - ); - } -} - /// Premium Play Button with dynamic scale on press class _PremiumPlayButton extends StatefulWidget { final String title; diff --git a/example/pubspec.lock b/example/pubspec.lock index cdbe35d..d44d941 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -47,7 +47,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0" + version: "2.0.1" fake_async: dependency: transitive description: diff --git a/lib/coverflow_carousel.dart b/lib/coverflow_carousel.dart index 6a4dd61..c89bd1d 100644 --- a/lib/coverflow_carousel.dart +++ b/lib/coverflow_carousel.dart @@ -6,3 +6,4 @@ library; export 'src/coverflow_carousel.dart'; export 'src/coverflow_carousel_controller.dart'; +export 'src/coverflow_page_indicator.dart'; diff --git a/lib/src/coverflow_page_indicator.dart b/lib/src/coverflow_page_indicator.dart new file mode 100644 index 0000000..5e66957 --- /dev/null +++ b/lib/src/coverflow_page_indicator.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'coverflow_carousel_controller.dart'; + +/// Shows the current page as a sliding pill over dots. +/// +/// Listens to [controller]'s [ValueNotifier] and animates the active +/// indicator between pages as the carousel scrolls. +class CoverflowPageIndicator extends StatelessWidget { + const CoverflowPageIndicator({ + super.key, + required this.controller, + required this.itemCount, + this.activeColor = Colors.white, + this.inactiveColor = Colors.white38, + this.dotSize = 8.0, + this.dotSpacing = 12.0, + this.onTap, + }); + + final CoverflowCarouselController controller; + final int itemCount; + final Color activeColor; + final Color inactiveColor; + final double dotSize; + final double dotSpacing; + final void Function(int index)? onTap; + + @override + Widget build(BuildContext context) { + final step = dotSize + dotSpacing; + + return ValueListenableBuilder( + valueListenable: controller.pageListenable, + builder: (context, page, _) { + if (itemCount <= 0) return const SizedBox.shrink(); + + final count = itemCount; + final clamped = page % count; + final floor = clamped.floor(); + final t = clamped - floor; + + final activeLeft = + floor * step + (t < 0.5 ? 0 : ((t - 0.5) / 0.5) * step); + final activeWidth = + dotSize + + (t < 0.5 ? (t / 0.5) * step : (1.0 - (t - 0.5) / 0.5) * step); + + return SizedBox( + width: count * dotSize + (count - 1) * dotSpacing, + height: dotSize + 8, + child: Stack( + clipBehavior: Clip.none, + children: [ + ...List.generate(count, (i) { + return Positioned( + left: i * step, + top: 4, + width: dotSize, + height: dotSize, + child: GestureDetector( + onTap: onTap != null ? () => onTap!(i) : null, + behavior: HitTestBehavior.opaque, + child: Container( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: inactiveColor, + ), + ), + ), + ); + }), + Positioned( + left: activeLeft, + top: 4, + width: activeWidth, + height: dotSize, + child: Container( + decoration: BoxDecoration( + color: activeColor, + borderRadius: BorderRadius.circular(dotSize / 2), + ), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/test/coverflow_page_indicator_test.dart b/test/coverflow_page_indicator_test.dart new file mode 100644 index 0000000..c1b5406 --- /dev/null +++ b/test/coverflow_page_indicator_test.dart @@ -0,0 +1,113 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:coverflow_carousel/coverflow_carousel.dart'; + +void main() { + group('CoverflowPageIndicator', () { + testWidgets('renders correct number of dots', (tester) async { + final controller = CoverflowCarouselController(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CoverflowPageIndicator(controller: controller, itemCount: 5), + ), + ), + ); + + expect(find.byType(GestureDetector), findsNWidgets(5)); + }); + + testWidgets('updates on controller page change', (tester) async { + final controller = CoverflowCarouselController(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CoverflowPageIndicator(controller: controller, itemCount: 5), + ), + ), + ); + + controller.updateMetrics(rawPage: 2.0, normalizedPage: 2.0); + await tester.pump(); + + expect(find.byType(CoverflowPageIndicator), findsOneWidget); + expect(find.byType(GestureDetector), findsNWidgets(5)); + }); + + testWidgets('calls onTap with correct index', (tester) async { + final controller = CoverflowCarouselController(); + int? tappedIndex; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: CoverflowPageIndicator( + controller: controller, + itemCount: 3, + onTap: (index) => tappedIndex = index, + ), + ), + ), + ), + ); + + final detectors = find.byType(GestureDetector); + await tester.tap(detectors.last); + expect(tappedIndex, 2); + }); + + testWidgets('applies custom colors and sizes', (tester) async { + final controller = CoverflowCarouselController(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CoverflowPageIndicator( + controller: controller, + itemCount: 3, + activeColor: Colors.red, + inactiveColor: Colors.grey, + dotSize: 12.0, + dotSpacing: 20.0, + ), + ), + ), + ); + + expect(find.byType(CoverflowPageIndicator), findsOneWidget); + expect(find.byType(GestureDetector), findsNWidgets(3)); + }); + + testWidgets('returns empty box for zero items', (tester) async { + final controller = CoverflowCarouselController(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CoverflowPageIndicator(controller: controller, itemCount: 0), + ), + ), + ); + + expect(find.byType(SizedBox), findsOneWidget); + expect(find.byType(GestureDetector), findsNothing); + }); + + testWidgets('handles single item gracefully', (tester) async { + final controller = CoverflowCarouselController(); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: CoverflowPageIndicator(controller: controller, itemCount: 1), + ), + ), + ); + + expect(find.byType(GestureDetector), findsOneWidget); + }); + }); +} From 1bf15b9af6c87a83b77335832ab1931359124e52 Mon Sep 17 00:00:00 2001 From: Hirdaya Shrestha Date: Sat, 18 Jul 2026 14:07:07 +0545 Subject: [PATCH 2/9] fix: page indicator boundary wrap, touch targets, ignore pointer --- lib/src/coverflow_page_indicator.dart | 87 ++++++++++++++++++--------- 1 file changed, 57 insertions(+), 30 deletions(-) diff --git a/lib/src/coverflow_page_indicator.dart b/lib/src/coverflow_page_indicator.dart index 5e66957..b607a0d 100644 --- a/lib/src/coverflow_page_indicator.dart +++ b/lib/src/coverflow_page_indicator.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'coverflow_carousel_controller.dart'; +const double _tapTargetSize = 40.0; + /// Shows the current page as a sliding pill over dots. /// /// Listens to [controller]'s [ValueNotifier] and animates the active @@ -35,15 +37,11 @@ class CoverflowPageIndicator extends StatelessWidget { if (itemCount <= 0) return const SizedBox.shrink(); final count = itemCount; - final clamped = page % count; + final clamped = ((page % count) + count) % count; final floor = clamped.floor(); final t = clamped - floor; - - final activeLeft = - floor * step + (t < 0.5 ? 0 : ((t - 0.5) / 0.5) * step); - final activeWidth = - dotSize + - (t < 0.5 ? (t / 0.5) * step : (1.0 - (t - 0.5) / 0.5) * step); + final indexB = (floor + 1) % count; + final isWrapping = count > 1 && indexB == 0 && floor == count - 1; return SizedBox( width: count * dotSize + (count - 1) * dotSpacing, @@ -51,40 +49,69 @@ class CoverflowPageIndicator extends StatelessWidget { child: Stack( clipBehavior: Clip.none, children: [ - ...List.generate(count, (i) { - return Positioned( - left: i * step, - top: 4, - width: dotSize, - height: dotSize, + for (final i in List.generate(count, (i) => i)) + Positioned( + left: i * step - (_tapTargetSize - dotSize) / 2, + top: 4 - (_tapTargetSize - dotSize) / 2, + width: _tapTargetSize, + height: _tapTargetSize, child: GestureDetector( onTap: onTap != null ? () => onTap!(i) : null, behavior: HitTestBehavior.opaque, - child: Container( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: inactiveColor, + child: Center( + child: Container( + width: dotSize, + height: dotSize, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: inactiveColor, + ), ), ), ), - ); - }), - Positioned( - left: activeLeft, - top: 4, - width: activeWidth, - height: dotSize, - child: Container( - decoration: BoxDecoration( - color: activeColor, - borderRadius: BorderRadius.circular(dotSize / 2), - ), ), - ), + if (isWrapping && t > 0.5) ...[ + _buildActivePill( + left: 0, + width: dotSize * ((t - 0.5) / 0.5), + ), + _buildActivePill( + left: floor * step, + width: dotSize * (1.0 - (t - 0.5) / 0.5), + ), + ] else + _buildActivePill( + left: floor * step + + (t < 0.5 ? 0 : ((t - 0.5) / 0.5) * step), + width: dotSize + + (t < 0.5 + ? (t / 0.5) * step + : (1.0 - (t - 0.5) / 0.5) * step), + ), ], ), ); }, ); } + + Widget _buildActivePill({ + required double left, + required double width, + }) { + return Positioned( + left: left, + top: 4, + width: width, + height: dotSize, + child: IgnorePointer( + child: Container( + decoration: BoxDecoration( + color: activeColor, + borderRadius: BorderRadius.circular(dotSize / 2), + ), + ), + ), + ); + } } From a1a04b07eb8cb25799b00890896e644ac360b21a Mon Sep 17 00:00:00 2001 From: Hirdaya Shrestha Date: Sat, 18 Jul 2026 14:53:14 +0545 Subject: [PATCH 3/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- example/lib/main.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index c0b7800..36171f4 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -310,9 +310,8 @@ class _CoverflowDemoScreenState extends State { controller: _controller, itemCount: _demoCards.length, activeColor: _indicatorUseThemeColor - ? Colors.pinkAccent + ? Theme.of(context).colorScheme.primary : Colors.white, - dotSize: _indicatorDotSize, dotSpacing: _indicatorDotSpacing, onTap: (index) { _controller.animateTo(index); From 1a014e9ecce3dbfd0186eb23a5162392e00f97e5 Mon Sep 17 00:00:00 2001 From: Hirdaya Shrestha Date: Sat, 18 Jul 2026 14:56:24 +0545 Subject: [PATCH 4/9] style: dart format --- lib/src/coverflow_page_indicator.dart | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/src/coverflow_page_indicator.dart b/lib/src/coverflow_page_indicator.dart index b607a0d..f1514b0 100644 --- a/lib/src/coverflow_page_indicator.dart +++ b/lib/src/coverflow_page_indicator.dart @@ -71,19 +71,16 @@ class CoverflowPageIndicator extends StatelessWidget { ), ), if (isWrapping && t > 0.5) ...[ - _buildActivePill( - left: 0, - width: dotSize * ((t - 0.5) / 0.5), - ), + _buildActivePill(left: 0, width: dotSize * ((t - 0.5) / 0.5)), _buildActivePill( left: floor * step, width: dotSize * (1.0 - (t - 0.5) / 0.5), ), ] else _buildActivePill( - left: floor * step + - (t < 0.5 ? 0 : ((t - 0.5) / 0.5) * step), - width: dotSize + + left: floor * step + (t < 0.5 ? 0 : ((t - 0.5) / 0.5) * step), + width: + dotSize + (t < 0.5 ? (t / 0.5) * step : (1.0 - (t - 0.5) / 0.5) * step), @@ -95,10 +92,7 @@ class CoverflowPageIndicator extends StatelessWidget { ); } - Widget _buildActivePill({ - required double left, - required double width, - }) { + Widget _buildActivePill({required double left, required double width}) { return Positioned( left: left, top: 4, From c851388ab12d26e45b4812d218954f8774f5cb75 Mon Sep 17 00:00:00 2001 From: Hirdaya Shrestha Date: Sat, 18 Jul 2026 14:59:54 +0545 Subject: [PATCH 5/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/src/coverflow_page_indicator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/coverflow_page_indicator.dart b/lib/src/coverflow_page_indicator.dart index f1514b0..2998ece 100644 --- a/lib/src/coverflow_page_indicator.dart +++ b/lib/src/coverflow_page_indicator.dart @@ -37,7 +37,7 @@ class CoverflowPageIndicator extends StatelessWidget { if (itemCount <= 0) return const SizedBox.shrink(); final count = itemCount; - final clamped = ((page % count) + count) % count; +final clamped = page.clamp(0.0, count.toDouble() - 1e-9).toDouble(); final floor = clamped.floor(); final t = clamped - floor; final indexB = (floor + 1) % count; From 681fc56eb705ec6cc651c368a29923b147ab0f46 Mon Sep 17 00:00:00 2001 From: Hirdaya Shrestha Date: Sat, 18 Jul 2026 15:07:03 +0545 Subject: [PATCH 6/9] style: dart format --- lib/src/coverflow_page_indicator.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/coverflow_page_indicator.dart b/lib/src/coverflow_page_indicator.dart index 2998ece..4b39728 100644 --- a/lib/src/coverflow_page_indicator.dart +++ b/lib/src/coverflow_page_indicator.dart @@ -37,7 +37,7 @@ class CoverflowPageIndicator extends StatelessWidget { if (itemCount <= 0) return const SizedBox.shrink(); final count = itemCount; -final clamped = page.clamp(0.0, count.toDouble() - 1e-9).toDouble(); + final clamped = page.clamp(0.0, count.toDouble() - 1e-9).toDouble(); final floor = clamped.floor(); final t = clamped - floor; final indexB = (floor + 1) % count; From 5191af2a04d48c32f8c2c4eb314b31dd5458e17c Mon Sep 17 00:00:00 2001 From: Hirdaya Shrestha Date: Sat, 18 Jul 2026 16:27:23 +0545 Subject: [PATCH 7/9] fix: simplify wrap animation and fix active pill position --- lib/src/coverflow_page_indicator.dart | 16 ++++++++-------- test/coverflow_page_indicator_test.dart | 7 ++++++- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/src/coverflow_page_indicator.dart b/lib/src/coverflow_page_indicator.dart index 4b39728..08d9104 100644 --- a/lib/src/coverflow_page_indicator.dart +++ b/lib/src/coverflow_page_indicator.dart @@ -70,13 +70,12 @@ class CoverflowPageIndicator extends StatelessWidget { ), ), ), - if (isWrapping && t > 0.5) ...[ - _buildActivePill(left: 0, width: dotSize * ((t - 0.5) / 0.5)), + if (isWrapping && t > 0.5) _buildActivePill( - left: floor * step, - width: dotSize * (1.0 - (t - 0.5) / 0.5), - ), - ] else + left: 0, + width: dotSize + (1.0 - (t - 0.5) / 0.5) * step, + ) + else _buildActivePill( left: floor * step + (t < 0.5 ? 0 : ((t - 0.5) / 0.5) * step), width: @@ -93,9 +92,10 @@ class CoverflowPageIndicator extends StatelessWidget { } Widget _buildActivePill({required double left, required double width}) { + final edge = (_tapTargetSize - dotSize) / 2; return Positioned( - left: left, - top: 4, + left: left + edge, + top: 4 + edge, width: width, height: dotSize, child: IgnorePointer( diff --git a/test/coverflow_page_indicator_test.dart b/test/coverflow_page_indicator_test.dart index c1b5406..f6ad6a0 100644 --- a/test/coverflow_page_indicator_test.dart +++ b/test/coverflow_page_indicator_test.dart @@ -92,7 +92,12 @@ void main() { ), ); - expect(find.byType(SizedBox), findsOneWidget); + expect( + find.byWidgetPredicate( + (w) => w is SizedBox && w.width == 0 && w.height == 0, + ), + findsOneWidget, + ); expect(find.byType(GestureDetector), findsNothing); }); From 2d6612a8c807021f868128613d54d2ffa56ee8ba Mon Sep 17 00:00:00 2001 From: Hirdaya Shrestha Date: Sat, 18 Jul 2026 16:41:46 +0545 Subject: [PATCH 8/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/src/coverflow_page_indicator.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/coverflow_page_indicator.dart b/lib/src/coverflow_page_indicator.dart index 08d9104..e4b0579 100644 --- a/lib/src/coverflow_page_indicator.dart +++ b/lib/src/coverflow_page_indicator.dart @@ -92,10 +92,9 @@ class CoverflowPageIndicator extends StatelessWidget { } Widget _buildActivePill({required double left, required double width}) { - final edge = (_tapTargetSize - dotSize) / 2; return Positioned( - left: left + edge, - top: 4 + edge, + left: left, + top: 4, width: width, height: dotSize, child: IgnorePointer( From 940f72484e57fcb2bd8f5a8e28ae28f1acef1a6a Mon Sep 17 00:00:00 2001 From: Hirdaya Shrestha Date: Sat, 18 Jul 2026 21:54:00 +0545 Subject: [PATCH 9/9] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- lib/src/coverflow_page_indicator.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/src/coverflow_page_indicator.dart b/lib/src/coverflow_page_indicator.dart index e4b0579..460b64f 100644 --- a/lib/src/coverflow_page_indicator.dart +++ b/lib/src/coverflow_page_indicator.dart @@ -17,7 +17,10 @@ class CoverflowPageIndicator extends StatelessWidget { this.dotSize = 8.0, this.dotSpacing = 12.0, this.onTap, - }); + }) : assert(itemCount >= 0), + assert(dotSize > 0), + assert(dotSpacing >= 0), + assert(_tapTargetSize >= dotSize); final CoverflowCarouselController controller; final int itemCount;