fix: suspend hero animation when not visible

This commit is contained in:
edde746
2026-02-05 10:57:29 +01:00
parent 5873914baa
commit 3d3c8641ed
3 changed files with 49 additions and 3 deletions
+8
View File
@@ -0,0 +1,8 @@
/// Mixin for screens that need to react to tab visibility changes.
///
/// Used by MainScreen to pause expensive work (e.g. animation tickers)
/// when the screen's tab is no longer visible, and resume it when shown again.
mixin TabVisibilityAware {
void onTabShown();
void onTabHidden();
}
+16 -3
View File
@@ -22,6 +22,7 @@ import 'profile/profile_switch_screen.dart';
import '../providers/user_profile_provider.dart';
import '../providers/settings_provider.dart';
import '../mixins/refreshable.dart';
import '../mixins/tab_visibility_aware.dart';
import '../i18n/strings.g.dart';
import '../mixins/item_updatable.dart';
import '../mixins/watch_state_aware.dart';
@@ -53,6 +54,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
FullRefreshable,
ItemUpdatable,
WatchStateAware,
TabVisibilityAware,
SingleTickerProviderStateMixin,
WidgetsBindingObserver {
static const Duration _heroAutoScrollDuration = Duration(seconds: 8);
@@ -470,6 +472,19 @@ class _DiscoverScreenState extends State<DiscoverScreen>
_startAutoScroll();
}
@override
void onTabHidden() {
_autoScrollTimer?.cancel();
_indicatorAnimationController.stop();
}
@override
void onTabShown() {
if (!_isAutoScrollPaused) {
_startAutoScroll();
}
}
// Helper method to calculate visible dot range (max 5 dots)
({int start, int end}) _getVisibleDotRange() {
final totalDots = _onDeck.length;
@@ -1277,9 +1292,7 @@ class _DiscoverScreenState extends State<DiscoverScreen>
final maxWidth = dotSize * 3; // 24px for normal, 15px for small
final fillWidth = dotSize + ((maxWidth - dotSize) * _indicatorAnimationController.value);
final onSurface = Theme.of(context).colorScheme.onSurface;
return AnimatedContainer(
duration: tokens(context).slow,
curve: Curves.easeInOut,
return Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
width: maxWidth,
height: dotSize,
+25
View File
@@ -14,6 +14,7 @@ import '../utils/platform_detector.dart';
import '../utils/video_player_navigation.dart';
import '../main.dart';
import '../mixins/refreshable.dart';
import '../mixins/tab_visibility_aware.dart';
import '../navigation/navigation_tabs.dart';
import '../providers/multi_server_provider.dart';
import '../providers/server_state_provider.dart';
@@ -495,10 +496,23 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
}
}
@override
void didPushNext() {
// Called when a child route is pushed on top (e.g., video player)
if (_currentIndex == 0 && !_isOffline) {
if (_discoverKey.currentState case final TabVisibilityAware aware) {
aware.onTabHidden();
}
}
}
@override
void didPopNext() {
// Called when returning to this route from a child route (e.g., from video player)
if (_currentIndex == 0 && !_isOffline) {
if (_discoverKey.currentState case final TabVisibilityAware aware) {
aware.onTabShown();
}
_onDiscoverBecameVisible();
}
}
@@ -593,8 +607,19 @@ class _MainScreenState extends State<MainScreen> with RouteAware, WindowListener
// Skip online-only screen logic in offline mode
if (!_isOffline) {
// Pause/resume discover auto-scroll when switching tabs
if (previousIndex == 0 && index != 0) {
if (_discoverKey.currentState case final TabVisibilityAware aware) {
aware.onTabHidden();
}
}
// Notify discover screen when it becomes visible via tab switch
if (index == 0) {
if (previousIndex != 0) {
if (_discoverKey.currentState case final TabVisibilityAware aware) {
aware.onTabShown();
}
}
_onDiscoverBecameVisible();
}
// Ensure the libraries screen applies focus when brought into view