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