fix: limit hero dots count

This commit is contained in:
edde746
2025-11-04 09:40:02 +01:00
parent 1c67fa3998
commit e50bd46386
+109 -47
View File
@@ -77,6 +77,11 @@ class _DiscoverScreenState extends State<DiscoverScreen>
if (_onDeck.isEmpty || !_heroController.hasClients || _isAutoScrollPaused)
return;
// Validate current index is within bounds before calculating next page
if (_currentHeroIndex >= _onDeck.length) {
_currentHeroIndex = 0;
}
final nextPage = (_currentHeroIndex + 1) % _onDeck.length;
_heroController.animateToPage(
nextPage,
@@ -112,6 +117,41 @@ class _DiscoverScreenState extends State<DiscoverScreen>
_startAutoScroll();
}
// Helper method to calculate visible dot range (max 5 dots)
({int start, int end}) _getVisibleDotRange() {
final totalDots = _onDeck.length;
if (totalDots <= 5) {
return (start: 0, end: totalDots - 1);
}
// Center the active dot when possible
final center = _currentHeroIndex;
int start = (center - 2).clamp(0, totalDots - 5);
int end = start + 4; // 5 dots total (0-4 inclusive)
return (start: start, end: end);
}
// Helper method to determine dot size based on position
double _getDotSize(int dotIndex, int start, int end) {
final totalDots = _onDeck.length;
// If we have 5 or fewer dots, all are full size (8px)
if (totalDots <= 5) {
return 8.0;
}
// First and last visible dots are smaller if there are more items beyond them
final isFirstVisible = dotIndex == start && start > 0;
final isLastVisible = dotIndex == end && end < totalDots - 1;
if (isFirstVisible || isLastVisible) {
return 5.0; // Smaller edge dots
}
return 8.0; // Normal size
}
Future<void> _loadContent() async {
appLogger.d('Loading discover content');
setState(() {
@@ -137,7 +177,16 @@ class _DiscoverScreenState extends State<DiscoverScreen>
_onDeck = onDeck;
_recentlyAdded = recentlyAdded;
_isLoading = false;
// Reset hero index to avoid sync issues
_currentHeroIndex = 0;
});
// Sync PageController to first page after data loads
if (_heroController.hasClients && onDeck.isNotEmpty) {
_heroController.jumpToPage(0);
}
appLogger.d('Discover content loaded successfully');
} catch (e) {
appLogger.e('Failed to load discover content', error: e);
@@ -452,10 +501,13 @@ class _DiscoverScreenState extends State<DiscoverScreen>
controller: _heroController,
itemCount: _onDeck.length,
onPageChanged: (index) {
setState(() {
_currentHeroIndex = index;
});
_resetAutoScrollTimer();
// Validate index is within bounds before updating
if (index >= 0 && index < _onDeck.length) {
setState(() {
_currentHeroIndex = index;
});
_resetAutoScrollTimer();
}
},
itemBuilder: (context, index) {
return _buildHeroItem(_onDeck[index]);
@@ -488,57 +540,67 @@ class _DiscoverScreenState extends State<DiscoverScreen>
),
// Spacer to separate indicators from button
const SizedBox(width: 8),
// Page indicators
...List.generate(_onDeck.length, (index) {
final isActive = _currentHeroIndex == index;
if (isActive) {
// Animated progress indicator for active page
return AnimatedBuilder(
animation: _indicatorAnimationController,
builder: (context, child) {
// Fill width animates from 8px to 24px
final fillWidth =
8.0 +
(16.0 * _indicatorAnimationController.value);
// Page indicators (limited to 5 dots)
...() {
final range = _getVisibleDotRange();
return List.generate(
range.end - range.start + 1,
(i) {
final index = range.start + i;
final isActive = _currentHeroIndex == index;
final dotSize = _getDotSize(index, range.start, range.end);
if (isActive) {
// Animated progress indicator for active page
return AnimatedBuilder(
animation: _indicatorAnimationController,
builder: (context, child) {
// Fill width animates based on dot size
final maxWidth = dotSize * 3; // 24px for normal, 15px for small
final fillWidth =
dotSize +
((maxWidth - dotSize) * _indicatorAnimationController.value);
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: const EdgeInsets.symmetric(horizontal: 4),
width: maxWidth,
height: dotSize,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(dotSize / 2),
),
child: Align(
alignment: Alignment.centerLeft,
child: Container(
width: fillWidth,
height: dotSize,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(dotSize / 2),
),
),
),
);
},
);
} else {
// Static indicator for inactive pages
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: const EdgeInsets.symmetric(horizontal: 4),
width: 24,
height: 8,
width: dotSize,
height: dotSize,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(4),
),
child: Align(
alignment: Alignment.centerLeft,
child: Container(
width: fillWidth,
height: 8,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(4),
),
),
borderRadius: BorderRadius.circular(dotSize / 2),
),
);
},
);
} else {
// Static indicator for inactive pages
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
margin: const EdgeInsets.symmetric(horizontal: 4),
width: 8,
height: 8,
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.4),
borderRadius: BorderRadius.circular(4),
),
);
}
}),
}
},
);
}(),
],
),
),