LCOV - code coverage report
Current view: top level - lib/_classes/controller - focus_controller.dart Coverage Total Hit
Test: lcov.info Lines: 70.1 % 67 47
Test Date: 2026-11-14 15:04:37 Functions: - 0 0

            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              : }
        

Generated by: LCOV version 2.0-1