2using System.Collections.ObjectModel;
3using System.Globalization;
7using System.Threading.Tasks;
8using CommunityToolkit.Mvvm.ComponentModel;
9using CommunityToolkit.Mvvm.Input;
44 private bool disposedValue;
48 private int currentPageIndex = 0;
50 private bool applicationSent;
51 private string? applicationId;
55 private List<Property> mappedValues;
56 private List<LegalIdentityAttachment> attachments;
59 private bool editingFromSummary;
60 [ObservableProperty]
private bool isLoading;
63 [NotifyCanExecuteChangedFor(nameof(ScanQrCodeCommand))]
64 [NotifyCanExecuteChangedFor(nameof(RequestReviewCommand))]
65 [NotifyCanExecuteChangedFor(nameof(RevokeApplicationCommand))]
66 private bool applicationSentPublic;
68 [ObservableProperty]
private bool peerReview;
69 [ObservableProperty]
private int nrReviews;
70 [ObservableProperty]
private int nrReviewers;
72 [NotifyCanExecuteChangedFor(nameof(RequestReviewCommand))]
73 private bool hasFeaturedPeerReviewers;
75 [ObservableProperty]
private int currentPagePosition;
76 [ObservableProperty]
private KycPage? currentPage;
77 [ObservableProperty]
private string? currentPageTitle;
78 [ObservableProperty]
private string? currentPageDescription;
79 [ObservableProperty]
private bool hasCurrentPageDescription;
80 [ObservableProperty]
private ReadOnlyObservableCollection<KycSection>? currentPageSections;
81 [ObservableProperty]
private bool hasSections;
82 [ObservableProperty]
private string nextButtonText =
"Next";
84 [ObservableProperty]
private ObservableCollection<DisplayQuad> personalInformationSummary;
85 [ObservableProperty]
private ObservableCollection<DisplayQuad> addressInformationSummary;
86 [ObservableProperty]
private ObservableCollection<DisplayQuad> attachmentInformationSummary;
87 [ObservableProperty]
private ObservableCollection<DisplayQuad> companyInformationSummary;
88 [ObservableProperty]
private ObservableCollection<DisplayQuad> companyAddressSummary;
89 [ObservableProperty]
private ObservableCollection<DisplayQuad> companyRepresentativeSummary;
90 [ObservableProperty]
private ObservableCollection<string> invalidatedItems =
new ObservableCollection<string>();
91 [ObservableProperty]
private ObservableCollection<string> unvalidatedItems =
new ObservableCollection<string>();
93 [ObservableProperty]
private string? errorDescription;
94 [ObservableProperty]
private bool hasErrorDescription;
95 [ObservableProperty]
private string unvalidatedSummaryText =
string.Empty;
97 [ObservableProperty]
private bool hasPersonalInformation;
98 [ObservableProperty]
private bool hasAddressInformation;
99 [ObservableProperty]
private bool hasAttachments;
100 [ObservableProperty]
private bool hasCompanyInformation;
101 [ObservableProperty]
private bool hasCompanyAddress;
102 [ObservableProperty]
private bool hasCompanyRepresentative;
103 [ObservableProperty]
private bool isNavigating;
106 private static readonly TimeSpan pageRefreshThrottle = TimeSpan.FromMilliseconds(250);
107 private static readonly TimeSpan fieldSnapshotThrottle = TimeSpan.FromMilliseconds(500);
114 public string BannerUri => (Application.Current?.RequestedTheme ?? AppTheme.Light)
switch
116 AppTheme.Dark => this.BannerUriDark,
117 AppTheme.Light => this.BannerUriLight,
118 _ => this.BannerUriLight
121 public ObservableCollection<KycPage> Pages => this.process is not
null ? this.process.Pages : [];
123 public bool IsInSummary => this.navigation.State == KycFlowState.Summary || this.navigation.State ==
KycFlowState.PendingSummary;
124 public bool IsEditingFromSummary => this.editingFromSummary;
128 public bool HasInvalidatedItems => this.InvalidatedItems.Count > 0;
129 public bool ShouldShowUnvalidatedBanner => this.HasUnvalidatedItems && this.InvalidatedItems.Count == 0 && this.kycReference?.
CreatedIdentityState ==
IdentityState.Created;
132 private void SetEditingFromSummary(
bool value)
134 if (this.editingFromSummary == value)
return;
135 this.editingFromSummary = value;
136 this.OnPropertyChanged(nameof(this.IsEditingFromSummary));
143 if (this.process is
null || this.CurrentPage is
null)
145 this.ProgressPercent =
"0%";
149 ObservableCollection<KycPage> Visible = [.. this.Pages.Where(p => p.IsVisible(
this.process.Values))];
150 if (Visible.Count == 0)
152 this.ProgressPercent =
"0%";
155 if (this.IsInSummary)
157 this.ProgressPercent =
"100%";
160 int index = Visible.IndexOf(this.CurrentPage);
164 this.ProgressPercent =
"0%";
167 double progress = Math.Clamp((
double)index / Visible.Count, 0, 1);
168 this.ProgressPercent = $
"{(progress * 100):0}%";
173 [ObservableProperty]
private string progressPercent =
"0%";
183 private bool isKeyboardVisible;
197 bool Visible = args?.IsVisible ??
false;
198 if (this.isKeyboardVisible == Visible)
200 this.isKeyboardVisible = Visible;
201 this.OnPropertyChanged(nameof(this.IsKeyboardVisible));
202 this.OnPropertyChanged(nameof(this.ShowBottomBar));
206 public IAsyncRelayCommand NextCommand {
get; }
207 public IRelayCommand PreviousCommand {
get; }
209 public KycProcessViewModel()
212 this.NextCommand =
new AsyncRelayCommand(this.ExecuteNextAsync, this.CanExecuteNext);
213 this.PreviousCommand =
new AsyncRelayCommand(this.ExecutePrevious);
214 this.PersonalInformationSummary =
new ObservableCollection<DisplayQuad>();
215 this.AddressInformationSummary =
new ObservableCollection<DisplayQuad>();
216 this.AttachmentInformationSummary =
new ObservableCollection<DisplayQuad>();
217 this.CompanyInformationSummary =
new ObservableCollection<DisplayQuad>();
218 this.CompanyAddressSummary =
new ObservableCollection<DisplayQuad>();
219 this.CompanyRepresentativeSummary =
new ObservableCollection<DisplayQuad>();
220 this.mappedValues =
new List<Property>();
221 this.attachments =
new List<LegalIdentityAttachment>();
222 this.ApplicationSentPublic = ServiceRef.TagProfile.IdentityApplication is not
null;
225 .Named(
"KYC Page Refresh")
228 .WithPolicy(
Policies.Debounce(pageRefreshThrottle))
229 .Run(async context =>
231 CancellationToken CancellationToken = context.CancellationToken;
232 if (CancellationToken.IsCancellationRequested ||
this.disposedValue)
239 await MainThread.InvokeOnMainThreadAsync(() =>
241 if (CancellationToken.IsCancellationRequested || this.disposedValue)
245 this.SetCurrentPage(this.currentPageIndex);
246 this.NextCommand.NotifyCanExecuteChanged();
249 catch (TaskCanceledException)
259 .Named(
"KYC Field Snapshot")
262 .WithPolicy(
Policies.Debounce(fieldSnapshotThrottle))
263 .Run(async context =>
265 if (context.CancellationToken.IsCancellationRequested ||
this.disposedValue)
270 if (this.kycReference is
null || this.process is
null)
277 string? CurrentPageId = this.CurrentPage?.Id;
279 double ProgressValue = this.
Progress;
281 await this.kycService.ScheduleSnapshotAsync(Reference, Process, NavigationSnapshot, ProgressValue, CurrentPageId).ConfigureAwait(
false);
288 this.IsLoading =
true;
289 await base.OnInitializeAsync();
291 string Lang = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
292 this.kycReference = this.navigationArguments?.Reference;
293 if (this.kycReference is
null)
300 this.process = await this.kycReference.
ToProcess(Lang);
302 this.OnPropertyChanged(nameof(this.Pages));
303 if (this.process is
null)
311 bool Pending = this.kycReference.CreatedIdentityState == IdentityState.Created && !
string.IsNullOrEmpty(this.kycReference.
CreatedIdentityId);
312 bool Rejected = this.kycReference.CreatedIdentityState == IdentityState.Rejected && !
string.IsNullOrEmpty(this.kycReference.
CreatedIdentityId);
313 this.applicationSent = Pending;
314 this.ApplicationSentPublic = Pending;
315 if (!Pending && !Rejected)
316 this.PrefillFieldsFromProfile();
318 ServiceRef.XmppService.IdentityApplicationChanged += this.XmppService_IdentityApplicationChanged;
321 await this.LoadApplicationAttributes();
322 await this.LoadFeaturedPeerReviewers();
326 if (ExistingReview is not
null)
328 this.ErrorDescription = ExistingReview.
Message;
329 this.HasErrorDescription = !
string.IsNullOrWhiteSpace(this.ErrorDescription);
330 KycInvalidation.ApplyInvalidations(this.process, ExistingReview, this.ErrorDescription);
331 this.UpdateReviewIndicators();
337 Page.PropertyChanged += this.Page_PropertyChanged;
339 Field.PropertyChanged += this.Field_PropertyChanged;
342 Section.PropertyChanged += this.Section_PropertyChanged;
344 Field.PropertyChanged += this.Field_PropertyChanged;
350 int ResumeIndex = -1;
353 for (
int I = 0;
I < this.Pages.Count;
I++)
356 if (PageItem.
Id ==
this.kycReference.LastVisitedPageId && PageItem.
IsVisible(
this.process.Values))
364 bool LastWasSummary =
string.Equals(this.kycReference.
LastVisitedMode,
"Summary", StringComparison.OrdinalIgnoreCase);
368 int FirstInvalid = await this.GetFirstInvalidVisiblePageIndexAsync();
369 if (FirstInvalid >= 0)
372 this.SetEditingFromSummary(
true);
373 this.currentPageIndex = FirstInvalid;
374 this.CurrentPagePosition = FirstInvalid;
375 this.SetCurrentPage(FirstInvalid);
380 await this.BuildMappedValuesAsync();
382 this.currentPageIndex = ResumeIndex >= 0 ? ResumeIndex : this.GetFirstVisibleIndex();
383 if (this.currentPageIndex >= 0)
385 this.CurrentPagePosition = this.currentPageIndex;
386 this.SetCurrentPage(this.currentPageIndex);
388 int AnchorIndex = this.currentPageIndex >= 0 ? this.currentPageIndex : this.navigation.AnchorPageIndex;
389 this.navigation = this.navigation with { State =
KycFlowState.Summary, AnchorPageIndex = AnchorIndex, CurrentPageIndex = AnchorIndex >= 0 ? AnchorIndex : this.navigation.CurrentPageIndex };
390 this.SetEditingFromSummary(
false);
391 this.NotifyNavigationChanged();
397 await this.BuildMappedValuesAsync();
399 int FirstInvalid = await this.GetFirstInvalidVisiblePageIndexAsync();
400 this.currentPageIndex = FirstInvalid >= 0 ? FirstInvalid : this.GetFirstVisibleIndex();
401 if (this.currentPageIndex >= 0)
403 this.CurrentPagePosition = this.currentPageIndex;
404 this.SetCurrentPage(this.currentPageIndex);
406 int AnchorIndexRejected = this.currentPageIndex >= 0 ? this.currentPageIndex : this.navigation.AnchorPageIndex;
407 this.navigation = this.navigation with { State =
KycFlowState.Summary, AnchorPageIndex = AnchorIndexRejected, CurrentPageIndex = AnchorIndexRejected >= 0 ? AnchorIndexRejected : this.navigation.CurrentPageIndex };
408 this.SetEditingFromSummary(
false);
409 this.NotifyNavigationChanged();
415 this.currentPageIndex = ResumeIndex >= 0 ? ResumeIndex : this.GetFirstVisibleIndex();
417 if (this.currentPageIndex >= 0)
418 this.navigation = this.navigation with { CurrentPageIndex = this.currentPageIndex };
419 this.CurrentPagePosition = this.currentPageIndex;
420 this.SetCurrentPage(this.currentPageIndex);
424 MainThread.BeginInvokeOnMainThread(this.NextCommand.NotifyCanExecuteChanged);
427 await this.BuildMappedValuesAsync();
428 int AnchorIndexPending = this.currentPageIndex >= 0 ? this.currentPageIndex : this.navigation.AnchorPageIndex;
429 this.navigation = this.navigation with { State =
KycFlowState.Summary, AnchorPageIndex = AnchorIndexPending, CurrentPageIndex = AnchorIndexPending >= 0 ? AnchorIndexPending : this.navigation.CurrentPageIndex };
430 this.SetEditingFromSummary(
false);
431 this.NotifyNavigationChanged();
434 this.IsLoading =
false;
437 private int GetFirstVisibleIndex()
439 if (this.process is
null)
return -1;
440 for (
int I = 0;
I < this.Pages.Count;
I++)
442 if (this.Pages[
I].IsVisible(this.process.
Values))
return I;
448 private async Task GoToPageWithMapping(
string? mapping)
450 if (
string.IsNullOrWhiteSpace(mapping))
return;
451 if (this.process is
null)
return;
452 if (this.ApplicationSentPublic)
return;
454 int TargetIndex = this.FindPageIndexByMapping(mapping);
455 if (TargetIndex < 0)
return;
458 this.navigation = this.navigation with { State = KycFlowState.Form };
459 this.SetEditingFromSummary(
true);
460 this.currentPageIndex = TargetIndex;
461 this.CurrentPagePosition = TargetIndex;
462 this.SetCurrentPage(TargetIndex);
463 this.NotifyNavigationChanged();
464 await MainThread.InvokeOnMainThreadAsync(this.ScrollUp);
467 private int FindPageIndexByMapping(
string Mapping)
469 if (this.process is
null)
return -1;
470 string MappingKey = Mapping.Trim();
471 for (
int I = 0;
I < this.Pages.Count;
I++)
474 if (!Page.
IsVisible(
this.process.Values))
continue;
477 if (FieldMatches(Field, MappingKey))
return I;
483 if (FieldMatches(Field, MappingKey))
return I;
492 if (
string.Equals(
Map.Key, mappingKey, StringComparison.OrdinalIgnoreCase))
return true;
493 if (mappingKey.Equals(
"BDATE", StringComparison.OrdinalIgnoreCase) &&
497 if (mappingKey.StartsWith(
"ORGREP", StringComparison.OrdinalIgnoreCase) &&
Map.Key.StartsWith(
"ORGREP", StringComparison.OrdinalIgnoreCase))
return true;
503 public bool CanRequestFeaturedPeerReviewer => this.ApplicationSentPublic && this.HasFeaturedPeerReviewers;
504 public bool FeaturedPeerReviewers => this.CanRequestFeaturedPeerReviewer && this.PeerReview;
506 partial
void OnApplicationSentPublicChanged(
bool value)
510 int anchor = this.currentPageIndex >= 0 ? this.currentPageIndex : this.navigation.AnchorPageIndex;
511 this.navigation = this.navigation with { State =
KycFlowState.Summary, AnchorPageIndex = anchor, CurrentPageIndex = anchor >= 0 ? anchor : this.navigation.CurrentPageIndex };
512 this.SetEditingFromSummary(
false);
513 this.NotifyNavigationChanged();
515 this.OnPropertyChanged(nameof(this.ShowBottomBar));
516 this.OnPropertyChanged(nameof(this.CanRequestFeaturedPeerReviewer));
517 this.OnPropertyChanged(nameof(this.FeaturedPeerReviewers));
518 this.NextCommand.NotifyCanExecuteChanged();
521 partial
void OnPeerReviewChanged(
bool value)
523 this.OnPropertyChanged(nameof(this.FeaturedPeerReviewers));
526 partial
void OnHasFeaturedPeerReviewersChanged(
bool value)
528 this.OnPropertyChanged(nameof(this.CanRequestFeaturedPeerReviewer));
529 this.OnPropertyChanged(nameof(this.FeaturedPeerReviewers));
532 private void PrefillFieldsFromProfile()
534 if (this.process is
null)
return;
536 if (Identity?.Properties is
null)
return;
537 Dictionary<string, string> ByName = Identity.Properties
538 .Where(p => p is not
null && !
string.IsNullOrWhiteSpace(p.Name))
539 .GroupBy(p => p.Name!, StringComparer.OrdinalIgnoreCase)
540 .Select(g => g.OrderByDescending(p => p?.Value?.Length ?? 0).First())
541 .ToDictionary(p => p.Name!, p => p.Value ??
string.Empty, StringComparer.OrdinalIgnoreCase);
542 IEnumerable<ObservableKycField> AllFields = this.process.
Pages.SelectMany(p => p.AllFields)
543 .Concat(this.process.
Pages.SelectMany(p => p.AllSections).SelectMany(s => s.AllFields));
546 if (!
string.IsNullOrWhiteSpace(Field.
StringValue))
continue;
547 if (Field.
Mappings.Count == 0)
continue;
550 if (
string.IsNullOrWhiteSpace(
Map.Key))
continue;
551 if (ByName.TryGetValue(
Map.Key, out
string? Value) && !
string.IsNullOrWhiteSpace(Value))
553 Field.StringValue = Value;
561 partial
void OnCurrentPagePositionChanged(
int value)
564 if (value >= 0 && value < this.Pages.Count)
566 this.currentPageIndex = value;
570 private void Field_PropertyChanged(
object? sender,
System.ComponentModel.PropertyChangedEventArgs e)
572 if (this.disposedValue)
return;
575 this.SchedulePageRefresh();
576 this.ScheduleFieldSnapshot();
580 MainThread.BeginInvokeOnMainThread(this.NextCommand.NotifyCanExecuteChanged);
584 private void ScheduleFieldSnapshot()
586 if (this.disposedValue)
588 if (this.kycReference is
null || this.process is
null)
590 this.fieldSnapshotTask.Run();
593 private void SchedulePageRefresh()
595 if (this.disposedValue)
return;
596 this.pageRefreshTask.Run();
599 private void Section_PropertyChanged(
object? sender,
System.ComponentModel.PropertyChangedEventArgs e)
601 if (this.disposedValue)
return;
602 if (e.PropertyName == nameof(
KycSection.IsVisible))
603 this.SchedulePageRefresh();
606 private void Page_PropertyChanged(
object? sender,
System.ComponentModel.PropertyChangedEventArgs e)
608 if (this.disposedValue)
return;
610 this.SchedulePageRefresh();
613 private void SetCurrentPage(
int index)
615 if (this.disposedValue)
return;
616 if (this.process is
null)
return;
617 if (index < 0 || index >= this.Pages.Count)
return;
619 if (!this.IsInSummary && !this.Pages[index].IsVisible(this.process.
Values))
621 int firstVisible = this.GetFirstVisibleIndex();
622 if (firstVisible < 0)
return;
623 index = firstVisible;
627 if (!this.IsInSummary && (this.currentPageIndex >= this.Pages.Count || (
this.currentPageIndex >= 0 && !
this.Pages[
this.currentPageIndex].IsVisible(
this.process.Values))))
629 int firstVisible = this.GetFirstVisibleIndex();
630 if (firstVisible >= 0) index = firstVisible;
633 KycPage Page = this.Pages[index];
634 bool pageChanged = !
object.ReferenceEquals(this.CurrentPage, Page) || this.currentPageIndex != index;
636 this.currentPageIndex = index;
637 if (this.CurrentPagePosition != index)
639 this.CurrentPagePosition = index;
641 this.CurrentPage = Page;
642 this.CurrentPageTitle = Page.
Title?.
Text ?? Page.
Id;
644 this.HasCurrentPageDescription = !
string.IsNullOrWhiteSpace(this.CurrentPageDescription);
646 this.HasSections = this.CurrentPageSections is not
null && this.CurrentPageSections.Count > 0;
647 if (!this.IsInSummary && this.kycReference is not
null && this.process is not
null && pageChanged)
648 _ = this.kycService.ScheduleSnapshotAsync(this.kycReference, this.process, this.navigation, this.
Progress, Page.
Id);
649 this.OnPropertyChanged(nameof(this.
Progress));
650 this.NextCommand.NotifyCanExecuteChanged();
653 private bool CanExecuteNext()
655 if (this.IsInSummary)
656 return !this.ApplicationSentPublic;
657 if (this.CurrentPage is
null)
return false;
658 IEnumerable<ObservableKycField> Fields = this.CurrentPage.VisibleFields;
659 if (this.CurrentPageSections is not
null)
660 Fields = Fields.Concat(this.CurrentPageSections.SelectMany(s => s.VisibleFields));
661 return Fields.All(f => f.IsValid);
664 private async Task ExecuteNextAsync()
666 if (!await this.navigationGuard.RunIfNotBusy(async () =>
668 this.IsNavigating =
true;
670 if (this.IsInSummary)
672 await this.ExecuteApplyAsync();
675 if (this.editingFromSummary)
677 bool OkEditing = await this.ValidateCurrentPageAsync();
682 int FirstInvalidFromSummary = await this.GetFirstInvalidVisiblePageIndexAsync();
683 if (FirstInvalidFromSummary >= 0)
685 this.currentPageIndex = FirstInvalidFromSummary;
686 this.CurrentPagePosition = FirstInvalidFromSummary;
687 this.SetCurrentPage(FirstInvalidFromSummary);
691 await this.GoToSummaryAsync();
692 this.SetEditingFromSummary(
false);
695 bool Ok = await this.ValidateCurrentPageAsync();
697 if (this.kycReference is not
null && this.process is not
null)
698 await this.kycService.FlushSnapshotAsync(this.kycReference, this.process, this.navigation, this.
Progress, this.CurrentPage?.Id);
699 if (this.process is
null)
return;
700 List<int> VisibleIndices =
new List<int>();
701 for (
int I = 0;
I < this.Pages.Count;
I++)
703 if (this.Pages[
I].IsVisible(this.process.
Values)) VisibleIndices.Add(
I);
709 await this.BuildMappedValuesAsync();
711 this.SetEditingFromSummary(
false);
712 this.navigation = NextSnap with { AnchorPageIndex = NextSnap.AnchorPageIndex >= 0 ? NextSnap.AnchorPageIndex : this.navigation.AnchorPageIndex >= 0 ? this.navigation.AnchorPageIndex : this.currentPageIndex };
713 this.NotifyNavigationChanged();
714 this.OnPropertyChanged(nameof(this.
Progress));
716 if (this.kycReference is not
null && this.process is not
null)
717 await this.kycService.FlushSnapshotAsync(this.kycReference, this.process, this.navigation, this.
Progress, this.CurrentPage?.Id);
721 this.navigation = NextSnap;
722 this.currentPageIndex = NextSnap.CurrentPageIndex;
723 this.CurrentPagePosition = this.currentPageIndex;
724 this.SetCurrentPage(this.currentPageIndex);
726 this.NotifyNavigationChanged();
728 }).ConfigureAwait(
false))
732 this.IsNavigating =
false;
735 public event EventHandler? ScrollToTop;
736 private void ScrollUp() => this.ScrollToTop?.Invoke(
this, EventArgs.Empty);
741 return base.OnDisposeAsync();
744 private void UnsubscribeProcessHandlers()
746 if (this.process is
null)
return;
749 Page.PropertyChanged -= this.Page_PropertyChanged;
751 Field.PropertyChanged -= this.Field_PropertyChanged;
754 Section.PropertyChanged -= this.Section_PropertyChanged;
756 Field.PropertyChanged -= this.Field_PropertyChanged;
761 protected virtual void Dispose(
bool disposing)
763 if (this.disposedValue)
769 try { this.UnsubscribeProcessHandlers(); }
catch { }
770 try { this.pageRefreshTask.Dispose(); }
catch { }
771 ServiceRef.XmppService.IdentityApplicationChanged -= this.XmppService_IdentityApplicationChanged;
773 this.disposedValue =
true;
776 public void Dispose()
779 GC.SuppressFinalize(
this);
783 private async Task GoToSummaryAsync()
785 if (this.process is
null)
return;
786 bool Ok = await this.ValidateCurrentPageAsync();
788 if (this.kycReference is not
null && this.process is not
null)
789 await this.kycService.FlushSnapshotAsync(this.kycReference, this.process, this.navigation, this.
Progress, this.CurrentPage?.Id);
790 await this.BuildMappedValuesAsync();
794 this.navigation = KycTransitions.EnterSummary(ProcessState);
795 int Anchor = this.navigation.AnchorPageIndex >= 0 ? this.navigation.AnchorPageIndex : this.currentPageIndex;
798 this.currentPageIndex = Anchor;
799 this.CurrentPagePosition = Anchor;
801 this.SetEditingFromSummary(
false);
802 this.NotifyNavigationChanged();
803 if (this.kycReference is not
null && this.process is not
null)
804 await this.kycService.FlushSnapshotAsync(this.kycReference, this.process, this.navigation, this.
Progress, this.CurrentPage?.Id);
805 this.OnPropertyChanged(nameof(this.
Progress));
808 private async Task ExecutePrevious()
810 if (this.process is
null)
815 List<int> VisibleIndices =
new List<int>();
816 for (
int I = 0;
I < this.Pages.Count;
I++)
818 if (this.Pages[
I].IsVisible(this.process.
Values)) VisibleIndices.Add(
I);
822 if (this.IsInSummary)
824 this.navigation = PrevSnap;
825 this.currentPageIndex = PrevSnap.CurrentPageIndex;
826 this.CurrentPagePosition = this.currentPageIndex;
827 this.SetCurrentPage(this.currentPageIndex);
830 this.OnPropertyChanged(nameof(this.
Progress));
831 this.NotifyNavigationChanged();
834 if (PrevSnap.CurrentPageIndex ==
this.currentPageIndex)
840 this.navigation = PrevSnap;
841 this.currentPageIndex = PrevSnap.CurrentPageIndex;
842 this.CurrentPagePosition = this.currentPageIndex;
843 this.SetCurrentPage(this.currentPageIndex);
845 this.NotifyNavigationChanged();
846 await this.ValidateCurrentPageAsync();
849 private void NotifyNavigationChanged()
851 this.OnPropertyChanged(nameof(this.IsInSummary));
852 this.OnPropertyChanged(nameof(this.
Progress));
857 if (this.applicationSent)
859 if (this.editingFromSummary)
861 int FirstInvalid = await this.GetFirstInvalidVisiblePageIndexAsync();
862 if (FirstInvalid >= 0)
864 this.currentPageIndex = FirstInvalid;
865 this.CurrentPagePosition = FirstInvalid;
866 this.SetCurrentPage(FirstInvalid);
871 await this.GoToSummaryAsync();
872 this.SetEditingFromSummary(
false);
876 await this.ExecutePrevious();
880 public async Task Exit()
882 if (this.kycReference is not
null && this.process is not
null)
883 await this.kycService.FlushSnapshotAsync(this.kycReference, this.process, this.navigation, this.
Progress, this.CurrentPage?.Id);
885 if (!this.IsInSummary)
894 private async Task<bool> ValidateCurrentPageAsync()
896 if (this.CurrentPage is
null)
return false;
897 bool Ok = await this.kycService.ValidatePageAsync(this.CurrentPage);
898 if (Ok && this.process is not
null)
900 IEnumerable<ObservableKycField> Fields = this.CurrentPage.VisibleFields;
901 if (this.CurrentPageSections is not
null)
902 Fields = Fields.Concat(this.CurrentPageSections.SelectMany(s => s.VisibleFields));
906 MainThread.BeginInvokeOnMainThread(this.NextCommand.NotifyCanExecuteChanged);
910 private async Task<int> GetFirstInvalidVisiblePageIndexAsync()
912 if (this.process is
null)
return -1;
913 return await this.kycService.GetFirstInvalidVisiblePageIndexAsync(this.process);
917 private async Task ExecuteApplyAsync()
919 if (this.applicationSent)
return;
920 if (!await this.applyGuard.RunIfNotBusy(async () =>
928 if (this.attachments is
null || this.mappedValues is
null)
return;
929 bool HasIdWithKey =
false;
936 ServiceRef.
LogService.LogWarning(
"Error checking for existing identity private key, genereting new keys...: " + Ex.Message);
939 if (Succeeded && Added is not
null)
942 this.applicationSent =
true;
943 if (this.kycReference is not
null)
945 try { await this.kycService.ApplySubmissionAsync(this.kycReference, Added); }
948 this.ErrorDescription =
null;
949 this.HasErrorDescription =
false;
950 this.InvalidatedItems.Clear();
951 this.UnvalidatedItems.Clear();
952 this.UnvalidatedSummaryText =
string.Empty;
953 this.OnPropertyChanged(nameof(this.HasUnvalidatedItems));
954 this.OnPropertyChanged(nameof(this.ShouldShowUnvalidatedBanner));
955 this.OnPropertyChanged(nameof(this.ShouldShowRejectionBanner));
956 this.RemovePendingAndResubmitCommand?.NotifyCanExecuteChanged();
959 Attachment? Match = Added.Attachments.FirstOrDefault(a =>
string.Equals(a.FileName, LocalAttachment.
FileName, StringComparison.OrdinalIgnoreCase));
960 if (Match !=
null && LocalAttachment.
Data is not
null && LocalAttachment.
ContentType is not
null)
965 this.applicationId = Added.Id;
966 this.ApplicationSentPublic =
true;
968 await this.LoadApplicationAttributes();
969 await this.LoadFeaturedPeerReviewers();
970 int AnchorAfterApply = this.currentPageIndex >= 0 ? this.currentPageIndex : this.navigation.AnchorPageIndex;
971 this.navigation = this.navigation with { State =
KycFlowState.Summary, AnchorPageIndex = AnchorAfterApply, CurrentPageIndex = AnchorAfterApply >= 0 ? AnchorAfterApply : this.navigation.CurrentPageIndex };
972 this.NotifyNavigationChanged();
974 }).ConfigureAwait(
false))
return;
977 private async Task LoadApplicationAttributes()
982 MainThread.BeginInvokeOnMainThread(() =>
984 this.PeerReview = AttributesEventArgs.
PeerReview;
985 this.NrReviewers = AttributesEventArgs.
NrReviewers;
991 private async Task LoadFeaturedPeerReviewers()
996 MainThread.BeginInvokeOnMainThread(() =>
998 this.HasFeaturedPeerReviewers = (this.peerReviewServices?.Length ?? 0) > 0;
1005 await MainThread.InvokeOnMainThreadAsync(async () =>
1007 this.ApplicationSentPublic = ServiceRef.TagProfile.IdentityApplication is not
null;
1011 try { await this.kycService.UpdateSubmissionStateAsync(this.kycReference, E.Identity); } catch (Exception Ex) {
ServiceRef.
LogService.LogException(Ex); }
1014 await base.GoBack();
1019 this.applicationSent = false;
1020 this.ApplicationSentPublic = false;
1023 await ServiceRef.TagProfile.SetIdentityApplication(null, true);
1025 catch (Exception Ex)
1029 await this.BuildMappedValuesAsync();
1030 int AnchorRejected = this.currentPageIndex >= 0 ? this.currentPageIndex : this.navigation.AnchorPageIndex;
1031 this.navigation = this.navigation with { State =
KycFlowState.Summary, AnchorPageIndex = AnchorRejected, CurrentPageIndex = AnchorRejected >= 0 ? AnchorRejected : this.navigation.CurrentPageIndex };
1032 this.SetEditingFromSummary(
false);
1033 if (this.kycReference is not
null && this.process is not
null)
1034 await this.kycService.FlushSnapshotAsync(this.kycReference, this.process, this.navigation, this.
Progress, this.CurrentPage?.Id);
1036 this.OnPropertyChanged(nameof(this.
Progress));
1038 string AlertMessage;
1057 if (this.ApplicationSentPublic && this.peerReviewServices is
null)
1058 await this.LoadFeaturedPeerReviewers();
1060 this.UpdateReviewIndicators();
1064 [RelayCommand(CanExecute = nameof(ApplicationSentPublic))]
1065 private async Task ScanQrCode()
1072 private async Task SendPeerReviewRequest(
string? reviewerId)
1075 if (ToReview is
null ||
string.IsNullOrEmpty(reviewerId))
return;
1081 catch (Exception Ex)
1088 [RelayCommand(CanExecute = nameof(CanRequestFeaturedPeerReviewer))]
1089 private async Task RequestReview()
1091 if (this.peerReviewServices is
null)
1092 await this.LoadFeaturedPeerReviewers();
1093 if ((this.peerReviewServices?.Length ?? 0) > 0)
1096 List<ServiceProviderWithLegalId> List = [.. LocalPeerReview,
new RequestFromPeer()];
1109 await this.SendPeerReviewRequest(SPWL.LegalId);
1115 await this.ScanQrCode();
1118 [RelayCommand(CanExecute = nameof(ApplicationSentPublic))]
1119 private async Task RevokeApplication()
1121 await this.RevokeCurrentApplicationAsync(
true);
1124 private Task BuildMappedValuesAsync()
1126 if (this.process is
null)
1128 this.mappedValues =
new();
1129 this.attachments =
new();
1130 return Task.CompletedTask;
1132 return Task.Run(async () =>
1134 (IReadOnlyList<Property> Props, IReadOnlyList<LegalIdentityAttachment> Atts) = await this.kycService.PreparePropertiesAndAttachmentsAsync(
this.process, CancellationToken.None);
1135 this.mappedValues = Props.ToList();
1136 this.attachments = Atts.ToList();
1146 ISet<string> Invalid = KycSummary.BuildInvalidMappingSet(this.kycReference);
1147 KycSummaryModel Model = KycSummary.Generate(this.process, this.mappedValues, this.attachments, Invalid);
1148 MainThread.BeginInvokeOnMainThread(() => this.ApplySummaryModel(Model));
1152 private void ApplySummaryModel(KycSummaryModel model)
1154 this.PersonalInformationSummary =
new ObservableCollection<DisplayQuad>(model.Get(KycSummary.Personal)?.Items ?? Array.Empty<
DisplayQuad>());
1155 this.AddressInformationSummary =
new ObservableCollection<DisplayQuad>(model.Get(KycSummary.Address)?.Items ?? Array.Empty<
DisplayQuad>());
1156 this.AttachmentInformationSummary =
new ObservableCollection<DisplayQuad>(model.Get(KycSummary.Attachments)?.Items ?? Array.Empty<
DisplayQuad>());
1157 this.CompanyInformationSummary =
new ObservableCollection<DisplayQuad>(model.Get(KycSummary.CompanyInfo)?.Items ?? Array.Empty<
DisplayQuad>());
1158 this.CompanyAddressSummary =
new ObservableCollection<DisplayQuad>(model.Get(KycSummary.CompanyAddress)?.Items ?? Array.Empty<
DisplayQuad>());
1159 this.CompanyRepresentativeSummary =
new ObservableCollection<DisplayQuad>(model.Get(KycSummary.CompanyRepresentative)?.Items ?? Array.Empty<
DisplayQuad>());
1160 this.HasPersonalInformation = this.PersonalInformationSummary.Count > 0;
1161 this.HasAddressInformation = this.AddressInformationSummary.Count > 0;
1162 this.HasAttachments = this.AttachmentInformationSummary.Count > 0;
1163 this.HasCompanyInformation = this.CompanyInformationSummary.Count > 0;
1164 this.HasCompanyAddress = this.CompanyAddressSummary.Count > 0;
1165 this.HasCompanyRepresentative = this.CompanyRepresentativeSummary.Count > 0;
1170 if (this.process is
null)
1173 List<KycPageState> PageStates =
new List<KycPageState>(this.process.
Pages.Count);
1177 List<KycFieldState> FieldStates =
new List<KycFieldState>();
1180 if (!Field.IsVisible)
continue;
1186 if (!
Section.IsVisible)
continue;
1189 if (!Field.IsVisible)
continue;
1194 PageStates.Add(
new KycPageState(Page.
Id, IsVisible, FieldStates));
1196 return new KycProcessState(PageStates, this.navigation, this.applicationSent);
1201 this.ErrorDescription = review.
Message;
1202 this.HasErrorDescription = !
string.IsNullOrWhiteSpace(review.
Message);
1203 if (this.kycReference is not
null)
1204 await this.kycService.ApplyApplicationReviewAsync(this.kycReference, review);
1205 KycInvalidation.ApplyInvalidations(this.process, review, this.ErrorDescription);
1206 this.UpdateReviewIndicators();
1207 await this.BuildMappedValuesAsync();
1210 [RelayCommand(CanExecute = nameof(ShouldShowUnvalidatedBanner))]
1211 private async Task RemovePendingAndResubmitAsync()
1214 if (!await AreYouSure(Confirmation))
1217 if (!await this.RevokeCurrentApplicationAsync(
false))
1220 if (!await this.ClearUnvalidatedFieldsAsync())
1223 if (this.process is not
null && this.kycReference is not
null)
1227 await this.kycService.FlushSnapshotAsync(this.kycReference, this.process, this.navigation, this.
Progress, this.CurrentPage?.Id);
1229 catch (Exception ex)
1235 await this.BuildMappedValuesAsync();
1236 await this.ExecuteApplyAsync();
1239 private async Task<bool> ClearUnvalidatedFieldsAsync()
1245 HashSet<string> mappingKeys =
new HashSet<string>(StringComparer.OrdinalIgnoreCase);
1248 if (!
string.IsNullOrWhiteSpace(Claim))
1249 mappingKeys.Add(Claim.Trim());
1253 if (!
string.IsNullOrWhiteSpace(
Photo))
1255 string trimmed =
Photo.Trim();
1256 mappingKeys.Add(trimmed);
1257 string? baseName = Path.GetFileNameWithoutExtension(trimmed);
1258 if (!
string.IsNullOrWhiteSpace(baseName))
1259 mappingKeys.Add(baseName.Trim());
1263 if (mappingKeys.Count == 0)
1266 bool cleared = this.ClearFieldsForMappings(mappingKeys);
1271 this.mappedValues = this.mappedValues
1272 .Where(p => !mappingKeys.Contains(p.Name ??
string.Empty))
1274 this.attachments = this.attachments
1277 string fileName = a.FileName ??
string.Empty;
1278 string baseName = Path.GetFileNameWithoutExtension(fileName) ?? fileName;
1279 return !mappingKeys.Contains(fileName.Trim()) && !mappingKeys.Contains(baseName.Trim());
1283 review.UnvalidatedClaims = Array.Empty<
string>();
1284 review.UnvalidatedPhotos = Array.Empty<
string>();
1286 await this.kycService.ApplyApplicationReviewAsync(this.kycReference, review);
1287 this.UpdateReviewIndicators();
1291 private bool ClearFieldsForMappings(HashSet<string> mappingKeys)
1293 if (this.process is
null || mappingKeys.Count == 0)
1296 bool cleared =
false;
1299 cleared |= this.ClearFields(Page.
AllFields, mappingKeys);
1303 cleared |= this.ClearFields(
Section.AllFields, mappingKeys);
1309 private async Task<bool> RevokeCurrentApplicationAsync(
bool requireConfirmation)
1312 if (Application is
null)
1314 this.ApplicationSentPublic =
false;
1315 this.applicationSent =
false;
1316 this.peerReviewServices =
null;
1317 this.HasFeaturedPeerReviewers =
false;
1318 if (this.kycReference is not
null)
1319 await this.kycService.ClearSubmissionAsync(this.kycReference);
1334 this.ApplicationSentPublic =
false;
1335 this.applicationSent =
false;
1336 this.peerReviewServices =
null;
1337 this.HasFeaturedPeerReviewers =
false;
1338 if (this.kycReference is not
null)
1339 await this.kycService.ClearSubmissionAsync(this.kycReference);
1340 await this.BuildMappedValuesAsync();
1341 int AnchorAfterRevoke = this.currentPageIndex >= 0 ? this.currentPageIndex : this.navigation.AnchorPageIndex;
1342 this.navigation = this.navigation with { State =
KycFlowState.Summary, AnchorPageIndex = AnchorAfterRevoke, CurrentPageIndex = AnchorAfterRevoke >= 0 ? AnchorAfterRevoke : this.navigation.CurrentPageIndex };
1343 this.SetEditingFromSummary(
false);
1344 this.NotifyNavigationChanged();
1347 catch (Exception Ex)
1355 private bool ClearFields(IEnumerable<ObservableKycField> fields, HashSet<string> mappingKeys)
1357 bool cleared =
false;
1360 if (!Field.
Mappings.Any(m => mappingKeys.Contains(m.Key)))
1363 if (!
string.IsNullOrWhiteSpace(Field.
StringValue))
1365 Field.StringValue =
null;
1366 if (this.process is not
null && this.process.
Values.ContainsKey(Field.
Id))
1367 this.process.
Values[Field.
Id] =
null;
1374 private void UpdateReviewIndicators()
1379 this.UnvalidatedSummaryText =
string.Empty;
1380 this.InvalidatedItems.Clear();
1381 this.UnvalidatedItems.Clear();
1387 if (claimCount == 0 && photoCount == 0)
1389 this.UnvalidatedSummaryText =
string.Empty;
1393 this.UnvalidatedSummaryText =
ServiceRef.
Localizer[
"KycUnvalidatedWarningDescription",
false, claimCount, photoCount];
1396 this.InvalidatedItems.Clear();
1402 string reason =
string.IsNullOrWhiteSpace(detail.
Reason) ? string.Empty : $
" — {detail.Reason}";
1403 this.InvalidatedItems.Add($
"{label}{reason}");
1410 string Reason =
string.IsNullOrWhiteSpace(Detail.
Reason) ? string.Empty : $
" — {Detail.Reason}";
1411 this.InvalidatedItems.Add($
"{Label}{Reason}");
1414 this.UnvalidatedItems.Clear();
1417 if (
string.IsNullOrWhiteSpace(claim))
1419 string label = this.ResolveDisplayLabel(claim.Trim(), claim.Trim());
1420 this.UnvalidatedItems.Add(label);
1424 if (
string.IsNullOrWhiteSpace(photo))
1426 string label = this.ResolveDisplayLabel(photo.Trim(), photo.Trim());
1427 this.UnvalidatedItems.Add(label);
1431 this.OnPropertyChanged(nameof(this.HasUnvalidatedItems));
1432 this.OnPropertyChanged(nameof(this.ShouldShowUnvalidatedBanner));
1433 this.OnPropertyChanged(nameof(this.ShouldShowRejectionBanner));
1434 this.RemovePendingAndResubmitCommand?.NotifyCanExecuteChanged();
1437 private string ResolveDisplayLabel(
string? MappingKey,
string? Fallback)
1439 string? FromMapping =
string.IsNullOrWhiteSpace(MappingKey) ? null : this.FindLabelByMapping(MappingKey);
1440 if (!
string.IsNullOrWhiteSpace(FromMapping))
1443 if (!
string.IsNullOrWhiteSpace(Fallback))
1445 string? FromLabel = this.FindLabelByDisplayName(Fallback);
1446 if (!
string.IsNullOrWhiteSpace(FromLabel))
1450 return Fallback ??
string.Empty;
1453 private string? FindLabelByMapping(
string mappingKey)
1455 foreach ((
DisplayQuad Item,
bool PreferValue) entry in this.IterateSummaryItems())
1457 if (!
string.IsNullOrWhiteSpace(entry.Item.Mapping) && entry.Item.Mapping.Equals(mappingKey, StringComparison.OrdinalIgnoreCase))
1458 return this.GetDisplayLabel(entry.Item, entry.PreferValue);
1464 private string? FindLabelByDisplayName(
string label)
1466 foreach ((
DisplayQuad Item,
bool PreferValue) entry in this.IterateSummaryItems())
1468 string candidate = this.GetDisplayLabel(entry.Item, entry.PreferValue);
1469 if (!
string.IsNullOrWhiteSpace(candidate) && candidate.Equals(label, StringComparison.OrdinalIgnoreCase))
1476 private IEnumerable<(
DisplayQuad Item,
bool PreferValue)> IterateSummaryItems()
1478 IEnumerable<(ObservableCollection<DisplayQuad>? Collection,
bool PreferValue)> collections =
new (ObservableCollection<DisplayQuad>?,
bool)[]
1480 (this.PersonalInformationSummary,
false),
1481 (this.AddressInformationSummary,
false),
1482 (this.AttachmentInformationSummary,
true),
1483 (this.CompanyInformationSummary,
false),
1484 (this.CompanyAddressSummary,
false),
1485 (this.CompanyRepresentativeSummary,
false)
1488 foreach ((ObservableCollection<DisplayQuad>? Collection,
bool PreferValue) entry in collections)
1490 if (entry.Collection is
null)
1494 yield
return (item, entry.PreferValue);
1498 private string GetDisplayLabel(
DisplayQuad item,
bool preferValue)
1501 return string.Empty;
1503 if (preferValue && !
string.IsNullOrWhiteSpace(item.Value))
1506 if (!
string.IsNullOrWhiteSpace(item.Label))
1509 return item.Value ??
string.Empty;
Image identifiers for branding.
static bool StartsWithIdScheme(string Url)
Checks if the specified code starts with the IoT ID scheme.
static ? string RemoveScheme(string Url)
Removes the URI Schema from an URL.
const string IotId
The IoT ID URI Scheme (iotid)
XMPP Protocol Properties.
const string DeviceId
Device ID
const string Country
Country
const string BirthYear
Birth Year
const string Phone
Phone number
const string EMail
e-Mail address
const string BirthDay
Birth Day
const string Jid
Jabber ID
const string BirthMonth
Birth Month
A set of never changing property constants and helpful values.
A strongly-typed resource class, for looking up localized strings, etc.
static string AreYouSureYouWantToRevokeTheCurrentIdApplication
Looks up a localized string similar to Are you sure you want to revoke the current ID application?...
static string CouldYouPleaseReviewMyIdentityInformation
Looks up a localized string similar to Could you please review my identity information?...
static string KycConfirmRemovePending
Looks up a localized string similar to This will revoke your current application, remove the pending ...
static string YourLegalIdentityHasBeenCompromised
Looks up a localized string similar to Your identity has been marked compromised. You have therefore ...
static string RequestReview
Looks up a localized string similar to Request review.
static string SelectServiceProviderPeerReview
Looks up a localized string similar to Select where to send the review request..
static string YourLegalIdentityHasBeenObsoleted
Looks up a localized string similar to Your identity has been marked obsolete. You have therefore bee...
static string QrPageTitleScanPeerId
Looks up a localized string similar to Scan the peers ID.
static string YourApplicationWasRejected
Looks up a localized string similar to Your application was rejected..
static string APetitionHasBeenSentToYourPeer
Looks up a localized string similar to A petition has been sent to your peer.
static string AreYouSureYouWantToSendThisIdApplication
Looks up a localized string similar to Are you sure you want to send a new ID Application?...
static string PetitionSent
Looks up a localized string similar to Petition sent.
static string Ok
Looks up a localized string similar to OK.
static string Rejected
Looks up a localized string similar to Rejected.
static string Kyc_Exit
Looks up a localized string similar to Are you sure you would like to leave the Identity Application ...
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
Contains additional data about an invalid claim.
string Claim
The claim identifier.
string DisplayName
Localized display name for the claim.
string Reason
The textual reason explaining the invalidation.
Captures the result of an application review returned from backend services.
ApplicationReviewPhotoDetail[] InvalidPhotoDetails
Optional details about invalid photos.
string[] UnvalidatedPhotos
Photos still pending validation.
string Message
The localized or raw message describing the review result.
string[] UnvalidatedClaims
Claims still pending validation.
ApplicationReviewClaimDetail[] InvalidClaimDetails
Optional details about invalid claims.
Contains additional data about an invalid photo.
string Reason
Textual reason for the invalidation.
string DisplayName
Display name presented to users.
Represents a displayable set of three related strings: a label, a value, and an optional mapping.
Contains a local reference to a KYC process.
string? LastVisitedPageId
Last visited page identifier to support resuming.
ApplicationReview? ApplicationReview
Latest backend review for this application, if any.
IdentityState? CreatedIdentityState
Last known state of the created identity (if any), for quick status tagging offline.
string LastVisitedMode
Last visited mode: "Form" or "Summary". Default is "Form".
string? CreatedIdentityId
The legal ID of the created identity (if any).
async Task< KycProcess?> ToProcess(string? lang=null)
Creates a KycProcess from this reference, populating its fields.
string Text
Gets the localized text for the current UI culture, or an appropriate fallback.
Mapping from a field value to an identity property, including optional transform pipeline.
Represents a page in a KYC process containing fields and sections.
ObservableCollection< KycSection > AllSections
Gets all sections contained in the page.
string Id
Gets the page identifier.
void UpdateVisibilities(IDictionary< string, string?> Values)
Updates visibility flags for fields and sections based on current values.
KycLocalizedText? Description
Gets or sets the localized page description.
ReadOnlyObservableCollection< KycSection > VisibleSections
Gets the read-only collection of visible sections in the page.
KycLocalizedText? Title
Gets or sets the localized page title.
bool IsVisible(IDictionary< string, string?> Values)
Gets a value indicating whether the page is visible based on current values and its condition.
ObservableCollection< ObservableKycField > AllFields
Gets all fields directly contained in the page (excluding sections).
Represents a parsed KYC process with pages, fields, and current values.
void ClearValidation()
Clears validation state (error messages and flags) across all fields.
ObservableCollection< KycPage > Pages
Gets the collection of pages in the KYC process.
void Initialize()
Initializes page value-change notifications.
IDictionary< string, string?> Values
Gets a modifiable dictionary of field values keyed by field identifier.
bool HasMapping(string Mapping)
Determines if the process contains a mapping for a specific identity property.
The base KYC field model. Use as-is for generic fields, or subclass for custom logic.
string Id
The unique identifier for the field.
abstract ? string StringValue
Gets or sets the value as a string, parsing or formatting as needed for the field type.
ObservableCollection< KycMapping > Mappings
Mappings to apply output.
Represent an attachment to a LegalIdentity.
byte?[] Data
The raw file data.
string? ContentType
Content type (mime) of the attachment.
string? FileName
The raw filename.
Base class that references services in the app.
static IServiceProvider Provider
The service provider for the app. This is set before the app is started, and will be used to resolve ...
static ILogService LogService
Log service.
static INetworkService NetworkService
Network service.
static IUiService UiService
Service serializing and managing UI-related tasks.
static INavigationService NavigationService
The navigation service for navigating between pages.
static IAttachmentCacheService AttachmentCacheService
AttachmentCache service.
static ITagProfile TagProfile
TAG Profile service.
static IReportingStringLocalizer Localizer
Localization service
static IXmppService XmppService
The XMPP service for XMPP communication.
static IPlatformSpecific PlatformSpecific
Localization service
An base class holding page specific navigation parameters.
Provides a data-binding friendly mechanism to manage and report the status of asynchronous operations...
Simple asynchronous reentrancy guard that ensures a critical async section is not executed concurrent...
Represents representing a peer review from a peer, by scanning their QR-code.
A base class for all view models, inheriting from the BindableObject. NOTE: using this class requir...
Navigation arguments for the KYC process page. Carries the KycReference to edit/continue.
Orchestrates the interactive KYC flow, covering navigation, validation, summary projection,...
override async Task OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
override async Task GoBack()
Method called when user wants to navigate to the previous screen.
override Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
bool ShowBottomBar
Gets a value indicating whether the bottom navigation bar should be shown. Hidden when the applicatio...
bool IsKeyboardVisible
Gets a value indicating whether any on-screen keyboard is currently visible.
void OnKeyboardInsetChanged(Services.UI.KeyboardInsetChangedEventArgs args)
Receives keyboard inset change notifications from the shell.
A page that allows the user to view its tokens.
Contains a reference to an attachment assigned to a legal object.
string Url
URL to retrieve attachment, if provided.
Event arguments for callback methods to ID Application attributes queries.
int NrReviewers
Number of peer reviewers required to get an ID approved using peer review.
bool PeerReview
If peer-review is allowed as a mechanism to approve ID applications.
Event arguments for legal identity responses
LegalIdentity Identity
Legal Identity
IdentityState State
Current state of identity
string Id
ID of the legal identity
Contains information about a service provider.
string Type
Type of service provider.
string Id
ID of service provider.
Contains information about a service provider with a legal identity.
Service for loading KYC processes, performing validation, building identity artifacts and managing pe...
Represents a component that consumes keyboard inset updates manually.
Interface for information about a service provider.
sealed record KycProcessState(IReadOnlyList< KycPageState > Pages, KycNavigationSnapshot Navigation, bool ApplicationSent)
Root aggregate-like snapshot passed through pure functions.
KycFlowState
High-level flow states for the KYC process UI.
sealed record KycPageState(string Id, bool IsVisible, IReadOnlyList< KycFieldState > FieldStates)
Immutable page state abstraction for domain calculations (validation, navigation decisions).
sealed record KycFieldState(string Id, bool IsValid, string? Value)
Immutable field state used by pure domain operations.
sealed record KycNavigationSnapshot(int CurrentPageIndex, int AnchorPageIndex, KycFlowState State)
Navigation snapshot capturing minimal mutable UI navigation state.
class Photo(byte[] Binary, int Rotation, Attachment? Attachment)
Class containing information about a photo.
AuthenticationPurpose
Purpose for requesting the user to authenticate itself.
IdentityState
Lists recognized legal identity states.
Reason
Reason a token is not valid.