Files
plezy/lib/mixins/grid_focus_node_mixin.dart
T
edde746 8d56171bd2 perf: library grid scroll and idle frame rendering
- Window _loadedItems and focus nodes to bound memory growth
- Eager prefetch and image precaching near viewport
- Static skeleton placeholders during active scroll
- Throttle alpha bar updates, dynamic initial fetch size
- Fix discover timer scheduling frames when tab hidden
- Disable tickers for offscreen IndexedStack children
- Static image placeholder with fade-in transition
2026-03-24 17:19:28 +01:00

73 lines
2.5 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'));
}
/// Record that the item at [index] received focus.
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;
/// Remove focus nodes for indices >= [itemCount].
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();
}
}
}
/// Dispose all grid-item focus nodes.
void disposeGridFocusNodes() {
for (final node in gridItemFocusNodes.values) {
node.dispose();
}
gridItemFocusNodes.clear();
}
}