1using System.Collections.ObjectModel;
2using CommunityToolkit.Mvvm.ComponentModel;
9using Microsoft.Extensions.Localization;
10using System.Globalization;
12using CommunityToolkit.Mvvm.Input;
27 private readonly PhotosLoader photosLoader;
28 private readonly ViewIdentityNavigationArgs? args;
29 private readonly IDispatcherTimer? timer;
30 private readonly IDispatcherTimer? qrTimer;
34 private bool hasAppeared;
40 private bool shouldCelebrate =
false;
43 private bool canAddContact =
false;
45 private bool canRemoveContact =
false;
48 private bool isThirdPartyIdentity =
false;
51 [NotifyPropertyChangedFor(nameof(HasPersonalFields))]
52 private bool hasDomainProperty =
false;
55 private string friendlyName =
string.Empty;
57 private string subText =
string.Empty;
60 [NotifyPropertyChangedFor(nameof(HasAge))]
61 private string ageText =
string.Empty;
62 public bool HasAge => !
string.IsNullOrEmpty(this.AgeText);
65 [NotifyPropertyChangedFor(nameof(IsApproved))]
68 public bool IsApproved => this.IdentityState is not
null && this.IdentityState ==
Waher.
Networking.
XMPP.
Contracts.IdentityState.Approved;
72 private DateTime? expireDate =
null;
74 private DateTime? issueDate =
null;
79 public ObservableCollection<ObservableFieldItem> PersonalFields {
get; } = [];
80 public ObservableCollection<ObservableFieldItem> OrganizationFields {
get; } = [];
81 public ObservableCollection<ObservableFieldItem> TechnicalFields {
get; } = [];
82 public ObservableCollection<ObservableFieldItem> OtherFields {
get; } = [];
84 public bool HasPersonalFields => this.PersonalFields.Count > 0 && !this.HasDomainProperty;
85 public bool HasOrganizationFields => this.OrganizationFields.Count > 0;
86 public bool HasTechnicalFields => this.TechnicalFields.Count > 0;
87 public bool HasOtherFields => this.OtherFields.Count > 0;
89 public double SuperSafeAreaBottom =>
SafeArea.ResolveInsetsForMode(SafeAreaMode.Bottom).Bottom;
93 public ObservableCollection<Photo> Photos {
get; } = [];
95 public ImageSource? ProfilePhoto
100 Photo? Profile = this.Photos.FirstOrDefault(p => p.Attachment?.FileName.StartsWith(
"ProfilePhoto", StringComparison.OrdinalIgnoreCase) ??
false);
103 return (Profile ?? this.Photos.FirstOrDefault())?.Source;
107 public bool HasProfilePhoto => this.ProfilePhoto is not
null;
109 public bool HasPhotos => this.Photos.Count > 0;
111 public bool HasTimer => this.timer?.IsRunning ??
false;
114 public double ProfileImageSideLength
119 double Width = DeviceDisplay.Current.MainDisplayInfo.Width;
120 double Height = DeviceDisplay.Current.MainDisplayInfo.Height;
123 double AspectRatio = Height / Width;
125 double Length = AspectRatio * AspectRatio * 45;
127 if (Length > 250) Length = 250;
128 if (Length < 125) Length = 125;
135 private static readonly List<CustomFieldDefinition> customFields =
137 new CustomFieldDefinition
144 string D = identity[Constants.XmppProperties.BirthDay];
145 string M = identity[Constants.XmppProperties.BirthMonth];
146 string y = identity[Constants.XmppProperties.BirthYear];
147 if (int.TryParse(D, out int Day) && int.TryParse(M, out int Month) && int.TryParse(y, out int Year))
151 return new DateTime(Year, Month, Day).ToString(
"d", CultureInfo.CurrentCulture.DateTimeFormat);
155 ServiceRef.LogService.LogException(Ex);
160 new CustomFieldDefinition(
161 Keys: Array.Empty<
string>(),
164 GetValue: identity => identity.Id),
166 new CustomFieldDefinition(
167 Keys: Array.Empty<
string>(),
170 GetValue: identity => identity.Provider),
172 new CustomFieldDefinition(
173 Keys: Array.Empty<
string>(),
178 new CustomFieldDefinition(
179 Keys: Array.Empty<
string>(),
182 GetValue: identity => identity.Created.ToString(
"g", CultureInfo.CurrentCulture)),
184 new CustomFieldDefinition(
185 Keys: Array.Empty<
string>(),
188 GetValue: identity => identity.Updated.ToString(
"g", CultureInfo.CurrentCulture)),
190 new CustomFieldDefinition(
191 Keys: Array.Empty<
string>(),
194 GetValue: identity => identity.From.ToString(
"d", CultureInfo.CurrentCulture)),
196 new CustomFieldDefinition(
197 Keys: Array.Empty<
string>(),
200 GetValue: identity => identity.To.ToString(
"d", CultureInfo.CurrentCulture)),
206 public string BannerUri =>
207 (Application.Current?.RequestedTheme ?? AppTheme.Light)
switch
209 AppTheme.Dark => this.BannerUriDark,
210 AppTheme.Light => this.BannerUriLight,
211 _ => this.BannerUriLight
215 public ViewIdentityViewModel()
219 this.photosLoader =
new PhotosLoader();
224 if (Application.Current is not
null)
226 Application.Current.RequestedThemeChanged += (
_, __) =>
228 this.OnPropertyChanged(nameof(this.BannerUri));
232 this.timer = Application.Current?.Dispatcher.CreateTimer();
233 if (this.timer is
null)
235 this.timer.Interval = TimeSpan.FromSeconds(1);
236 this.timer.Tick += this.OnTimerTick;
238 this.qrTimer = Application.Current?.Dispatcher.CreateTimer();
239 if (this.qrTimer is
null)
242 this.qrTimer.Tick += this.OnQrTimerTick;
249 await base.OnAppearingAsync();
251 bool IsRefresh = this.hasAppeared;
252 this.hasAppeared =
true;
256 this.identity = Identity;
265 this.IdentityState = Identity.
State;
267 string Domain = Identity.GetDomain();
269 string FullJid = Identity.GetJid();
270 string[]? Jid =
null;
273 if (!
string.IsNullOrEmpty(FullJid))
275 Jid = FullJid.Split(
'@');
276 Jid[1] =
"@" + Jid[1];
277 this.FriendlyName = Jid.Length > 0 ? Jid[0] : FullJid;
278 this.SubText = Jid.Length > 1 ? Jid[1] :
string.Empty;
282 this.FriendlyName = Identity.
Id;
285 if (!
string.IsNullOrEmpty(Domain))
287 this.FriendlyName = Domain;
288 this.SubText = Identity.
Id;
289 this.HasDomainProperty =
true;
295 if (!this.HasDomainProperty)
305 if (PInfo is not
null)
307 if (!
string.IsNullOrEmpty(PInfo.
FullName))
311 this.SubText = PInfo.
BirthDate!.Value.ToShortDateString();
313 this.SubText = Jid is not
null ? Jid[1] :
string.Empty;
316 this.AgeText = PInfo.
Age.ToString(CultureInfo.InvariantCulture);
320 this.IssueDate = Identity.
From;
321 this.ExpireDate = Identity.
To;
324 this.LoadIdentityTask.Load(async ctx =>
328 List<ObservableFieldItem> PersonalList =
new();
329 List<ObservableFieldItem> OrganizationList =
new();
330 List<ObservableFieldItem> TechnicalList =
new();
331 List<ObservableFieldItem> OtherList =
new();
334 PersonalList.Add(
new ObservableFieldItem(F.Key,
new LocalizedString(F.Label, F.Label), Identity, F.IsReviewable, F.Value));
336 OrganizationList.Add(
new ObservableFieldItem(F.Key,
new LocalizedString(F.Label, F.Label), Identity, F.IsReviewable, F.Value));
338 TechnicalList.Add(
new ObservableFieldItem(F.Key,
new LocalizedString(F.Label, F.Label), Identity, F.IsReviewable, F.Value));
340 OtherList.Add(
new ObservableFieldItem(F.Key,
new LocalizedString(F.Label, F.Label), Identity, F.IsReviewable, F.Value));
343 !
string.IsNullOrEmpty(Item.Value) &&
344 DateTime.TryParse(Item.Value, out DateTime BirthDate) &&
345 BirthDate == DateTime.Today);
348 bool CanAddContact =
false;
349 bool CanRemoveContact =
false;
350 bool IsThirdPartyIdentity =
false;
353 string Jid = this.identity.GetJid();
354 if (!Jid.Equals(MyJid, StringComparison.OrdinalIgnoreCase))
359 if ((Info is not
null) &&
360 (Info.LegalIdentity is
null ||
361 (Info.LegalId != this.identity.Id &&
362 Info.LegalIdentity.Created < this.identity!.Created &&
365 Info.LegalId = this.identity.Id;
366 Info.LegalIdentity = this.identity;
373 CanAddContact = Info is
null;
374 CanRemoveContact = Info is not
null;
376 IsThirdPartyIdentity =
true;
386 await MainThread.InvokeOnMainThreadAsync(async () =>
388 this.PersonalFields.Clear(); PersonalList.ForEach(this.PersonalFields.Add);
390 this.OrganizationFields.Clear(); OrganizationList.ForEach(this.OrganizationFields.Add);
392 this.TechnicalFields.Clear(); TechnicalList.ForEach(this.TechnicalFields.Add);
394 this.OtherFields.Clear(); OtherList.ForEach(this.OtherFields.Add);
396 this.ShouldCelebrate = ShouldCelebrate;
397 this.CanAddContact = CanAddContact;
398 this.CanRemoveContact = CanRemoveContact;
400 this.IsThirdPartyIdentity = IsThirdPartyIdentity;
402 this.OnPropertyChanged(nameof(this.HasPersonalFields));
403 this.OnPropertyChanged(nameof(this.HasOrganizationFields));
404 this.OnPropertyChanged(nameof(this.HasTechnicalFields));
405 this.OnPropertyChanged(nameof(this.HasOtherFields));
406 this.OnPropertyChanged(nameof(this.HasAge));
410 this.OnPropertyChanged(nameof(this.HasTimer));
412 this.OnQrTimerTick(
this, EventArgs.Empty);
420 this.LoadPhotosTask.Load(async ctx =>
422 this.photosLoader.CancelLoadPhotos();
424 List<Photo> Buffer = [];
425 Attachment[] Atts = Identity.Attachments ?? [];
426 string[] AllowedContentTypes =
new[]
432 for (
int Index = 0; Index < Atts.Length; Index++)
434 if (!AllowedContentTypes.Contains(Atts[Index].
ContentType))
437 if (ctx.CancellationToken.IsCancellationRequested)
440 ctx.Progress.Report(Index * 100 / Math.Max(Atts.Length, 1));
441 (
byte[]? Bin,
string _,
int Rot) = await this.photosLoader.LoadOnePhoto(Atts[Index],
SignWith.LatestApprovedIdOrCurrentKeys);
443 Buffer.Add(
new Photo(Bin, Rot, Atts[Index]));
446 await MainThread.InvokeOnMainThreadAsync(() =>
449 Buffer.ForEach(this.Photos.Add);
451 this.OnPropertyChanged(nameof(this.ProfilePhoto));
452 this.OnPropertyChanged(nameof(this.HasProfilePhoto));
453 this.OnPropertyChanged(nameof(this.HasPhotos));
457 ctx.Progress.Report(100);
471 return base.OnDisappearingAsync();
474 private void OnTimerTick(
object? sender, EventArgs e)
476 MainThread.BeginInvokeOnMainThread(async () =>
478 if (this.TimerSeconds > 0)
497 private void OnQrTimerTick(
object? sender, EventArgs e)
499 MainThread.BeginInvokeOnMainThread(() =>
504 if (this.identity is
null)
515 [RelayCommand(AllowConcurrentExecutions =
false)]
516 private async Task ImageTappedAsync(
Attachment ClickedAttachment)
518 await MainThread.InvokeOnMainThreadAsync(() =>
535 MainThread.BeginInvokeOnMainThread(() =>
541 [RelayCommand(AllowConcurrentExecutions =
false)]
542 private async Task QrTappedAsync()
544 if (this.QrCodeBin is
null ||
string.IsNullOrEmpty(this.QrCodeUri))
547 await MainThread.InvokeOnMainThreadAsync(() =>
554 await Clipboard.SetTextAsync(this.QrCodeUri);
564 MainThread.BeginInvokeOnMainThread(() =>
570 [RelayCommand(AllowConcurrentExecutions =
false)]
571 private async Task FieldTappedAsync(
string Value)
573 if (this.QrCodeBin is
null ||
string.IsNullOrEmpty(this.QrCodeUri))
576 await MainThread.InvokeOnMainThreadAsync(() =>
583 await Clipboard.SetTextAsync(Value);
593 MainThread.BeginInvokeOnMainThread(() =>
599 [RelayCommand(AllowConcurrentExecutions =
false)]
600 private async Task ShareAsync()
602 if (this.identity is
null && this.LoadIdentityTask.IsSucceeded)
605 await MainThread.InvokeOnMainThreadAsync(() =>
619 MainThread.BeginInvokeOnMainThread(() =>
626 [RelayCommand(AllowConcurrentExecutions =
false)]
627 private async Task RemoveContact()
629 if (this.identity is
null)
636 string BareJid = this.identity.GetJid();
639 if (Info is not
null)
647 if (Item is not
null)
650 await MainThread.InvokeOnMainThreadAsync(() =>
652 this.CanAddContact =
true;
653 this.CanRemoveContact =
false;
662 [RelayCommand(AllowConcurrentExecutions =
false)]
663 private async Task AddContact()
665 if (this.identity is
null)
672 string BareJid = this.identity.GetJid();
684 LegalId = this.identity.Id,
686 FriendlyName = FriendlyName,
694 Info.LegalId = this.identity.Id;
695 Info.LegalIdentity = this.identity;
696 Info.FriendlyName = FriendlyName;
703 await MainThread.InvokeOnMainThreadAsync(() =>
705 this.CanAddContact =
false;
706 this.CanRemoveContact =
true;
715 [RelayCommand(AllowConcurrentExecutions =
false)]
716 private async Task OpenChat()
718 if (this.identity is
null)
722 string? Jid = this.identity.GetJid();
725 if (
string.IsNullOrEmpty(Jid))
737 #region ILinkableView
743 public override Task<string> Title => Task.FromResult(
"Test");
748 private record CustomFieldDefinition(
string[] Keys,
750 Func<LegalIdentity, LocalizedString> GetLabel,
751 Func<LegalIdentity, string?> GetValue);
Image identifiers for branding.
Custom XMPP Protocol Properties.
const string BirthDate
Full birthday
const string To
“To” / expiry date
const string From
“From” date
const string State
Current state (Approved, Rejected, …)
const string Created
When it was created
const string Updated
When it was last updated
const string Provider
Issuer / Provider
const string Neuro_Id
Identity ID
static readonly TimeSpan Qr
Qr interval
static readonly TimeSpan IdentityAllowedWatch
Allowed time to watch an Identity
static string CreateIdUri(string id)
Generates a IoT ID Uri form the specified id.
XMPP Protocol Properties.
const string BirthYear
Birth Year
const string BirthDay
Birth Day
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 BirthDate
Looks up a localized string similar to Birth Date.
static string Updated
Looks up a localized string similar to Updated.
static string Provider
Looks up a localized string similar to Provider.
static string Issued
Looks up a localized string similar to Issued.
static string Status
Looks up a localized string similar to Status.
static string IdCopiedSuccessfully
Looks up a localized string similar to A link to the ID was copied to the clipboard....
static string TagValueCopiedToClipboard
Looks up a localized string similar to Tag value copied to clipboard.
static string PersonalId
Looks up a localized string similar to Personal ID.
static string Expires
Looks up a localized string similar to Expires.
static string Created
Looks up a localized string similar to Created.
static string SuccessTitle
Looks up a localized string similar to Success.
static string NeuroID
Looks up a localized string similar to Neuro-ID.
Base class that references services in the app.
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 IPopupService PopupService
Popup service for presenting application popups.
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.
Provides a data-binding friendly mechanism to manage and report the status of asynchronous operations...
virtual ? object GetValue(string PropertyName)
Gets the value of a property in the view model.
Holds navigation parameters specific to views displaying a list of contacts.
A page that displays a list of the current user's contacts.
ViewModel for a single property field of a LegalIdentity.
override async Task OnAppearingAsync()
Method called when view is appearing on the screen.
override Task OnDisappearingAsync()
Method called when view is disappearing from the screen.
A view model that holds the XMPP state.
const string ContentTypeJpg
image/jpeg
const string ContentTypePng
image/png
Contains a reference to an attachment assigned to a legal object.
string ContentType
Internet Content Type of binary attachment.
DateTime From
From what point in time the legal identity is valid.
DateTime To
To what point in time the legal identity is valid.
IdentityState State
Current state of identity
static PersonalInformation GetPersonalInformation(IEnumerable< Property > Properties)
Gets personal information from an identity.
string Id
ID of the legal identity
Maintains information about an item in the roster.
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 Update(object Object)
Updates an object in the database.
static async Task Delete(object Object)
Deletes an object in the database.
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Task GoToAsync(string Route)
Navigates to the specified route and pushes the page onto the navigation stack.
Task< bool > DisplayAlert(string Title, string Message, string? Accept=null, string? Cancel=null)
Displays an alert/message box to the user.
Task Flush()
Persists any pending changes.
class Photo(byte[] Binary, int Rotation, Attachment? Attachment)
Class containing information about a photo.
BackMethod
Navigation Back Method
IdentityState
Lists recognized legal identity states.
SignWith
Options on what keys to use when signing data.