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 0 : 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 0 : @override
38 0 : 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 0 : @override
49 : void initState() {
50 0 : result = widget.result;
51 0 : options = widget.options;
52 0 : controller.addListener(() => controller.text.isEmpty && value.isEmpty ? () {} : setState(filter));
53 0 : super.initState();
54 : }
55 :
56 0 : @override
57 : void dispose() {
58 0 : controller.dispose();
59 0 : super.dispose();
60 : }
61 :
62 0 : void filter() {
63 0 : value = controller.text;
64 0 : options = widget.options.where((e) => e.match(value)).toList();
65 : }
66 :
67 0 : void onTap(T? item) {
68 0 : if (widget.onTap != null) {
69 0 : widget.onTap!(item);
70 : } else {
71 0 : nav.pop<T>(item);
72 : }
73 : }
74 :
75 0 : Widget itemBuilder(List<ListSelectorItem> options) =>
76 0 : widget.itemBuilder?.call(options, nav) ??
77 0 : ListView.builder(
78 0 : itemCount: options.length,
79 0 : itemBuilder: (BuildContext context, int index) {
80 0 : return ListTile(
81 0 : tileColor: index % 2 == 0 ? context.colorScheme.primary.withValues(alpha: 0.05) : null,
82 0 : hoverColor: context.colorScheme.primary.withValues(alpha: 0.15),
83 0 : title: options[index].suggest(context),
84 0 : onTap: () => onTap(options[index] as T),
85 : );
86 : },
87 : );
88 :
89 0 : @override
90 : Widget build(BuildContext context) {
91 0 : final design = Provider.of<AppDesign>(context, listen: false);
92 0 : if (design.value == AppDesignType.germany) {
93 0 : options.sort((a, b) => a.name.compareTo(b.name));
94 : }
95 0 : final indent = ThemeHelper.getIndent();
96 0 : nav = Navigator.of(context);
97 0 : return Directionality(
98 : textDirection: TextDirection.ltr,
99 0 : child: Scaffold(
100 0 : appBar: AppBar(backgroundColor: context.colorScheme.primary, toolbarHeight: 0),
101 0 : body: Column(
102 0 : crossAxisAlignment: AppDesign.getAlignment(),
103 0 : mainAxisAlignment: AppDesign.getAlignment<MainAxisAlignment>(),
104 0 : children: [
105 0 : Container(
106 0 : padding: EdgeInsets.all(ThemeHelper.getIndent()),
107 : width: double.infinity,
108 0 : decoration: BoxDecoration(
109 0 : color: context.colorScheme.surface,
110 0 : boxShadow: [
111 0 : BoxShadow(
112 0 : color: context.colorScheme.onSurface.withValues(alpha: 0.1),
113 : offset: const Offset(0, 1),
114 : blurRadius: 3,
115 : spreadRadius: 1,
116 : ),
117 0 : BoxShadow(
118 0 : color: context.colorScheme.surface,
119 : offset: const Offset(0, 3),
120 : blurRadius: 0,
121 : spreadRadius: 0,
122 : ),
123 : ],
124 0 : border: Border(bottom: BorderSide(width: 4, color: context.colorScheme.surface.withValues(alpha: 0.2))),
125 : ),
126 0 : child: Container(
127 0 : decoration: BoxDecoration(
128 0 : color: context.colorScheme.surface,
129 0 : border: Border.all(color: context.colorScheme.onSurface.withValues(alpha: 0.1)),
130 0 : borderRadius: BorderRadius.all(Radius.circular(indent / 2)),
131 : ),
132 0 : height: ThemeHelper.barHeight + indent,
133 : width: double.infinity,
134 0 : padding: EdgeInsets.all(indent / 2),
135 0 : child: GridContainer(
136 0 : alignment: AppDesign.getAlignment<MainAxisAlignment>(),
137 0 : rows: [null, ThemeHelper.barHeight / 2, ThemeHelper.barHeight / 2, ThemeHelper.barHeight, indent],
138 : // ignore: prefer_const_literals_to_create_immutables
139 0 : columns: [ThemeHelper.barHeight],
140 0 : children: [
141 0 : GridItem(
142 : start: const Size(0, 0),
143 : end: const Size(2, 1),
144 0 : child: SimpleInput(
145 0 : controller: controller,
146 0 : 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 0 : GridItem(
154 : start: const Size(1, 0),
155 : end: const Size(3, 1),
156 0 : child: IconButton(
157 0 : tooltip: AppLocale.labels.clear,
158 0 : style: ButtonStyle(
159 0 : backgroundColor: WidgetStateProperty.all<Color>(
160 0 : 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 0 : GridItem(
168 : start: const Size(3, 0),
169 : end: const Size(4, 1),
170 0 : child: IconButton(
171 0 : 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 0 : if (result != null)
181 0 : ListTile(
182 0 : tileColor: context.colorScheme.primary.withValues(alpha: 0.15),
183 0 : hoverColor: context.colorScheme.primary.withValues(alpha: 0.20),
184 0 : title: widget.options.where((e) => e.equal(result)).firstOrNull?.build(context) ?? ThemeHelper.emptyBox,
185 0 : onTap: () => onTap(result as T?),
186 : ),
187 0 : Expanded(child: Padding(padding: EdgeInsets.all(indent), child: itemBuilder(options))),
188 : ],
189 : ),
190 : ),
191 : );
192 : }
193 : }
|