refactor(core): consolidate shared app foundations

This commit is contained in:
edde746
2026-07-12 17:31:12 +02:00
parent a481fc1000
commit 97f7508067
59 changed files with 1244 additions and 919 deletions
@@ -81,38 +81,34 @@ class _LibraryCollectionsTabState extends BaseLibraryTabState<MediaItem, Library
@override
Future<void> loadItems() async {
setState(() {
isLoading = true;
errorMessage = null;
items = [];
resetPaginationState();
});
try {
final initialPage = await loadInitialPageWithStatus(_pageSize);
if (!initialPage.applied || !mounted) return;
setState(() {
items = loadedItems.values.toList();
await loadInitialPaginatedItems(
pageSize: _pageSize,
resetViewState: () {
isLoading = true;
errorMessage = null;
items = [];
},
applyLoadedItems: (loaded) {
items = loaded;
isLoading = false;
});
hasLoadedData = true;
tryFocus();
if (widget.onDataLoaded != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) widget.onDataLoaded!();
});
}
} catch (e, st) {
appLogger.e('Error loading $errorContext', error: e, stackTrace: st);
if (!mounted) return;
setState(() {
errorMessage = 'Failed to load $errorContext: ${e.toString()}';
},
applyError: (error, _) {
errorMessage = 'Failed to load $errorContext: ${error.toString()}';
isLoading = false;
});
}
},
onLoaded: (_, _) {
hasLoadedData = true;
tryFocus();
if (widget.onDataLoaded != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) widget.onDataLoaded!();
});
}
},
onError: (error, stackTrace) {
appLogger.e('Error loading $errorContext', error: error, stackTrace: stackTrace);
},
);
}
@override
@@ -86,38 +86,34 @@ class _LibraryPlaylistsTabState extends BaseLibraryTabState<MediaPlaylist, Libra
@override
Future<void> loadItems() async {
setState(() {
isLoading = true;
errorMessage = null;
items = [];
resetPaginationState();
});
try {
final initialPage = await loadInitialPageWithStatus(_pageSize);
if (!initialPage.applied || !mounted) return;
setState(() {
items = loadedItems.values.toList();
await loadInitialPaginatedItems(
pageSize: _pageSize,
resetViewState: () {
isLoading = true;
errorMessage = null;
items = [];
},
applyLoadedItems: (loaded) {
items = loaded;
isLoading = false;
});
hasLoadedData = true;
tryFocus();
if (widget.onDataLoaded != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) widget.onDataLoaded!();
});
}
} catch (e, st) {
appLogger.e('Error loading $errorContext', error: e, stackTrace: st);
if (!mounted) return;
setState(() {
errorMessage = 'Failed to load $errorContext: ${e.toString()}';
},
applyError: (error, _) {
errorMessage = 'Failed to load $errorContext: ${error.toString()}';
isLoading = false;
});
}
},
onLoaded: (_, _) {
hasLoadedData = true;
tryFocus();
if (widget.onDataLoaded != null) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) widget.onDataLoaded!();
});
}
},
onError: (error, stackTrace) {
appLogger.e('Error loading $errorContext', error: error, stackTrace: stackTrace);
},
);
}
@override
@@ -4,6 +4,7 @@ import '../../../media/ids.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../focus/hub_vertical_navigation.dart';
import '../../../i18n/strings.g.dart';
import '../../../media/media_hub.dart';
import '../../../media/media_item.dart';
@@ -267,27 +268,15 @@ class _LibraryRecommendedTabState extends BaseLibraryTabState<MediaHub, LibraryR
/// Handle vertical navigation between hubs
bool _handleVerticalNavigation(int hubIndex, bool isUp) {
final targetIndex = isUp ? hubIndex - 1 : hubIndex + 1;
// Check if target is valid
if (targetIndex < 0) {
// At top boundary - return false to allow onNavigateUp to handle it
return false;
}
if (targetIndex >= _hubKeys.length) {
// At bottom boundary, block navigation
return true;
}
// Navigate to target hub with column memory
final targetState = _hubKeys[targetIndex].currentState;
if (targetState != null) {
targetState.requestFocusFromMemory();
return true;
}
return false;
return navigateVerticalHubRows(
hubCount: items.length,
hubIndex: hubIndex,
isUp: isUp,
propagateTopBoundary: true,
requestFocus: (targetIndex) {
_hubKeys[targetIndex].currentState?.requestFocusFromMemory();
},
);
}
/// Focus the first item in the first hub (for tab activation)