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