LCOV - code coverage report
Current view: top level - _classes/controller - focus_controller.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 28 67 41.8 %
Date: 2024-10-04 11:08:31 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:flutter/material.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           1 :   FocusController();
      24             : 
      25           0 :   ScrollController get controller {
      26           0 :     order.clear();
      27           0 :     values.clear();
      28           0 :     return _controller;
      29             :   }
      30             : 
      31           2 :   int key(dynamic item) => item?.hashCode ?? 0;
      32             : 
      33           0 :   FocusNode last(dynamic item) {
      34           0 :     idButton = key(item);
      35           0 :     scope[idButton] ??= FocusNode();
      36           0 :     return scope[idButton]!;
      37             :   }
      38             : 
      39           1 :   FocusNode bind(dynamic item, {required BuildContext context, dynamic value}) {
      40           1 :     final id = key(item);
      41           2 :     if (!order.containsValue(id)) {
      42           4 :       order[order.length] = id;
      43             :     }
      44           3 :     scope[id] ??= FocusNode();
      45           2 :     values[id] = value;
      46             :     if (!force) {
      47           3 :       _focus.run(onEditingComplete);
      48             :     }
      49           2 :     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           6 :   bool _isLast(int? idx) => idx == order[order.lastKey()];
      66             : 
      67           3 :   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           1 :   void onEditingComplete([dynamic item]) {
      88           4 :     int? targetKey = item == null ? focus ?? order.values.firstOrNull : null;
      89             :     int? idx;
      90             :     force = false;
      91             :     if (item != null) {
      92           1 :       idx = key(item);
      93           2 :       values[idx] = true;
      94             :     }
      95           3 :     List<int?> check = order.values.toList();
      96           1 :     if (idButton != null) {
      97           0 :       values[idButton] = null;
      98           0 :       check.add(idButton);
      99             :     }
     100           2 :     for (int? key in check) {
     101           6 :       if (targetKey != null && key != targetKey && (values[key] == '' || values[key] == null)) {
     102           1 :         onFocus(null, key, false);
     103             :         targetKey = null;
     104             :       }
     105           1 :       if (key == idx) {
     106             :         targetKey = key;
     107             :       }
     108             :     }
     109             :   }
     110             : 
     111           4 :   bool isFocused(dynamic item) => key(item) == focus;
     112             : 
     113           0 :   void blur() => scope.forEach((_, value) => value.unfocus());
     114             : 
     115           1 :   void onFocus(dynamic item, [int? idx, bool isForced = true]) {
     116           0 :     idx ??= key(item);
     117           1 :     focus = idx;
     118           2 :     if ((order.containsValue(idx) || idx == idButton) &&
     119           2 :         scope[idx] != null &&
     120           3 :         scope[idx]!.context != null &&
     121           0 :         scope[idx]!.context!.mounted) {
     122           0 :       blur();
     123           0 :       scope[idx]?.requestFocus();
     124             :       if (item != null) {
     125           0 :         _scrollTo.run(() => scrollToFocusedElement(item, idx));
     126             :       }
     127             :     }
     128             :     force = isForced;
     129             :   }
     130             : 
     131           0 :   void dispose() {
     132           0 :     scope.forEach((_, node) => node.dispose());
     133           0 :     scope.clear();
     134           0 :     values.clear();
     135           0 :     order.clear();
     136           0 :     _controller.dispose();
     137           0 :     focus = null;
     138             :   }
     139             : }

Generated by: LCOV version 1.14