1using System.Collections.ObjectModel;
2using System.Diagnostics;
3using System.Diagnostics.CodeAnalysis;
4using CommunityToolkit.Mvvm.ComponentModel;
40 private WeakReference<KycProcess>? ownerProcess;
41 private readonly List<IKycRule> rules =
new();
42 public ReadOnlyCollection<IKycRule> ValidationRules => this.rules.AsReadOnly();
46 private bool hasAsyncRules;
50 private static readonly TimeSpan SyncValidationDebounce = TimeSpan.FromMilliseconds(500);
54 this.Metadata =
new Dictionary<string, object?>(StringComparer.OrdinalIgnoreCase);
56 this.SelectedOptions.CollectionChanged += this.SelectedOptions_CollectionChanged;
59 .Named(
"KYC Field Validation (Async Rules)")
60 .WithPolicy(
Policies.Debounce(TimeSpan.FromMilliseconds(500)))
61 .Run(this.ValidateAsync)
67 .Named(
"KYC Field Sync Validation")
68 .WithPolicy(
Policies.Debounce(SyncValidationDebounce))
72 await MainThread.InvokeOnMainThreadAsync(() =>
74 this.RunSynchronousValidation();
75 if (this.hasAsyncRules)
76 this.ValidationTask.
Run();
87 public string Id {
get;
set; } =
string.Empty;
132 public ObservableCollection<KycOption>
Options {
get; } =
new();
137 public ObservableCollection<KycMapping>
Mappings {
get; } =
new();
144 private void SelectedOptions_CollectionChanged(
object? sender,
System.
Collections.Specialized.NotifyCollectionChangedEventArgs e)
156 public Dictionary<string, object?>
Metadata {
get; }
158 private object? rawValue;
159 public object? RawValue
161 get => this.rawValue;
164 if (this.SetProperty(ref this.rawValue, value))
169 this.IsValid =
false;
170 string Label = this.Label?.
Get(
null) ?? this.
Id;
171 this.ValidationText =
string.IsNullOrEmpty(
Label) ?
"Required" : $
"{Label} is required";
175 this.syncValidationTask.Run();
180 private bool isVisible =
true;
181 public bool IsVisible
183 get => this.isVisible;
184 set => this.SetProperty(ref this.isVisible, value);
187 private bool isValid =
true;
191 set => this.SetProperty(ref this.isValid, value);
194 private bool isValidating;
195 public bool IsValidating
197 get => this.isValidating;
198 set => this.SetProperty(ref this.isValidating, value);
201 private string? validationText;
202 public string? ValidationText
204 get => this.validationText;
205 set => this.SetProperty(ref this.validationText, value);
219 this.rules.Add(Rule);
221 this.hasAsyncRules =
true;
231 this.syncValidationTask.Cancel();
232 this.RunSynchronousValidation();
233 if (this.hasAsyncRules)
234 this.ValidationTask.
Run();
242 private void RunSynchronousValidation()
244 this.TryGetOwnerProcess(out
KycProcess? process);
246 foreach (
IKycRule rule
in this.rules)
251 if (!rule.
Validate(
this, process, out
string error))
253 this.IsValid =
false;
254 this.ValidationText = error;
260 this.ValidationText =
null;
263 protected virtual async Task<bool> ValidateAsync(TaskContext<int> context)
268 this.TryGetOwnerProcess(out
KycProcess? process);
269 bool asyncFailed =
false;
270 string? asyncError =
null;
274 await MainThread.InvokeOnMainThreadAsync(() => this.IsValidating =
true);
276 foreach (
IKycRule rule
in this.rules)
280 (
bool Ok,
string? Error) result = await asyncRule.ValidateAsync(
this, process);
284 asyncError = result.Error;
296 await MainThread.InvokeOnMainThreadAsync(() => this.IsValidating =
false);
301 await MainThread.InvokeOnMainThreadAsync(() =>
303 this.IsValid =
false;
304 this.ValidationText = asyncError;
309 await MainThread.InvokeOnMainThreadAsync(() =>
312 this.ValidationText =
null;
324 internal void SetOwnerProcess(
KycProcess Process) => this.ownerProcess =
new WeakReference<KycProcess>(Process);
326 public bool TryGetOwnerProcess([MaybeNullWhen(
false), NotNullWhen(
true)] out
KycProcess? Process)
328 if (this.ownerProcess is not
null && this.ownerProcess.TryGetTarget(out
KycProcess? p))
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.
Ensures the field has a value if required.
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.
KycLocalizedText? Label
Field label.
abstract ? string StringValue
Gets or sets the value as a string, parsing or formatting as needed for the field type.
bool Required
Whether the field is required.
KycLocalizedText? Description
Field description.
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.
static ILogService LogService
Log service.
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.
Contract for synchronous KYC field validation rules.
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.