1using System.Collections.ObjectModel;
2using System.Globalization;
5using System.Threading.Tasks;
6using CommunityToolkit.Mvvm.ComponentModel;
7using CommunityToolkit.Mvvm.Input;
9using Microsoft.Maui.ApplicationModel;
49 private const int AvailableTemplatesPageSize = 10;
50 public ObservableCollection<KycReference> Applications {
get; } =
new();
51 public ObservableCollection<KycApplicationTemplate> AvailableApplications {
get; } =
new();
56 public string BannerUri =>
57 Application.Current?.UserAppTheme
switch
59 AppTheme.Dark => this.BannerUriDark,
60 AppTheme.Light => this.BannerUriLight,
61 _ => this.BannerUriLight
62 } ?? this.BannerUriLight;
68 private bool hasMoreAvailableTemplates;
70 public bool HasCurrentApplication => this.CurrentApplication is not
null;
72 public bool CanLoadMoreAvailableApplications => this.CanExecuteCommands && this.HasMoreAvailableTemplates;
77 public bool IsLoading => this.Loader.IsRunning;
78 public string? LoadError => this.Loader.ErrorMessage;
80 public bool HasApplications => this.Applications.Count > 0;
82 public bool ShowProgressBar => this.CurrentApplication is not
null
83 && (this.CurrentApplication.CreatedIdentityState is
null
84 || this.CurrentApplication.CreatedIdentityState ==
IdentityState.Created);
86 partial
void OnHasMoreAvailableTemplatesChanged(
bool value)
88 this.LoadMoreAvailableApplicationsCommand.NotifyCanExecuteChanged();
99 .Named(
"LoadApplications")
101 .WithPolicy(
Policies.Retry(3, (attempt, ex) => TimeSpan.FromMilliseconds(250 * attempt * attempt)))
104 .Run(this.LoadApplicationsAsync)
105 .Build(this.CreateNewApplicationCommand, this.OpenApplicationCommand);
108 .Named(
"LoadAvailableApplications")
110 .WithPolicy(
Policies.Retry(3, (attempt, ex) => TimeSpan.FromMilliseconds(250 * attempt * attempt)))
113 .Run(this.LoadAvailableApplicationsAsync)
114 .Build(this.CreateNewApplicationCommand, this.LoadMoreAvailableApplicationsCommand);
119 this.IdentityApplicationSent = ServiceRef.TagProfile.IdentityApplication is not
null;
121 this.HasLegalIdentity = ServiceRef.TagProfile.LegalIdentity is not
null &&
122 ServiceRef.TagProfile.LegalIdentity.State ==
IdentityState.Approved;
124 ServiceRef.XmppService.IdentityApplicationChanged += this.XmppService_IdentityApplicationChanged;
125 ServiceRef.XmppService.LegalIdentityChanged += this.XmppService_LegalIdentityChanged;
126 ServiceRef.TagProfile.OnPropertiesChanged += this.TagProfile_OnPropertiesChanged;
128 await base.OnInitializeAsync();
130 this.NotifyCommandsCanExecuteChanged();
135 ServiceRef.XmppService.IdentityApplicationChanged -= this.XmppService_IdentityApplicationChanged;
136 ServiceRef.XmppService.LegalIdentityChanged -= this.XmppService_LegalIdentityChanged;
137 ServiceRef.TagProfile.OnPropertiesChanged -= this.TagProfile_OnPropertiesChanged;
142 return base.OnDisposeAsync();
147 MainThread.BeginInvokeOnMainThread(() =>
149 this.IdentityApplicationSent = ServiceRef.TagProfile.IdentityApplication is not
null;
152 return Task.CompletedTask;
157 MainThread.BeginInvokeOnMainThread(() =>
159 this.HasLegalIdentity = ServiceRef.TagProfile.LegalIdentity is not
null &&
160 ServiceRef.TagProfile.LegalIdentity.State ==
IdentityState.Approved;
163 return Task.CompletedTask;
166 private void TagProfile_OnPropertiesChanged(
object? sender, EventArgs e)
173 await base.OnAppearingAsync();
176 this.AvailableLoader.
Run();
179 bool IdApplicationSent = ServiceRef.TagProfile.IdentityApplication is not
null;
180 if (this.IdentityApplicationSent != IdApplicationSent)
181 this.IdentityApplicationSent = IdApplicationSent;
183 this.OnPropertyChanged(nameof(this.IdentityApplicationSent));
189 return MainThread.InvokeOnMainThreadAsync(async () =>
191 await base.XmppService_ConnectionStateChanged(Sender, NewState);
192 this.NotifyCommandsCanExecuteChanged();
199 base.SetIsBusy(IsBusy);
200 this.NotifyCommandsCanExecuteChanged();
203 private void NotifyCommandsCanExecuteChanged()
205 this.CreateNewApplicationCommand.NotifyCanExecuteChanged();
206 this.OpenApplicationCommand.NotifyCanExecuteChanged();
207 this.LoadMoreAvailableApplicationsCommand.NotifyCanExecuteChanged();
221 private bool identityApplicationSent;
227 private bool hasLegalIdentity;
240 if (this.CurrentApplication is not
null)
244 if (this.CurrentApplication.Fields is not
null)
246 PreviousFields = this.CurrentApplication.Fields
251 if (!
string.IsNullOrEmpty(this.CurrentApplication.CreatedIdentityId))
281 .OrderByDescending(r => r.UpdatedUtc)
282 .FirstOrDefault(r => r.Fields is not
null && r.Fields.Length > 0);
284 if (LatestWithFields?.Fields is not
null)
286 PreviousFields = LatestWithFields.Fields
297 string Language = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
312 [RelayCommand(CanExecute = nameof(CanLoadMoreAvailableApplications))]
313 private async Task LoadMoreAvailableApplications()
315 if (!this.CanLoadMoreAvailableApplications)
320 if (this.availableApplicationsPage is
null ||
string.IsNullOrWhiteSpace(this.availableApplicationsPage.
NextAfter))
323 string Language = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
324 string After = this.availableApplicationsPage.
NextAfter;
327 await MainThread.InvokeOnMainThreadAsync(() =>
331 bool Exists = this.AvailableApplications.Any(existing => TemplatesEqual(existing, Template));
333 this.AvailableApplications.Add(Template);
336 this.availableApplicationsPage = Page;
405 private void RefreshApplications() => this.Loader.Refresh();
408 private void ReloadApplications() => this.Loader.Reload();
411 private void CancelLoading() => this.Loader.Cancel();
418 if (!
string.IsNullOrEmpty(firstId) && !
string.IsNullOrEmpty(secondId))
419 return string.Equals(firstId, secondId,
System.StringComparison.Ordinal);
433 #region Loading (refactored to ObservableTask)
439 private async Task LoadApplicationsAsync(TaskContext<int> ctx)
441 CancellationToken Ct = ctx.CancellationToken;
442 IProgress<int> Progress = ctx.Progress;
449 await MainThread.InvokeOnMainThreadAsync(() =>
451 this.Applications.Clear();
452 this.OnPropertyChanged(nameof(this.HasApplications));
456 IEnumerable<KycReference> Refs = Array.Empty<
KycReference>();
460 Ct.ThrowIfCancellationRequested();
463 catch (OperationCanceledException) {
throw; }
470 Ct.ThrowIfCancellationRequested();
472 KycReference? Latest = Refs.OrderByDescending(r => r.UpdatedUtc).FirstOrDefault();
474 await MainThread.InvokeOnMainThreadAsync(() =>
476 this.Applications.Clear();
477 this.CurrentApplication = Latest;
478 if (Latest is not
null)
479 this.Applications.Add(Latest);
483 Progress.Report(100);
486 await MainThread.InvokeOnMainThreadAsync(() =>
488 this.OnPropertyChanged(nameof(this.HasCurrentApplication));
489 this.OnPropertyChanged(nameof(this.HasApplications));
491 this.NotifyCommandsCanExecuteChanged();
494 catch (OperationCanceledException)
507 private async Task LoadAvailableApplicationsAsync(TaskContext<int> ctx)
509 CancellationToken Ct = ctx.CancellationToken;
510 IProgress<int> Progress = ctx.Progress;
516 string Language = CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
517 Ct.ThrowIfCancellationRequested();
520 Ct.ThrowIfCancellationRequested();
522 await MainThread.InvokeOnMainThreadAsync(() =>
524 this.AvailableApplications.Clear();
526 this.AvailableApplications.Add(Template);
527 this.availableApplicationsPage = Page;
532 Progress.Report(100);
534 catch (OperationCanceledException)
545 private static string GetTextOrFallback(
string key,
string fallback)
548 return L.ResourceNotFound ? fallback : L.Value;
551 private static string GetIdentityStateText(
IdentityState state)
553 Microsoft.Extensions.Localization.LocalizedString L =
ServiceRef.
Localizer[
"IdentityState_" + state.ToString(),
false];
554 return L.ResourceNotFound ? state.ToString() : L.Value;
Image identifiers for branding.
A set of never changing property constants and helpful values.
string ItemId
Gets the PubSub item identifier.
Represents a page of KYC application templates along with pagination metadata.
string? NextAfter
Gets the item identifier to use in an "after" query when requesting the next page.
bool UsedFallback
Gets a value indicating if the bundled fallback template was returned instead of remote data.
IReadOnlyList< KycApplicationTemplate > Templates
Gets the application templates on this page.
bool HasMoreAfter
Gets a value indicating whether more pages are likely available after the current one.
Represents a parsed KYC application template along with optional PubSub metadata.
KycReference Reference
Gets the parsed reference constructed from the template XML.
KycApplicationItem? Source
Gets the originating PubSub metadata, if available.
Contains a local reference to a KYC process.
IdentityState? CreatedIdentityState
Last known state of the created identity (if any), for quick status tagging offline.
string? CreatedIdentityId
The legal ID of the created identity (if any).
string? KycXml
XML describing the KYC process.
Represents a field value in a KYC process reference.
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 IUiService UiService
Service serializing and managing UI-related tasks.
static INavigationService NavigationService
The navigation service for navigating between pages.
static ITagProfile TagProfile
TAG Profile service.
static IReportingStringLocalizer Localizer
Localization service
static IXmppService XmppService
The XMPP service for XMPP communication.
Basic telemetry that writes ObservableTask events to the app log.
Provides a data-binding friendly mechanism to manage and report the status of asynchronous operations...
void Cancel()
Cancels the running task.
void Run()
Start the configured task now (no CanExecute gating).
void Refresh()
Refreshes the current task. This cancels any running task and starts a new one using the stored facto...
The view model to bind to for when displaying the applications page.
override Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
override async Task OnAppearingAsync()
Method called when view is appearing on the screen.
override void SetIsBusy(bool IsBusy)
Sets the IsBusy property.
override async Task OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
override Task XmppService_ConnectionStateChanged(object? Sender, XmppState NewState)
Listens to connection state changes from the XMPP server.
ApplicationsViewModel()
Creates an instance of the ApplicationsViewModel class.
bool CanExecuteCommands
Used to find out if a command can execute
A page to display when the user wants to view an identity.
Navigation arguments for the KYC process page. Carries the KycReference to edit/continue.
A view model that holds the XMPP state.
Event arguments for legal identity responses
IdentityState State
Current state of identity
string Id
ID of the legal identity
Static interface for database persistence. In order to work, a database provider has to be assigned t...
static IDatabaseProvider Provider
Registered database provider.
static async Task Delete(object Object)
Deletes an object in the database.
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Task< KycApplicationPage > LoadKycApplicationsPageAsync(string? After=null, string? Before=null, int? Index=null, int? Max=null, string? Lang=null, CancellationToken CancellationToken=default)
Loads a page of KYC application templates using PubSub pagination, with bundled fallback when remote ...
Task PrepareReferenceForNewApplicationAsync(KycReference Reference, string? Language, IReadOnlyList< KycFieldValue >? SeedFields)
Resets reference state and seeds optional field values for a fresh application session.
Task< KycReference > LoadKycReferenceAsync(string? Lang=null, KycApplicationTemplate? Template=null)
Loads (or creates) the persisted KYC reference and ensures process XML is available.
Task GoToAsync(string Route)
Navigates to the specified route and pushes the page onto the navigation stack.
Task DisplayException(Exception Exception, string? Title=null)
Displays an alert/message box to the user.
Task Flush()
Persists any pending changes.
AuthenticationPurpose
Purpose for requesting the user to authenticate itself.
IdentityState
Lists recognized legal identity states.
XmppState
State of XMPP connection.