76 lines
2.7 KiB
Dart
76 lines
2.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
/// Manages a map of grid-item [FocusNode]s with focus-tracking and restoration.
|
|
///
|
|
/// Provides:
|
|
/// - Lazy creation of per-index focus nodes via [getGridItemFocusNode].
|
|
/// - Focus tracking ([lastFocusedGridIndex], [gridContentVersion]) so callers
|
|
/// can restore focus after rebuilds.
|
|
/// - [cleanupGridFocusNodes] to prune nodes for indices beyond the current count.
|
|
/// - [disposeGridFocusNodes] for full teardown.
|
|
mixin GridFocusNodeMixin<T extends StatefulWidget> on State<T> {
|
|
final Map<int, FocusNode> gridItemFocusNodes = {};
|
|
int? lastFocusedGridIndex;
|
|
int gridContentVersion = 0;
|
|
int lastFocusedGridContentVersion = 0;
|
|
|
|
/// Get or create a focus node for a grid item at [index].
|
|
FocusNode getGridItemFocusNode(int index, {String prefix = 'grid_item'}) {
|
|
return gridItemFocusNodes.putIfAbsent(index, () => FocusNode(debugLabel: '${prefix}_$index'));
|
|
}
|
|
|
|
/// Get the focus node for [index], routing index 0 through [firstNode] when
|
|
/// the grid pins a dedicated node for the first item (e.g. `firstItemFocusNode`).
|
|
FocusNode focusNodeForIndex(int index, FocusNode firstNode, {required String prefix}) {
|
|
return index == 0 ? firstNode : getGridItemFocusNode(index, prefix: prefix);
|
|
}
|
|
|
|
void trackGridItemFocus(int index, bool hasFocus) {
|
|
if (hasFocus) {
|
|
lastFocusedGridIndex = index;
|
|
lastFocusedGridContentVersion = gridContentVersion;
|
|
}
|
|
}
|
|
|
|
/// Whether the last-focused index is still valid for restoration.
|
|
bool get shouldRestoreGridFocus =>
|
|
lastFocusedGridIndex != null && lastFocusedGridContentVersion == gridContentVersion && lastFocusedGridIndex! >= 0;
|
|
|
|
void cleanupGridFocusNodes(int itemCount) {
|
|
final keysToRemove = gridItemFocusNodes.keys.where((i) => i >= itemCount).toList();
|
|
for (final key in keysToRemove) {
|
|
gridItemFocusNodes[key]?.dispose();
|
|
gridItemFocusNodes.remove(key);
|
|
}
|
|
}
|
|
|
|
/// Evict focus nodes far from [centerIndex], keeping at most [keepCount] around it.
|
|
void evictDistantFocusNodes(int centerIndex, {int keepCount = 200}) {
|
|
if (gridItemFocusNodes.length <= keepCount) return;
|
|
|
|
final halfKeep = keepCount ~/ 2;
|
|
final keepStart = centerIndex - halfKeep;
|
|
final keepEnd = centerIndex + halfKeep;
|
|
|
|
final keysToRemove = <int>[];
|
|
for (final key in gridItemFocusNodes.keys) {
|
|
if (key < keepStart || key > keepEnd) {
|
|
keysToRemove.add(key);
|
|
}
|
|
}
|
|
for (final key in keysToRemove) {
|
|
final node = gridItemFocusNodes.remove(key);
|
|
if (node != null && !node.hasFocus) {
|
|
node.dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
void disposeGridFocusNodes() {
|
|
for (final node in gridItemFocusNodes.values) {
|
|
node.dispose();
|
|
}
|
|
gridItemFocusNodes.clear();
|
|
}
|
|
}
|