Skip to content

Commit 8296962

Browse files
committed
feat: async blue forms dependency data
1 parent 9b0fef7 commit 8296962

10 files changed

+238
-49
lines changed

lib/async/async_content_block.dart

+33-19
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,18 @@ class AsyncContentBlock<T> extends StatelessWidget {
1414
final kAsyncContentBlockType type;
1515
/// {@macro AsyncDataLoader.dataKey}
1616
final String? dataKey;
17+
final EdgeInsets internalWidgetsPadding;
1718
final AsyncDataLoaderCallback<T> onLoad;
18-
final Widget? noDataKeyWidget;
19+
final Widget? noDataRequestedWidget;
20+
final void Function(kAsyncDataState state)? onStateChange;
1921
final AsyncContentBuilder<T> builder;
2022

2123
const AsyncContentBlock({
2224
super.key,
2325
this.type = kAsyncContentBlockType.smallBrick,
24-
this.noDataKeyWidget,
26+
this.noDataRequestedWidget,
27+
this.onStateChange,
28+
this.internalWidgetsPadding = EdgeInsets.zero,
2529
required this.dataKey,
2630
required this.onLoad,
2731
required this.builder,
@@ -33,20 +37,24 @@ class AsyncContentBlock<T> extends StatelessWidget {
3337
return AsyncDataLoader<T>(
3438
dataKey: dataKey,
3539
onLoad: onLoad,
40+
onStateChange: onStateChange,
3641
builder: (context, data, errorMessage, isLoading, retry)
3742
{
3843
if (isLoading)
3944
{
4045
return Center(
41-
child: ArchLoadingIndicator(
42-
size: type == kAsyncContentBlockType.smallBrick ? kSize3.S : kSize3.M,
46+
child: Padding(
47+
padding: internalWidgetsPadding,
48+
child: ArchLoadingIndicator(
49+
size: type == kAsyncContentBlockType.smallBrick ? kSize3.S : kSize3.M,
50+
),
4351
)
4452
);
4553
}
4654

4755
if (dataKey == null)
4856
{
49-
return noDataKeyWidget ?? EmptyWidget();
57+
return noDataRequestedWidget ?? EmptyWidget();
5058
}
5159

5260
if (errorMessage != null)
@@ -69,13 +77,16 @@ class AsyncContentBlock<T> extends StatelessWidget {
6977
{
7078
return Center(
7179
child: Padding(
72-
padding: type == kAsyncContentBlockType.hugeSection ? context.paddingXXL : EdgeInsets.zero,
73-
child: ArchInfoBox.error(
74-
variant: type == kAsyncContentBlockType.hugeSection ? kInfoBoxVariant.contentPlaceholder : kInfoBoxVariant.smallBrick,
75-
icon: Symbols.warning_rounded,
76-
title: LibStrings.lib_general_titleError.tr(),
77-
message: errorMessage,
78-
onAction: retry,
80+
padding: internalWidgetsPadding,
81+
child: Padding(
82+
padding: type == kAsyncContentBlockType.hugeSection ? context.paddingXXL : EdgeInsets.zero,
83+
child: ArchInfoBox.error(
84+
variant: type == kAsyncContentBlockType.hugeSection ? kInfoBoxVariant.contentPlaceholder : kInfoBoxVariant.smallBrick,
85+
icon: Symbols.warning_rounded,
86+
title: LibStrings.lib_general_titleError.tr(),
87+
message: errorMessage,
88+
onAction: retry,
89+
),
7990
),
8091
)
8192
);
@@ -85,13 +96,16 @@ class AsyncContentBlock<T> extends StatelessWidget {
8596
{
8697
return Center(
8798
child: Padding(
88-
padding: type == kAsyncContentBlockType.hugeSection ? context.paddingXXL : EdgeInsets.zero,
89-
child: ArchInfoBox.info(
90-
variant: type == kAsyncContentBlockType.hugeSection ? kInfoBoxVariant.contentPlaceholder : kInfoBoxVariant.smallBrick,
91-
icon: Symbols.info_rounded,
92-
title: LibStrings.lib_warning_resourceNotFound_title.tr(),
93-
message: LibStrings.lib_warning_resourceNotFound_message.tr(),
94-
onAction: retry,
99+
padding: internalWidgetsPadding,
100+
child: Padding(
101+
padding: type == kAsyncContentBlockType.hugeSection ? context.paddingXXL : EdgeInsets.zero,
102+
child: ArchInfoBox.info(
103+
variant: type == kAsyncContentBlockType.hugeSection ? kInfoBoxVariant.contentPlaceholder : kInfoBoxVariant.smallBrick,
104+
icon: Symbols.info_rounded,
105+
title: LibStrings.lib_warning_resourceNotFound_title.tr(),
106+
message: LibStrings.lib_warning_resourceNotFound_message.tr(),
107+
onAction: retry,
108+
),
95109
),
96110
)
97111
);

lib/async/async_data_loader.dart

+50-2
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,24 @@ typedef AsyncDataLoaderCallback<T> = Future<T?> Function(String dataKey);
44

55
typedef AsyncDataLoaderBuilder<T> = Widget Function(BuildContext context, T? data, String? errorMessage, bool isLoading, void Function() retry);
66

7+
// ignore: camel_case_types
8+
enum kAsyncDataState
9+
{
10+
noDataRequested,
11+
loading,
12+
loadedHasData,
13+
loadedNoData,
14+
error
15+
}
16+
17+
718
class AsyncDataLoader<T> extends StatefulWidget {
819

920
/// {@template AsyncDataLoader.dataKey}
1021
///
11-
/// To start loading data, the key must be present. If the key changes, new
12-
/// data will be loaded. If the key is null, no data will be loaded.
22+
/// To start loading data (requesting data), the key must be present. If the
23+
/// key changes, new data will be loaded. If the key is null, no data will be
24+
/// loaded.
1325
/// The data will be cached in case the key changes back to a already loaded
1426
/// key.
1527
/// The data will be cached until the widget is disposed.
@@ -18,12 +30,14 @@ class AsyncDataLoader<T> extends StatefulWidget {
1830
final String? dataKey;
1931
final AsyncDataLoaderCallback<T> onLoad;
2032
final AsyncDataLoaderBuilder<T> builder;
33+
final void Function(kAsyncDataState state)? onStateChange;
2134

2235
const AsyncDataLoader({
2336
super.key,
2437
required this.dataKey,
2538
required this.onLoad,
2639
required this.builder,
40+
this.onStateChange,
2741
});
2842

2943
@override
@@ -47,6 +61,7 @@ class _AsyncDataLoaderState<T> extends State<AsyncDataLoader<T>> {
4761

4862
final Map<String, _AsyncDataLoadingEntry<T>> _dataCache = {};
4963
bool _isLoading = false;
64+
kAsyncDataState _state = kAsyncDataState.noDataRequested;
5065

5166
@override
5267
void initState()
@@ -61,6 +76,22 @@ class _AsyncDataLoaderState<T> extends State<AsyncDataLoader<T>> {
6176
}
6277
}
6378

79+
void _setDataState(kAsyncDataState state)
80+
{
81+
if (_state == state)
82+
{
83+
return;
84+
}
85+
86+
_state = state;
87+
88+
if (widget.onStateChange != null)
89+
{
90+
widget.onStateChange!(_state);
91+
}
92+
}
93+
94+
6495
@override
6596
void didUpdateWidget(AsyncDataLoader<T> oldWidget)
6697
{
@@ -73,6 +104,11 @@ class _AsyncDataLoaderState<T> extends State<AsyncDataLoader<T>> {
73104
if (oldWidget.dataKey != null)
74105
{
75106
_getOrCreateEntry(oldWidget.dataKey!).canceled = true;
107+
108+
if (widget.dataKey == null)
109+
{
110+
_setDataState(kAsyncDataState.noDataRequested);
111+
}
76112
}
77113

78114
if (widget.dataKey != null)
@@ -86,6 +122,7 @@ class _AsyncDataLoaderState<T> extends State<AsyncDataLoader<T>> {
86122
{
87123
if (_dataCache.containsKey(key) && !forceReload && _dataCache[key]!.data != null)
88124
{
125+
_setDataState(kAsyncDataState.loadedHasData);
89126
return;
90127
}
91128

@@ -96,6 +133,7 @@ class _AsyncDataLoaderState<T> extends State<AsyncDataLoader<T>> {
96133
entry.data = null;
97134
entry.errorMessage = null;
98135

136+
_setDataState(kAsyncDataState.loading);
99137
setState(() {
100138
_isLoading = true;
101139
});
@@ -110,6 +148,15 @@ class _AsyncDataLoaderState<T> extends State<AsyncDataLoader<T>> {
110148

111149
entry.data = data;
112150

151+
if (data == null)
152+
{
153+
_setDataState(kAsyncDataState.loadedNoData);
154+
}
155+
else
156+
{
157+
_setDataState(kAsyncDataState.loadedHasData);
158+
}
159+
113160
if (mounted)
114161
{
115162
setState(()
@@ -127,6 +174,7 @@ class _AsyncDataLoaderState<T> extends State<AsyncDataLoader<T>> {
127174
}
128175

129176
entry.errorMessage = ExceptionTool.toUserFriendlyMessage(e);
177+
_setDataState(kAsyncDataState.error);
130178

131179
if (mounted)
132180
{

lib/blue_forms/models/elements/form_async_dependency.dart

+14-11
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,25 @@
22

33
part of devspace;
44

5-
// TODO: implement
5+
typedef FormAsyncDependencyBuilder<T> = FormElement Function(T data);
66

7-
class FormAsyncDependency extends FormInput {
7+
class FormAsyncDependency<T> extends FormInput {
88

9-
final List<FormsInputPickOptionItem> options;
10-
final void Function(String? newValue)? onChange;
9+
/// {@macro AsyncDataLoader.dataKey}
10+
final String? dataKey;
11+
final AsyncDataLoaderCallback<T> onLoad;
12+
final Widget? noDataRequestedWidget;
13+
final FormAsyncDependencyBuilder<T> builder;
14+
final bool preventSubmitWithoutData;
1115

1216
const FormAsyncDependency({
13-
required super.id,
14-
super.initialValue,
15-
super.description,
16-
super.label,
17+
super.id = 'NONE',
1718
super.isActive,
18-
required this.options,
19-
super.isOptional,
20-
this.onChange,
19+
required this.dataKey,
20+
required this.onLoad,
21+
this.noDataRequestedWidget,
22+
this.preventSubmitWithoutData = true,
23+
required this.builder,
2124
});
2225

2326
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
2+
3+
part of devspace;
4+
5+
6+
7+
8+
class _FormAsyncDependency extends material.StatefulWidget {
9+
10+
final FormAsyncDependency definition;
11+
final Color? labelColor;
12+
final bool isFirstElement;
13+
final bool isLastElement;
14+
final bool visuallyMarkRequired;
15+
final Map<String, dynamic> currentSavedValues;
16+
final Map<String, String> externalErrors;
17+
final void Function(String id, dynamic value) onSave;
18+
final VoidCallback onSubmitRequested;
19+
20+
const _FormAsyncDependency({
21+
// ignore: unused_element
22+
super.key,
23+
required this.definition,
24+
this.labelColor,
25+
required this.isFirstElement,
26+
required this.isLastElement,
27+
required this.visuallyMarkRequired,
28+
required this.currentSavedValues,
29+
required this.externalErrors,
30+
required this.onSave,
31+
required this.onSubmitRequested
32+
});
33+
34+
@override
35+
material.State<_FormAsyncDependency> createState() => _FormAsyncDependencyState();
36+
}
37+
38+
class _FormAsyncDependencyState extends material.State<_FormAsyncDependency> {
39+
40+
41+
String? lastDataKey;
42+
Key key = UniqueKey();
43+
44+
@override
45+
void initState()
46+
{
47+
super.initState();
48+
49+
lastDataKey = widget.definition.dataKey;
50+
}
51+
52+
@override
53+
Widget build(BuildContext context)
54+
{
55+
if (widget.definition.isActive == false)
56+
{
57+
return EmptyWidget();
58+
}
59+
60+
if (widget.definition.dataKey != lastDataKey)
61+
{
62+
key = UniqueKey();
63+
lastDataKey = widget.definition.dataKey;
64+
}
65+
66+
return AsyncContentBlock(
67+
dataKey: widget.definition.dataKey,
68+
onLoad: widget.definition.onLoad,
69+
internalWidgetsPadding: context.paddingL,
70+
builder: (context, data, retry)
71+
{
72+
FormElement formElement = widget.definition.builder(data);
73+
74+
return _FormElementWidget(
75+
key: key,
76+
definition: formElement,
77+
labelColor: widget.labelColor,
78+
isFirstElement: widget.isFirstElement,
79+
isLastElement: widget.isLastElement,
80+
visuallyMarkRequired: widget.visuallyMarkRequired,
81+
currentSavedValues: widget.currentSavedValues,
82+
externalErrors: widget.externalErrors,
83+
onSave: widget.onSave,
84+
onSubmitRequested: widget.onSubmitRequested
85+
);
86+
}
87+
);
88+
}
89+
}

lib/blue_forms/widgets/form_element.dart

+15
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ class _FormElementWidget extends StatelessWidget {
8282
);
8383
}
8484

85+
if (definition is FormAsyncDependency)
86+
{
87+
return _FormAsyncDependency(
88+
definition: definition as FormAsyncDependency,
89+
labelColor: labelColor,
90+
isFirstElement: isFirstElement,
91+
isLastElement: isLastElement,
92+
visuallyMarkRequired: visuallyMarkRequired,
93+
currentSavedValues: currentSavedValues,
94+
externalErrors: externalErrors,
95+
onSave: onSave,
96+
onSubmitRequested: onSubmitRequested
97+
);
98+
}
99+
85100
if (definition is FormGroup)
86101
{
87102
return _FormGroup(

lib/blue_forms/widgets/form_page.dart

+2
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class _FormPageWidgetState extends State<_FormPageWidget> {
8484

8585
bool valid = _formKey.currentState!.validate();
8686

87+
// TODO: check if all async dependencies are resolved
88+
8789
if (valid)
8890
{
8991
_formKey.currentState!.save();

0 commit comments

Comments
 (0)