Line data Source code
1 : // Copyright 2023 The terCAD team. All rights reserved.
2 : // Use of this source code is governed by a CC BY-NC-ND 4.0 license that can be found in the LICENSE file.
3 :
4 : import 'dart:collection';
5 : import 'dart:math';
6 :
7 : import 'package:app_finance/_classes/controller/delayed_call.dart';
8 : import 'package:app_finance/_configs/theme_helper.dart';
9 : import 'package:material_ui/material_ui.dart';
10 :
11 : class FocusController {
12 : final DelayedCall _scrollTo = DelayedCall(300);
13 : final DelayedCall _focus = DelayedCall(300);
14 : final _controller = ScrollController();
15 :
16 : final scope = <int?, FocusNode>{};
17 : final values = <int?, dynamic>{};
18 : final order = SplayTreeMap<int, int?>();
19 : int? focus;
20 : int? idButton;
21 : static bool force = false;
22 :
23 5 : FocusController();
24 :
25 3 : ScrollController get controller {
26 6 : order.clear();
27 6 : values.clear();
28 3 : return _controller;
29 : }
30 :
31 8 : int key(dynamic item) => item?.hashCode ?? 0;
32 :
33 2 : FocusNode last(dynamic item) {
34 4 : idButton = key(item);
35 8 : scope[idButton] ??= FocusNode();
36 6 : return scope[idButton]!;
37 : }
38 :
39 4 : FocusNode bind(dynamic item, {required BuildContext context, dynamic value}) {
40 4 : final id = key(item);
41 8 : if (!order.containsValue(id)) {
42 16 : order[order.length] = id;
43 : }
44 12 : scope[id] ??= FocusNode();
45 8 : values[id] = value;
46 : if (!force) {
47 12 : _focus.run(onEditingComplete);
48 : }
49 8 : return scope[id]!;
50 : }
51 :
52 0 : bool _isControllerActive() => _controller.hasClients;
53 :
54 0 : double _getPosition(BuildContext context) {
55 0 : RenderObject? obj = context.findRenderObject();
56 0 : return obj is RenderBox ? obj.localToGlobal(Offset.zero).dy : 0;
57 : }
58 :
59 0 : double _getMinPosition() => order.values
60 0 : .map((id) => scope[id]?.context)
61 0 : .where((element) => element != null)
62 0 : .map((e) => _getPosition(e!))
63 0 : .reduce(min);
64 :
65 12 : bool _isLast(int? idx) => idx == order[order.lastKey()];
66 :
67 6 : TextInputAction getAction(dynamic item) => _isLast(key(item)) ? TextInputAction.done : TextInputAction.next;
68 :
69 0 : void scrollToFocusedElement(dynamic item, [int? idx]) {
70 0 : idx ??= key(item!);
71 0 : if (!_isControllerActive() || scope.isEmpty) {
72 : return;
73 : }
74 0 : if (order.containsValue(idx) && scope[idx] != null && scope[idx]!.context != null) {
75 0 : double position = _getPosition(scope[idx]!.context!) - _getMinPosition() - ThemeHelper.barHeight;
76 0 : if (position < 0) {
77 : position = 0;
78 : }
79 0 : _controller.animateTo(
80 : position,
81 : duration: const Duration(milliseconds: 600),
82 : curve: Curves.easeInOut,
83 : );
84 : }
85 : }
86 :
87 4 : void onEditingComplete([dynamic item]) {
88 16 : int? targetKey = item == null ? focus ?? order.values.firstOrNull : null;
89 : int? idx;
90 : force = false;
91 : if (item != null) {
92 0 : idx = key(item);
93 0 : values[idx] = true;
94 : }
95 12 : List<int?> check = order.values.toList();
96 4 : if (idButton != null) {
97 6 : values[idButton] = null;
98 4 : check.add(idButton);
99 : }
100 8 : for (int? key in check) {
101 14 : if (targetKey != null && key != targetKey && (values[key] == '' || values[key] == null)) {
102 2 : onFocus(null, key, false);
103 : targetKey = null;
104 : }
105 4 : if (key == idx) {
106 : targetKey = key;
107 : }
108 : }
109 : }
110 :
111 16 : bool isFocused(dynamic item) => key(item) == focus;
112 :
113 10 : void blur() => scope.forEach((_, value) => value.unfocus());
114 :
115 2 : void onFocus(dynamic item, [int? idx, bool isForced = true]) {
116 0 : idx ??= key(item);
117 2 : focus = idx;
118 4 : if ((order.containsValue(idx) || idx == idButton) &&
119 4 : scope[idx] != null &&
120 6 : scope[idx]!.context != null &&
121 8 : scope[idx]!.context!.mounted) {
122 2 : blur();
123 6 : scope[idx]?.requestFocus();
124 : if (item != null) {
125 0 : _scrollTo.run(() => scrollToFocusedElement(item, idx));
126 : }
127 : }
128 : force = isForced;
129 : }
130 :
131 3 : void dispose() {
132 10 : scope.forEach((_, node) => node.dispose());
133 6 : scope.clear();
134 6 : values.clear();
135 6 : order.clear();
136 6 : _controller.dispose();
137 3 : focus = null;
138 : }
139 : }
|