Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ObservableKycField.cs
1using System.Collections.ObjectModel;
2using System.Diagnostics;
3using System.Diagnostics.CodeAnalysis;
4using CommunityToolkit.Mvvm.ComponentModel;
9
11{
15 public enum FieldType
16 {
17 Text,
18 Date,
19 Boolean,
20 Picker,
21 File,
22 Image,
23 Phone,
24 Email,
25 Integer,
26 Decimal,
27 Country,
28 Checkbox,
29 Radio,
30 Gender,
31 Label, // non-input
32 Info // non-input
33 }
34
38 public abstract class ObservableKycField : ObservableObject
39 {
40 private WeakReference<KycProcess>? ownerProcess;
41 private readonly List<IKycRule> rules = new();
42 public ReadOnlyCollection<IKycRule> ValidationRules => this.rules.AsReadOnly();
43
44 public ObservableTask<int> ValidationTask { get; } = new();
45
46 private bool hasAsyncRules;
47
48 // Debounce support for synchronous validation implemented via ObservableTask (avoids manual CTS & CA1001 warning)
49 private readonly ObservableTask<int> syncValidationTask;
50 private static readonly TimeSpan SyncValidationDebounce = TimeSpan.FromMilliseconds(500);
51
52 public ObservableKycField()
53 {
54 this.Metadata = new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
55 this.rules.Add(new RequiredRule());
56 this.SelectedOptions.CollectionChanged += this.SelectedOptions_CollectionChanged;
57
58 this.ValidationTask = new ObservableTaskBuilder<int>()
59 .Named("KYC Field Validation (Async Rules)")
60 .WithPolicy(Policies.Debounce(TimeSpan.FromMilliseconds(500)))
61 .Run(this.ValidateAsync)
62 .AutoStart(false)
63 .UseTaskRun(true)
64 .Build();
65
66 this.syncValidationTask = new ObservableTaskBuilder<int>()
67 .Named("KYC Field Sync Validation")
68 .WithPolicy(Policies.Debounce(SyncValidationDebounce))
69 .Run(async ctx =>
70 {
71 // Run sync validation on UI thread
72 await MainThread.InvokeOnMainThreadAsync(() =>
73 {
74 this.RunSynchronousValidation();
75 if (this.hasAsyncRules)
76 this.ValidationTask.Run();
77 });
78 })
79 .AutoStart(false)
80 .UseTaskRun(false) // lightweight
81 .Build();
82 }
83
87 public string Id { get; set; } = string.Empty;
88
92 public FieldType FieldType { get; set; } = FieldType.Text;
93
97 public bool Required { get; set; }
98
102 public KycLocalizedText? Label { get; set; }
103
107 public KycLocalizedText? Placeholder { get; set; }
108
112 public KycLocalizedText? Hint { get; set; }
113
117 public KycLocalizedText? Description { get; set; }
118
122 public string? SpecialType { get; set; }
123
127 public KycCondition? Condition { get; set; }
128
132 public ObservableCollection<KycOption> Options { get; } = new();
133
137 public ObservableCollection<KycMapping> Mappings { get; } = new();
138
142 public ObservableCollection<KycOption> SelectedOptions { get; set; } = new();
143
144 private void SelectedOptions_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
145 {
146 if (this.FieldType == FieldType.Checkbox)
147 {
148 this.RawValue = this.SelectedOptions.ToList();
149 }
150 }
151
156 public Dictionary<string, object?> Metadata { get; }
157
158 private object? rawValue;
159 public object? RawValue
160 {
161 get => this.rawValue;
162 set
163 {
164 if (this.SetProperty(ref this.rawValue, value))
165 {
166 // QUICK required check so navigation buttons disable immediately when user clears content
167 if (this.Required && string.IsNullOrEmpty(this.StringValue))
168 {
169 this.IsValid = false;
170 string Label = this.Label?.Get(null) ?? this.Id;
171 this.ValidationText = string.IsNullOrEmpty(Label) ? "Required" : $"{Label} is required";
172 }
173
174 // Debounced synchronous validation + chaining async validation (if any)
175 this.syncValidationTask.Run();
176 }
177 }
178 }
179
180 private bool isVisible = true;
181 public bool IsVisible
182 {
183 get => this.isVisible;
184 set => this.SetProperty(ref this.isVisible, value);
185 }
186
187 private bool isValid = true;
188 public bool IsValid
189 {
190 get => this.isValid;
191 set => this.SetProperty(ref this.isValid, value);
192 }
193
194 private bool isValidating;
195 public bool IsValidating
196 {
197 get => this.isValidating;
198 set => this.SetProperty(ref this.isValidating, value);
199 }
200
201 private string? validationText;
202 public string? ValidationText
203 {
204 get => this.validationText;
205 set => this.SetProperty(ref this.validationText, value);
206 }
207
208 // Value helpers for different field types:
212 public abstract string? StringValue { get; set; }
213
217 public void AddRule(IKycRule Rule)
218 {
219 this.rules.Add(Rule);
220 if (Rule is IAsyncKycRule)
221 this.hasAsyncRules = true;
222 }
223
228 {
229 try
230 {
231 this.syncValidationTask.Cancel();
232 this.RunSynchronousValidation();
233 if (this.hasAsyncRules)
234 this.ValidationTask.Run();
235 }
236 catch (Exception Ex)
237 {
238 ServiceRef.LogService.LogException(Ex);
239 }
240 }
241
242 private void RunSynchronousValidation()
243 {
244 this.TryGetOwnerProcess(out KycProcess? process);
245
246 foreach (IKycRule rule in this.rules)
247 {
248 if (rule is IAsyncKycRule)
249 continue;
250
251 if (!rule.Validate(this, process, out string error))
252 {
253 this.IsValid = false;
254 this.ValidationText = error;
255 return;
256 }
257 }
258
259 this.IsValid = true;
260 this.ValidationText = null;
261 }
262
263 protected virtual async Task<bool> ValidateAsync(TaskContext<int> context)
264 {
265 if (!this.IsValid)
266 return false;
267
268 this.TryGetOwnerProcess(out KycProcess? process);
269 bool asyncFailed = false;
270 string? asyncError = null;
271
272 try
273 {
274 await MainThread.InvokeOnMainThreadAsync(() => this.IsValidating = true);
275
276 foreach (IKycRule rule in this.rules)
277 {
278 if (rule is IAsyncKycRule asyncRule)
279 {
280 (bool Ok, string? Error) result = await asyncRule.ValidateAsync(this, process);
281 if (!result.Ok)
282 {
283 asyncFailed = true;
284 asyncError = result.Error;
285 break;
286 }
287 }
288 }
289 }
290 catch (Exception ex)
291 {
292 ServiceRef.LogService.LogException(ex);
293 }
294 finally
295 {
296 await MainThread.InvokeOnMainThreadAsync(() => this.IsValidating = false);
297 }
298
299 if (asyncFailed)
300 {
301 await MainThread.InvokeOnMainThreadAsync(() =>
302 {
303 this.IsValid = false;
304 this.ValidationText = asyncError;
305 });
306 return false;
307 }
308
309 await MainThread.InvokeOnMainThreadAsync(() =>
310 {
311 if (this.IsValid)
312 this.ValidationText = null;
313 });
314 return this.IsValid;
315 }
316
320 public virtual void MapToApplyModel()
321 {
322 }
323
324 internal void SetOwnerProcess(KycProcess Process) => this.ownerProcess = new WeakReference<KycProcess>(Process);
325
326 public bool TryGetOwnerProcess([MaybeNullWhen(false), NotNullWhen(true)] out KycProcess? Process)
327 {
328 if (this.ownerProcess is not null && this.ownerProcess.TryGetTarget(out KycProcess? p))
329 {
330 Process = p;
331 return true;
332 }
333
334 Process = null;
335 return false;
336 }
337 }
338}
Represents a set of localized strings, addressable by language code (e.g. "en", "sv").
string? Get(string? Lang)
Gets a localized value for a specific language.
Represents a parsed KYC process with pages, fields, and current values.
Definition: KycProcess.cs:11
Ensures the field has a value if required.
Definition: KycRules.cs:48
The base KYC field model. Use as-is for generic fields, or subclass for custom logic.
string Id
The unique identifier for the field.
void AddRule(IKycRule Rule)
Add a validation rule to this field.
abstract ? string StringValue
Gets or sets the value as a string, parsing or formatting as needed for the field type.
virtual void MapToApplyModel()
Map the value of this field to your application viewmodel.
string? SpecialType
Optional special type for custom UI/logic.
ObservableCollection< KycOption > SelectedOptions
For Checkbox type: multi-selection of options.
KycLocalizedText? Hint
Help/hint text for the field.
ObservableCollection< KycMapping > Mappings
Mappings to apply output.
KycCondition? Condition
Conditional display of this field.
ObservableCollection< KycOption > Options
All possible options (for picker, country, checkbox, radio).
Dictionary< string, object?> Metadata
Arbitrary metadata for field-specific extensions. Use e.g. Metadata["TargetWidth"] or Metadata["MaxFi...
void ForceSynchronousValidation()
Forces immediate synchronous validation (used when advancing pages or explicit full validation is req...
KycLocalizedText? Placeholder
Placeholder text for the field input.
Base class that references services in the app.
Definition: ServiceRef.cs:43
static ILogService LogService
Log service.
Definition: ServiceRef.cs:214
Provides a data-binding friendly mechanism to manage and report the status of asynchronous operations...
void Run()
Start the configured task now (no CanExecute gating).
Contract for asynchronous KYC field validation rules.
Definition: KycRules.cs:33
Contract for synchronous KYC field validation rules.
Definition: KycRules.cs:17
bool Validate(ObservableKycField Field, KycProcess? process, out string Error, string? Lang=null)
Validates a field and returns a result.
FieldType
Enumeration of all supported field types in the KYC process.