Files
plezy/lib/widgets/hub_navigation_controller.dart
T
2025-11-26 13:51:25 +01:00

116 lines
3.8 KiB
Dart

import 'package:flutter/material.dart';
/// Controller that manages navigation between hub sections.
/// Hub sections register themselves, and MediaCards can request
/// to navigate to adjacent hub sections.
class HubNavigationController extends ChangeNotifier {
final List<HubSectionRegistration> _registrations = [];
/// Map of hub ID to last focused item index
final Map<String, int> _focusMemory = {};
/// Register a hub section with the controller
/// If a hub with the same ID is already registered, it will be replaced
/// Registrations are kept sorted by order for consistent navigation
void register(HubSectionRegistration registration) {
// Remove any existing registration with the same hubId
_registrations.removeWhere((r) => r.hubId == registration.hubId);
_registrations.add(registration);
// Sort by order to maintain consistent navigation regardless of registration timing
_registrations.sort((a, b) => a.order.compareTo(b.order));
}
/// Unregister a hub section
void unregister(String hubId) {
_registrations.removeWhere((r) => r.hubId == hubId);
_focusMemory.remove(hubId);
}
/// Remember the focused item index for a hub
void rememberFocusedIndex(String hubId, int index) {
_focusMemory[hubId] = index;
}
/// Get the remembered focused index for a hub (or 0 if none)
int getRememberedIndex(String hubId) {
return _focusMemory[hubId] ?? 0;
}
/// Navigate to the next hub section (direction: 1 for down, -1 for up)
/// Returns true if navigation was handled
bool navigateToAdjacentHub(String currentHubId, int direction) {
final currentIndex = _registrations.indexWhere(
(r) => r.hubId == currentHubId,
);
if (currentIndex == -1) return false;
final targetIndex = currentIndex + direction;
if (targetIndex < 0 || targetIndex >= _registrations.length) return false;
final targetHub = _registrations[targetIndex];
if (targetHub.itemCount == 0) {
// Nothing to focus in the target hub
return false;
}
final rememberedIndex = getRememberedIndex(targetHub.hubId);
// Focus the remembered item or first item
targetHub.focusItem(rememberedIndex.clamp(0, targetHub.itemCount - 1));
return true;
}
/// Focus a specific hub and item by order index
/// [hubIndex] is the index in the sorted list (0 = first hub)
/// [itemIndex] is the item within that hub (0 = first item)
void focusHub(int hubIndex, int itemIndex) {
if (hubIndex < 0 || hubIndex >= _registrations.length) return;
final hub = _registrations[hubIndex];
if (hub.itemCount > 0) {
hub.focusItem(itemIndex.clamp(0, hub.itemCount - 1));
}
}
}
/// Registration info for a hub section
class HubSectionRegistration {
final String hubId;
final int itemCount;
final int order; // Visual order on screen (lower = higher on screen)
final void Function(int index) focusItem;
HubSectionRegistration({
required this.hubId,
required this.itemCount,
required this.focusItem,
this.order = 1000, // Default high order for dynamic hubs
});
}
/// InheritedWidget to provide the HubNavigationController down the tree
class HubNavigationScope extends InheritedWidget {
final HubNavigationController controller;
const HubNavigationScope({
super.key,
required this.controller,
required super.child,
});
static HubNavigationController? of(BuildContext context) {
final scope = context
.dependOnInheritedWidgetOfExactType<HubNavigationScope>();
return scope?.controller;
}
static HubNavigationController? maybeOf(BuildContext context) {
final scope = context.getInheritedWidgetOfExactType<HubNavigationScope>();
return scope?.controller;
}
@override
bool updateShouldNotify(HubNavigationScope oldWidget) {
return controller != oldWidget.controller;
}
}