- PaginatedCardGridTabState: the collections and playlists tabs were 95% identical; they now supply only pageSize/fetchPage/idOf instead of each duplicating the grid, memo, inflation budget and focus wiring. - EtagCachedRemoteStore: the anime-lists and fribb mapping stores now share one download/cache/isolate-parse/conditional-GET lifecycle. - FocusableTileStateMixin manages its own initState/didUpdateWidget/dispose instead of requiring every caller to forward three lifecycle hooks. Also drops unused ServerCapabilities entries and dead code in focusable_list_tile and music/track_row.
47 lines
1.1 KiB
Dart
47 lines
1.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../utils/scroll_utils.dart';
|
|
import 'owned_focus_node_binding.dart';
|
|
|
|
/// Manages the internal/external FocusNode lifecycle for list-tile widgets and
|
|
/// auto-scrolls the tile into view when it gains focus.
|
|
mixin FocusableTileStateMixin<T extends StatefulWidget> on State<T> {
|
|
final _focusNodeBinding = OwnedFocusNodeBinding();
|
|
FocusNode? _boundExternalNode;
|
|
|
|
FocusNode? get widgetFocusNode;
|
|
|
|
FocusNode get effectiveFocusNode => _focusNodeBinding.node;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_bindFocusNode();
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(T oldWidget) {
|
|
super.didUpdateWidget(oldWidget);
|
|
if (_boundExternalNode != widgetFocusNode) {
|
|
_bindFocusNode();
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_focusNodeBinding.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
void _bindFocusNode() {
|
|
_boundExternalNode = widgetFocusNode;
|
|
_focusNodeBinding.bind(externalNode: widgetFocusNode, listener: _onFocusChange);
|
|
}
|
|
|
|
void _onFocusChange() {
|
|
if (effectiveFocusNode.hasFocus) {
|
|
scrollContextToCenter(context);
|
|
}
|
|
}
|
|
}
|