LCOV - code coverage report
Current view: top level - lib/design/form - list_selector_page.dart Coverage Total Hit
Test: lcov.info Lines: 92.0 % 87 80
Test Date: 2026-11-14 15:08:11 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 'package:app_finance/_classes/herald/app_design.dart';
       5              : import 'package:app_finance/_classes/herald/app_locale.dart';
       6              : import 'package:app_finance/_configs/design_type.dart';
       7              : import 'package:app_finance/design/form/list_selector_item.dart';
       8              : import 'package:app_finance/_configs/theme_helper.dart';
       9              : import 'package:app_finance/_ext/build_context_ext.dart';
      10              : import 'package:app_finance/_ext/color_ext.dart';
      11              : import 'package:app_finance/design/form/simple_input.dart';
      12              : import 'package:material_ui/material_ui.dart';
      13              : import 'package:flutter_grid_layout/flutter_grid_layout.dart';
      14              : import 'package:provider/provider.dart';
      15              : 
      16              : typedef FntSelectorCallback = Widget Function(
      17              :   List<ListSelectorItem> options,
      18              :   NavigatorState nav,
      19              : );
      20              : 
      21              : class ListSelectorPage<T extends Object?> extends StatefulWidget {
      22              :   final T? result;
      23              :   final List<ListSelectorItem> options;
      24              :   final String tooltip;
      25              :   final FntSelectorCallback? itemBuilder;
      26              :   final void Function(T?)? onTap;
      27              : 
      28            1 :   const ListSelectorPage({
      29              :     super.key,
      30              :     required this.options,
      31              :     required this.tooltip,
      32              :     this.result,
      33              :     this.itemBuilder,
      34              :     this.onTap,
      35              :   });
      36              : 
      37            1 :   @override
      38            1 :   State<StatefulWidget> createState() => ListSelectorPageState<T>();
      39              : }
      40              : 
      41              : class ListSelectorPageState<T extends Object?> extends State<ListSelectorPage<T>> {
      42              :   final controller = TextEditingController();
      43              :   late NavigatorState nav;
      44              :   dynamic result;
      45              :   List<ListSelectorItem> options = [];
      46              :   String value = '';
      47              : 
      48            1 :   @override
      49              :   void initState() {
      50            3 :     result = widget.result;
      51            3 :     options = widget.options;
      52           10 :     controller.addListener(() => controller.text.isEmpty && value.isEmpty ? () {} : setState(filter));
      53            1 :     super.initState();
      54              :   }
      55              : 
      56            1 :   @override
      57              :   void dispose() {
      58            2 :     controller.dispose();
      59            1 :     super.dispose();
      60              :   }
      61              : 
      62            1 :   void filter() {
      63            3 :     value = controller.text;
      64            8 :     options = widget.options.where((e) => e.match(value)).toList();
      65              :   }
      66              : 
      67            1 :   void onTap(T? item) {
      68            2 :     if (widget.onTap != null) {
      69            0 :       widget.onTap!(item);
      70              :     } else {
      71            2 :       nav.pop<T>(item);
      72              :     }
      73              :   }
      74              : 
      75            1 :   Widget itemBuilder(List<ListSelectorItem> options) =>
      76            2 :       widget.itemBuilder?.call(options, nav) ??
      77            1 :       ListView.builder(
      78            1 :         itemCount: options.length,
      79            1 :         itemBuilder: (BuildContext context, int index) {
      80            1 :           return ListTile(
      81            5 :             tileColor: index % 2 == 0 ? context.colorScheme.primary.withValues(alpha: 0.05) : null,
      82            3 :             hoverColor: context.colorScheme.primary.withValues(alpha: 0.15),
      83            2 :             title: options[index].suggest(context),
      84            3 :             onTap: () => onTap(options[index] as T),
      85              :           );
      86              :         },
      87              :       );
      88              : 
      89            1 :   @override
      90              :   Widget build(BuildContext context) {
      91            1 :     final design = Provider.of<AppDesign>(context, listen: false);
      92            2 :     if (design.value == AppDesignType.germany) {
      93            0 :       options.sort((a, b) => a.name.compareTo(b.name));
      94              :     }
      95            1 :     final indent = ThemeHelper.getIndent();
      96            2 :     nav = Navigator.of(context);
      97            1 :     return Directionality(
      98              :       textDirection: TextDirection.ltr,
      99            1 :       child: Scaffold(
     100            3 :         appBar: AppBar(backgroundColor: context.colorScheme.primary, toolbarHeight: 0),
     101            1 :         body: Column(
     102            1 :           crossAxisAlignment: AppDesign.getAlignment(),
     103            1 :           mainAxisAlignment: AppDesign.getAlignment<MainAxisAlignment>(),
     104            1 :           children: [
     105            1 :             Container(
     106            2 :               padding: EdgeInsets.all(ThemeHelper.getIndent()),
     107              :               width: double.infinity,
     108            1 :               decoration: BoxDecoration(
     109            2 :                 color: context.colorScheme.surface,
     110            1 :                 boxShadow: [
     111            1 :                   BoxShadow(
     112            3 :                     color: context.colorScheme.onSurface.withValues(alpha: 0.1),
     113              :                     offset: const Offset(0, 1),
     114              :                     blurRadius: 3,
     115              :                     spreadRadius: 1,
     116              :                   ),
     117            1 :                   BoxShadow(
     118            2 :                     color: context.colorScheme.surface,
     119              :                     offset: const Offset(0, 3),
     120              :                     blurRadius: 0,
     121              :                     spreadRadius: 0,
     122              :                   ),
     123              :                 ],
     124            5 :                 border: Border(bottom: BorderSide(width: 4, color: context.colorScheme.surface.withValues(alpha: 0.2))),
     125              :               ),
     126            1 :               child: Container(
     127            1 :                 decoration: BoxDecoration(
     128            2 :                   color: context.colorScheme.surface,
     129            4 :                   border: Border.all(color: context.colorScheme.onSurface.withValues(alpha: 0.1)),
     130            3 :                   borderRadius: BorderRadius.all(Radius.circular(indent / 2)),
     131              :                 ),
     132            1 :                 height: ThemeHelper.barHeight + indent,
     133              :                 width: double.infinity,
     134            2 :                 padding: EdgeInsets.all(indent / 2),
     135            1 :                 child: GridContainer(
     136            1 :                   alignment: AppDesign.getAlignment<MainAxisAlignment>(),
     137            3 :                   rows: [null, ThemeHelper.barHeight / 2, ThemeHelper.barHeight / 2, ThemeHelper.barHeight, indent],
     138              :                   // ignore: prefer_const_literals_to_create_immutables
     139            1 :                   columns: [ThemeHelper.barHeight],
     140            1 :                   children: [
     141            1 :                     GridItem(
     142              :                       start: const Size(0, 0),
     143              :                       end: const Size(2, 1),
     144            1 :                       child: SimpleInput(
     145            1 :                         controller: controller,
     146            2 :                         tooltip: widget.tooltip,
     147              :                         withLabel: true,
     148              :                         forceFocus: true,
     149            0 :                         onFieldSubmitted: (String value) =>
     150            0 :                             onTap(widget.options.where((e) => e.match(value)).firstOrNull as T?),
     151              :                       ),
     152              :                     ),
     153            1 :                     GridItem(
     154              :                       start: const Size(1, 0),
     155              :                       end: const Size(3, 1),
     156            1 :                       child: IconButton(
     157            2 :                         tooltip: AppLocale.labels.clear,
     158            1 :                         style: ButtonStyle(
     159            1 :                           backgroundColor: WidgetStateProperty.all<Color>(
     160            6 :                             context.colorScheme.surface.mesh(context.colorScheme.primary.withValues(alpha: 1), 0.1),
     161              :                           ),
     162              :                         ),
     163              :                         icon: const Icon(Icons.clear),
     164            0 :                         onPressed: () => onTap(null),
     165              :                       ),
     166              :                     ),
     167            1 :                     GridItem(
     168              :                       start: const Size(3, 0),
     169              :                       end: const Size(4, 1),
     170            1 :                       child: IconButton(
     171            2 :                         tooltip: AppLocale.labels.returnBack,
     172              :                         icon: const Icon(Icons.rotate_left_rounded),
     173            0 :                         onPressed: () => onTap(result as T?),
     174              :                       ),
     175              :                     ),
     176              :                   ],
     177              :                 ),
     178              :               ),
     179              :             ),
     180            1 :             if (result != null)
     181            1 :               ListTile(
     182            3 :                 tileColor: context.colorScheme.primary.withValues(alpha: 0.15),
     183            3 :                 hoverColor: context.colorScheme.primary.withValues(alpha: 0.20),
     184            8 :                 title: widget.options.where((e) => e.equal(result)).firstOrNull?.build(context) ?? ThemeHelper.emptyBox,
     185            0 :                 onTap: () => onTap(result as T?),
     186              :               ),
     187            5 :             Expanded(child: Padding(padding: EdgeInsets.all(indent), child: itemBuilder(options))),
     188              :           ],
     189              :         ),
     190              :       ),
     191              :     );
     192              :   }
     193              : }
        

Generated by: LCOV version 2.0-1