Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ApplyIdViewModel.cs
1using System.Collections.ObjectModel;
2using System.ComponentModel;
3using CommunityToolkit.Mvvm.ComponentModel;
4using CommunityToolkit.Mvvm.Input;
15using SkiaSharp;
16using Waher.Content;
21
23{
28 {
29 private const string profilePhotoFileName = "ProfilePhoto.jpg";
30 private readonly string localPhotoFileName;
31 private readonly PhotosLoader photosLoader;
32 private LegalIdentityAttachment? photo;
33 private ServiceProviderWithLegalId[]? peerReviewServices = null;
34
39 : base()
40 {
41 this.localPhotoFileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), profilePhotoFileName);
42 this.photosLoader = new PhotosLoader();
43 this.countries = new ObservableCollection<ISO_3166_Country>(ISO_3166_1.Countries);
44 this.genders = new ObservableCollection<ISO_5218_Gender>(ISO_5218.Genders);
45 }
46
47 protected override async Task OnInitialize()
48 {
49 this.ApplicationId = null;
50
51 if (ServiceRef.TagProfile.IdentityApplication is not null)
52 {
53 if (ServiceRef.TagProfile.IdentityApplication.IsDiscarded())
54 await ServiceRef.TagProfile.SetIdentityApplication(null, true);
55 }
56
57 LegalIdentity? IdentityReference;
58
59 if (ServiceRef.TagProfile.IdentityApplication is not null)
60 {
61 IdentityReference = ServiceRef.TagProfile.IdentityApplication;
62 this.ApplicationSent = true;
63 this.ApplicationId = IdentityReference.Id;
64 this.NrReviews = ServiceRef.TagProfile.NrReviews;
65
66 await Task.Run(this.LoadFeaturedPeerReviewers);
67 }
68 else
69 {
70 this.ApplicationSent = false;
71 IdentityReference = ServiceRef.TagProfile.LegalIdentity;
72 this.NrReviews = 0;
73 this.peerReviewServices = null;
74 this.HasFeaturedPeerReviewers = false;
75 }
76
78
79 if (Args is not null)
80 {
81 this.Personal = Args.Personal;
82 this.Organizational = Args.Organizational;
83 }
84 else if (IdentityReference is not null)
85 {
86 this.Organizational = IdentityReference.IsOrganizational();
87 this.Personal = !this.Organizational;
88 }
89
90 if (IdentityReference is not null)
91 {
92 await this.SetProperties(IdentityReference, Args?.ReusePhoto ?? true, true, true, this.Organizational, true);
93
94 if (string.IsNullOrEmpty(this.OrgCountryCode) && this.Organizational)
95 this.OrgCountryCode = this.CountryCode;
96
97 if (string.IsNullOrEmpty(this.NationalityCode) && this.RequiresNationality)
98 this.NationalityCode = this.CountryCode;
99 }
100
101 this.RequiresOrgName = this.Organizational;
102 this.RequiresOrgDepartment = this.Organizational;
103 this.RequiresOrgRole = this.Organizational;
104 this.RequiresOrgNumber = this.Organizational;
105
106 ServiceRef.XmppService.IdentityApplicationChanged += this.XmppService_IdentityApplicationChanged;
107
108 await base.OnInitialize();
109
110 if (!this.HasApplicationAttributes && this.IsConnected)
111 await Task.Run(this.LoadApplicationAttributes);
112 }
113
114 protected override Task OnDispose()
115 {
116 this.photosLoader.CancelLoadPhotos();
117
118 ServiceRef.XmppService.IdentityApplicationChanged -= this.XmppService_IdentityApplicationChanged;
119
120 return base.OnDispose();
121 }
122
123 private Task XmppService_IdentityApplicationChanged(object? Sender, LegalIdentityEventArgs e)
124 {
125 MainThread.BeginInvokeOnMainThread(async () =>
126 {
127 this.ApplicationSent = ServiceRef.TagProfile.IdentityApplication is not null;
128 this.NrReviews = ServiceRef.TagProfile.NrReviews;
129
130 if (this.ApplicationId is not null && this.ApplicationId == ServiceRef.TagProfile.LegalIdentity?.Id)
132 else
133 {
134 if (this.ApplicationSent)
135 {
136 if (this.peerReviewServices is null)
137 await Task.Run(this.LoadFeaturedPeerReviewers);
138 }
139 else
140 {
141 this.peerReviewServices = null;
142 this.HasFeaturedPeerReviewers = false;
143 }
144
145 if (!this.ApplicationSent && !this.IsRevoking)
146 {
148 ServiceRef.Localizer[nameof(AppResources.Rejected)],
149 ServiceRef.Localizer[nameof(AppResources.YourApplicationWasRejected)]);
150 }
151 }
152 });
153
154 return Task.CompletedTask;
155 }
156
158 protected override async Task XmppService_ConnectionStateChanged(object? Sender, XmppState NewState)
159 {
160 await base.XmppService_ConnectionStateChanged(Sender, NewState);
161 this.OnPropertyChanged(nameof(this.ApplicationSentAndConnected));
162 }
163
165 protected override async Task OnConnected()
166 {
167 await base.OnConnected();
168
169 if (!this.HasApplicationAttributes && this.IsConnected)
170 await Task.Run(this.LoadApplicationAttributes);
171 }
172
174 public override void SetIsBusy(bool IsBusy)
175 {
176 base.SetIsBusy(IsBusy);
177 this.NotifyCommandsCanExecuteChanged();
178 }
179
189 protected async Task SetProperties(LegalIdentity Identity, bool SetPhoto, bool ClearPropertiesNotFound, bool SetPersonalProperties,
190 bool SetOrganizationalProperties, bool SetAppProperties)
191 {
192 await base.SetProperties(Identity, ClearPropertiesNotFound, SetPersonalProperties, SetOrganizationalProperties, SetAppProperties);
193
194 int i = 0;
195
196 foreach (ISO_3166_Country Country in this.Countries)
197 {
198 if (Country.Alpha2 == this.CountryCode)
199 {
200 this.Countries.Move(i, 0);
201 break;
202 }
203
204 i++;
205 }
206
207 if (SetPhoto && Identity?.Attachments is not null)
208 {
209 Photo? First = await this.photosLoader.LoadPhotos(Identity.Attachments, SignWith.LatestApprovedIdOrCurrentKeys);
210
211 if (First is null)
212 {
213 if (ClearPropertiesNotFound)
214 {
215 this.photo = null;
216 this.Image = null;
217 this.ImageBin = null;
218 this.HasPhoto = false;
219 }
220 }
221 else
222 {
223 this.Image = First.Source;
224 this.ImageBin = First.Binary;
225 this.HasPhoto = true;
226 }
227 }
228 }
229
230 private async Task LoadApplicationAttributes()
231 {
232 try
233 {
234 IdApplicationAttributesEventArgs e = await ServiceRef.XmppService.GetIdApplicationAttributes();
235 if (e.Ok)
236 {
237 MainThread.BeginInvokeOnMainThread(() =>
238 {
239 bool RequiresFirstName = false;
240 bool RequiresMiddleNames = false;
241 bool RequiresLastNames = false;
242 bool RequiresPersonalNumber = false;
243 bool RequiresAddress = false;
244 bool RequiresAddress2 = false;
245 bool RequiresZipCode = false;
246 bool RequiresArea = false;
247 bool RequiresCity = false;
248 bool RequiresRegion = false;
249 bool RequiresCountry = false;
250 bool RequiresNationality = false;
251 bool RequiresGender = false;
252 bool RequiresBirthDate = false;
253
254 foreach (string Name in e.RequiredProperties)
255 {
256 switch (Name)
257 {
258 case Constants.XmppProperties.FirstName:
259 RequiresFirstName = true;
260 break;
261
262 case Constants.XmppProperties.MiddleNames:
263 RequiresMiddleNames = true;
264 break;
265
266 case Constants.XmppProperties.LastNames:
267 RequiresLastNames = true;
268 break;
269
270 case Constants.XmppProperties.PersonalNumber:
271 RequiresPersonalNumber = true;
272 break;
273
274 case Constants.XmppProperties.Address:
275 RequiresAddress = true;
276 break;
277
278 case Constants.XmppProperties.Address2:
279 RequiresAddress2 = true;
280 break;
281
282 case Constants.XmppProperties.Area:
283 RequiresArea = true;
284 break;
285
286 case Constants.XmppProperties.City:
287 RequiresCity = true;
288 break;
289
290 case Constants.XmppProperties.ZipCode:
291 RequiresZipCode = true;
292 break;
293
294 case Constants.XmppProperties.Region:
295 RequiresRegion = true;
296 break;
297
298 case Constants.XmppProperties.Country:
299 RequiresCountry = true;
300 break;
301
302 case Constants.XmppProperties.Nationality:
303 RequiresNationality = true;
304 break;
305
306 case Constants.XmppProperties.Gender:
307 RequiresGender = true;
308 break;
309
310 case Constants.XmppProperties.BirthDay:
311 case Constants.XmppProperties.BirthMonth:
312 case Constants.XmppProperties.BirthYear:
313 RequiresBirthDate = true;
314 break;
315 }
316 }
317
318 this.PeerReview = e.PeerReview;
319 this.NrPhotos = e.NrPhotos;
320 this.NrReviewers = e.NrReviewers;
321 this.RequiresCountryIso3166 = e.Iso3166;
322 this.RequiresFirstName = RequiresFirstName;
323 this.RequiresMiddleNames = RequiresMiddleNames;
324 this.RequiresLastNames = RequiresLastNames;
325 this.RequiresPersonalNumber = RequiresPersonalNumber;
326 this.RequiresAddress = RequiresAddress;
327 this.RequiresAddress2 = RequiresAddress2;
328 this.RequiresZipCode = RequiresZipCode;
329 this.RequiresArea = RequiresArea;
330 this.RequiresCity = RequiresCity;
331 this.RequiresRegion = RequiresRegion;
332 this.RequiresCountry = RequiresCountry;
333 this.RequiresNationality = RequiresNationality;
334 this.RequiresGender = RequiresGender;
335 this.RequiresBirthDate = RequiresBirthDate;
336 this.RequiresOrgAddress = this.Organizational && RequiresAddress;
337 this.RequiresOrgAddress2 = this.Organizational && RequiresAddress2;
338 this.RequiresOrgZipCode = this.Organizational && RequiresZipCode;
339 this.RequiresOrgArea = this.Organizational && RequiresArea;
340 this.RequiresOrgCity = this.Organizational && RequiresCity;
341 this.RequiresOrgRegion = this.Organizational && RequiresRegion;
342 this.RequiresOrgCountry = this.Organizational && RequiresCountry;
343 this.HasApplicationAttributes = true;
344 });
345 }
346 }
347 catch (Exception ex)
348 {
349 ServiceRef.LogService.LogException(ex);
350 }
351 }
352
353 #region Properties
354
358 [ObservableProperty]
359 [NotifyCanExecuteChangedFor(nameof(ApplyCommand))]
360 private bool consent;
361
365 [ObservableProperty]
366 [NotifyCanExecuteChangedFor(nameof(ApplyCommand))]
367 private bool correct;
368
372 [ObservableProperty]
373 [NotifyCanExecuteChangedFor(nameof(ApplyCommand))]
374 private bool hasApplicationAttributes;
375
379 [ObservableProperty]
380 [NotifyCanExecuteChangedFor(nameof(ApplyCommand))]
381 private int nrPhotos;
382
386 [ObservableProperty]
387 [NotifyCanExecuteChangedFor(nameof(ApplyCommand))]
388 private int nrReviewers;
389
393 [ObservableProperty]
394 private int nrReviews;
395
399 [ObservableProperty]
400 [NotifyCanExecuteChangedFor(nameof(ApplyCommand))]
401 [NotifyPropertyChangedFor(nameof(FeaturedPeerReviewers))]
402 private bool peerReview;
403
407 [ObservableProperty]
408 [NotifyCanExecuteChangedFor(nameof(ApplyCommand))]
409 [NotifyCanExecuteChangedFor(nameof(ScanQrCodeCommand))]
410 [NotifyCanExecuteChangedFor(nameof(RequestReviewCommand))]
411 [NotifyCanExecuteChangedFor(nameof(RevokeApplicationCommand))]
412 [NotifyPropertyChangedFor(nameof(CanEdit))]
413 [NotifyPropertyChangedFor(nameof(CanRemovePhoto))]
414 [NotifyPropertyChangedFor(nameof(CanTakePhoto))]
415 [NotifyPropertyChangedFor(nameof(ApplicationSentAndConnected))]
416 [NotifyPropertyChangedFor(nameof(CanRequestFeaturedPeerReviewer))]
417 [NotifyPropertyChangedFor(nameof(FeaturedPeerReviewers))]
418 private bool applicationSent;
419
423 [ObservableProperty]
424 private bool personal;
425
429 [ObservableProperty]
430 private bool organizational;
431
435 [ObservableProperty]
436 [NotifyPropertyChangedFor(nameof(CanRemovePhoto))]
437 [NotifyCanExecuteChangedFor(nameof(ApplyCommand))]
438 [NotifyCanExecuteChangedFor(nameof(RemovePhotoCommand))]
439 private bool hasPhoto;
440
444 [ObservableProperty]
445 private ImageSource? image;
446
450 [ObservableProperty]
451 private byte[]? imageBin;
452
456 [ObservableProperty]
457 private int imageRotation;
458
462 public bool CanEdit => !this.ApplicationSent;
463
467 public bool CanRemovePhoto => this.CanEdit && this.HasPhoto;
468
472 public bool CanTakePhoto => this.CanEdit && MediaPicker.IsCaptureSupported;
473
477 public bool ApplicationSentAndConnected => this.ApplicationSent && this.IsConnected;
478
482 [ObservableProperty]
483 private bool isApplying;
484
488 [ObservableProperty]
489 private bool isRevoking;
490
494 [ObservableProperty]
495 private string? applicationId;
496
500 [ObservableProperty]
501 [NotifyCanExecuteChangedFor(nameof(this.RequestReviewCommand))]
502 [NotifyPropertyChangedFor(nameof(CanRequestFeaturedPeerReviewer))]
503 [NotifyPropertyChangedFor(nameof(FeaturedPeerReviewers))]
504 private bool hasFeaturedPeerReviewers;
505
509 public bool CanRequestFeaturedPeerReviewer => this.ApplicationSent && this.HasFeaturedPeerReviewers;
510
514 public bool FeaturedPeerReviewers => this.CanRequestFeaturedPeerReviewer && this.PeerReview;
515
519 [ObservableProperty]
520 private ObservableCollection<ISO_3166_Country> countries;
521
522 [ObservableProperty]
523 [NotifyPropertyChangedFor(nameof(NationalityCode))]
524 [NotifyCanExecuteChangedFor(nameof(ApplyCommand))]
525 private ISO_3166_Country? nationality;
526
530 [ObservableProperty]
531 private ObservableCollection<ISO_5218_Gender> genders;
532
533 [ObservableProperty]
534 [NotifyPropertyChangedFor(nameof(GenderCode))]
535 [NotifyCanExecuteChangedFor(nameof(ApplyCommand))]
536 private ISO_5218_Gender? gender;
537
539 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
540 {
541 switch (e.PropertyName)
542 {
543 case nameof(this.Nationality):
544 this.NationalityCode = this.Nationality?.Alpha2 ?? string.Empty;
545 break;
546
547 case nameof(this.Gender):
548 this.GenderCode = this.Gender?.Letter ?? string.Empty;
549 break;
550 }
551
552 base.OnPropertyChanged(e);
553 }
554
555 #endregion
556
557 #region Commands
558
562 [RelayCommand]
563 public void ToggleConsent()
564 {
565 this.Consent = !this.Consent;
566 }
567
571 [RelayCommand]
572 public void ToggleCorrect()
573 {
574 this.Correct = !this.Correct;
575 }
576
580 public override bool CanApply
581 {
582 get
583 {
584 if (!this.CanExecuteCommands || !this.Consent || !this.Correct || this.ApplicationSent || !this.HasPhoto)
585 return false;
586
587 if (this.HasApplicationAttributes)
588 {
589 if (!this.FirstNameOk ||
590 !this.MiddleNamesOk ||
591 !this.LastNamesOk ||
592 !this.PersonalNumberOk ||
593 !this.AddressOk ||
594 !this.Address2Ok ||
595 !this.ZipCodeOk ||
596 !this.AreaOk ||
597 !this.CityOk ||
598 !this.RegionOk ||
599 !this.CountryOk ||
600 !this.NationalityOk ||
601 !this.GenderOk ||
602 !this.BirthDateOk)
603 {
604 return false;
605 }
606
607 if (this.RequiresCountryIso3166 && !ISO_3166_1.TryGetCountryByCode(this.CountryCode, out _))
608 return false;
609
610 if (this.Organizational)
611 {
612 if (!this.OrgNameOk ||
613 !this.OrgDepartmentOk ||
614 !this.OrgRoleOk ||
615 !this.OrgNumberOk ||
616 !this.OrgAddressOk ||
617 !this.OrgAddress2Ok ||
618 !this.OrgZipCodeOk ||
619 !this.OrgAreaOk ||
620 !this.OrgCityOk ||
621 !this.OrgRegionOk ||
622 !this.OrgCountryOk)
623 {
624 return false;
625 }
626 }
627 }
628
629 return true;
630 }
631 }
632
636 protected override async Task Apply()
637 {
638 if (this.ApplicationSent)
639 return;
640
641 if (!await AreYouSure(ServiceRef.Localizer[nameof(AppResources.AreYouSureYouWantToSendThisIdApplication)]))
642 return;
643
644 if (!await App.AuthenticateUser(AuthenticationPurpose.SignApplication, true))
645 return;
646
647 try
648 {
649 LegalIdentityAttachment[] Photos = this.photo is null ? [] : [this.photo];
650
651 this.SetIsBusy(true);
652 this.IsApplying = true;
653 NumberInformation Info = await PersonalNumberSchemes.Validate(this.CountryCode!, this.PersonalNumber!);
654 this.PersonalNumber = Info.PersonalNumber;
655
656
657 bool HasIdWithPrivateKey = ServiceRef.TagProfile.LegalIdentity is not null &&
658 await ServiceRef.XmppService.HasPrivateKey(ServiceRef.TagProfile.LegalIdentity.Id);
659
660 (bool Succeeded, LegalIdentity? AddedIdentity) = await ServiceRef.NetworkService.TryRequest(() =>
661 ServiceRef.XmppService.AddLegalIdentity(this, !HasIdWithPrivateKey, Photos));
662
663 if (Succeeded && AddedIdentity is not null)
664 {
665 await ServiceRef.TagProfile.SetIdentityApplication(AddedIdentity, true);
666 this.ApplicationSent = true;
667 this.ApplicationId = AddedIdentity.Id;
668
669 await Task.Run(this.LoadFeaturedPeerReviewers);
670
671 if (this.HasPhoto)
672 {
673 Attachment? FirstImage = AddedIdentity.Attachments.GetFirstImageAttachment();
674
675 if (FirstImage is not null && this.ImageBin is not null)
676 await ServiceRef.AttachmentCacheService.Add(FirstImage.Url, AddedIdentity.Id, true, this.ImageBin, FirstImage.ContentType);
677 }
678 }
679 }
680 catch (Exception ex)
681 {
682 ServiceRef.LogService.LogException(ex);
684 }
685 finally
686 {
687 this.SetIsBusy(false);
688 this.IsApplying = false;
689 }
690 }
691
695 [RelayCommand(CanExecute = nameof(ApplicationSent))]
696 private async Task RevokeApplication()
697 {
698 LegalIdentity? Application = ServiceRef.TagProfile.IdentityApplication;
699 if (Application is null)
700 {
701 this.ApplicationSent = false;
702 this.peerReviewServices = null;
703 this.HasFeaturedPeerReviewers = false;
704 return;
705 }
706
707 if (!await AreYouSure(ServiceRef.Localizer[nameof(AppResources.AreYouSureYouWantToRevokeTheCurrentIdApplication)]))
708 return;
709
710 if (!await App.AuthenticateUser(AuthenticationPurpose.RevokeApplication, true))
711 return;
712
713 try
714 {
715 this.SetIsBusy(true);
716 this.IsRevoking = true; // Will be cleared from event-handle.
717
718 try
719 {
720 await ServiceRef.XmppService.ObsoleteLegalIdentity(Application.Id);
721 }
722 catch (ForbiddenException)
723 {
724 // Ignore. Application may have been rejected or elapsed outside of the
725 // scope of the app.
726 }
727
728 await ServiceRef.TagProfile.SetIdentityApplication(null, true);
729 this.ApplicationSent = false;
730 this.peerReviewServices = null;
731 this.HasFeaturedPeerReviewers = false;
732 }
733 catch (Exception ex)
734 {
735 ServiceRef.LogService.LogException(ex);
737 this.IsRevoking = false;
738 }
739 finally
740 {
741 this.SetIsBusy(false);
742 }
743 }
744
748 [RelayCommand(CanExecute = nameof(ApplicationSent))]
749 private async Task ScanQrCode()
750 {
751 string? Url = await Services.UI.QR.QrCode.ScanQrCode(nameof(AppResources.QrPageTitleScanPeerId),
752 [
754 ]);
755
756 if (string.IsNullOrEmpty(Url) || !Constants.UriSchemes.StartsWithIdScheme(Url))
757 return;
758
759 await this.SendPeerReviewRequest(Constants.UriSchemes.RemoveScheme(Url));
760 }
761
762 private async Task SendPeerReviewRequest(string? ReviewerId)
763 {
764 LegalIdentity? ToReview = ServiceRef.TagProfile.IdentityApplication;
765 if (ToReview is null || string.IsNullOrEmpty(ReviewerId))
766 return;
767
768 try
769 {
770 this.SetIsBusy(true);
771
772 await ServiceRef.XmppService.PetitionPeerReviewId(ReviewerId, ToReview, Guid.NewGuid().ToString(),
773 ServiceRef.Localizer[nameof(AppResources.CouldYouPleaseReviewMyIdentityInformation)]);
774
775 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.PetitionSent)],
776 ServiceRef.Localizer[nameof(AppResources.APetitionHasBeenSentToYourPeer)]);
777 }
778 catch (Exception ex)
779 {
780 ServiceRef.LogService.LogException(ex);
782 }
783 finally
784 {
785 this.SetIsBusy(false);
786 }
787 }
788
792 [RelayCommand(CanExecute = nameof(CanTakePhoto))]
793 private async Task TakePhoto()
794 {
795 if (!this.CanTakePhoto)
796 return;
797
798 try
799 {
800 FileResult? Result = await MediaPicker.Default.CapturePhotoAsync(new MediaPickerOptions()
801 {
802 Title = ServiceRef.Localizer[nameof(AppResources.TakePhotoOfYourself)]
803 });
804
805 if (Result is null)
806 return;
807
808 Stream stream = await Result.OpenReadAsync();
809 await this.AddPhoto(stream, Result.FullPath, true);
810 }
811 catch (Exception ex)
812 {
813 ServiceRef.LogService.LogException(ex);
815 }
816 }
817
826 public async Task AddPhoto(byte[] Bin, string ContentType, int Rotation, bool saveLocalCopy, bool showAlert)
827 {
828 if (Bin.Length > ServiceRef.TagProfile.HttpFileUploadMaxSize)
829 {
830 if (showAlert)
832 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
833 ServiceRef.Localizer[nameof(AppResources.PhotoIsTooLarge)]);
834
835 return;
836 }
837
838 this.RemovePhoto(saveLocalCopy);
839
840 if (saveLocalCopy)
841 {
842 try
843 {
844 File.WriteAllBytes(this.localPhotoFileName, Bin);
845 }
846 catch (Exception ex)
847 {
848 ServiceRef.LogService.LogException(ex);
849 }
850 }
851
852 this.photo = new LegalIdentityAttachment(this.localPhotoFileName, ContentType, Bin);
853 this.ImageRotation = Rotation;
854 this.Image = ImageSource.FromStream(() => new MemoryStream(Bin));
855 this.ImageBin = Bin;
856 this.HasPhoto = true;
857 }
858
865 public async Task AddPhoto(Stream InputStream, string FilePath, bool SaveLocalCopy)
866 {
867 SKData? ImageData = null;
868
869 try
870 {
871 bool FallbackOriginal = true;
872
873 if (SaveLocalCopy)
874 {
875 // try to downscale and comress the image
876 ImageData = CompressImage(InputStream);
877
878 if (ImageData is not null)
879 {
880 FallbackOriginal = false;
881 await this.AddPhoto(ImageData.ToArray(), Constants.MimeTypes.Jpeg, 0, SaveLocalCopy, true);
882 }
883 }
884
885 if (FallbackOriginal)
886 {
887 byte[] Bin = File.ReadAllBytes(FilePath);
888 if (!InternetContent.TryGetContentType(Path.GetExtension(FilePath), out string ContentType))
889 ContentType = "application/octet-stream";
890
891 await this.AddPhoto(Bin, ContentType, PhotosLoader.GetImageRotation(Bin), SaveLocalCopy, true);
892 }
893 }
894 catch (Exception ex)
895 {
896 ServiceRef.LogService.LogException(ex);
898 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
899 ServiceRef.Localizer[nameof(AppResources.FailedToLoadPhoto)]);
900 }
901 finally
902 {
903 ImageData?.Dispose();
904 }
905 }
906
907 private static SKData? CompressImage(Stream inputStream)
908 {
909 try
910 {
911 using SKManagedStream ManagedStream = new(inputStream);
912 using SKData ImageData = SKData.Create(ManagedStream);
913
914 SKCodec Codec = SKCodec.Create(ImageData);
915 SKBitmap SkBitmap = SKBitmap.Decode(ImageData);
916
917 SkBitmap = HandleOrientation(SkBitmap, Codec.EncodedOrigin);
918
919 bool Resize = false;
920 int Height = SkBitmap.Height;
921 int Width = SkBitmap.Width;
922
923 // downdsample to FHD
924 if ((Width >= Height) && (Width > 1920))
925 {
926 Height = (int)(Height * (1920.0 / Width) + 0.5);
927 Width = 1920;
928 Resize = true;
929 }
930 else if ((Height > Width) && (Height > 1920))
931 {
932 Width = (int)(Width * (1920.0 / Height) + 0.5);
933 Height = 1920;
934 Resize = true;
935 }
936
937 if (Resize)
938 {
939 SKImageInfo Info = SkBitmap.Info;
940 SKImageInfo NewInfo = new(Width, Height, Info.ColorType, Info.AlphaType, Info.ColorSpace);
941 SkBitmap = SkBitmap.Resize(NewInfo, SKFilterQuality.High);
942 }
943
944 return SkBitmap.Encode(SKEncodedImageFormat.Jpeg, 80);
945 }
946 catch (Exception ex)
947 {
948 ServiceRef.LogService.LogException(ex);
949 return null;
950 }
951 }
952
953 private static SKBitmap HandleOrientation(SKBitmap Bitmap, SKEncodedOrigin Orientation)
954 {
955 SKBitmap Rotated;
956
957 switch (Orientation)
958 {
959 case SKEncodedOrigin.BottomRight:
960 Rotated = new SKBitmap(Bitmap.Width, Bitmap.Height);
961
962 using (SKCanvas Surface = new(Rotated))
963 {
964 Surface.RotateDegrees(180, Bitmap.Width / 2, Bitmap.Height / 2);
965 Surface.DrawBitmap(Bitmap, 0, 0);
966 }
967 break;
968
969 case SKEncodedOrigin.RightTop:
970 Rotated = new SKBitmap(Bitmap.Height, Bitmap.Width);
971
972 using (SKCanvas Surface = new(Rotated))
973 {
974 Surface.Translate(Rotated.Width, 0);
975 Surface.RotateDegrees(90);
976 Surface.DrawBitmap(Bitmap, 0, 0);
977 }
978 break;
979
980 case SKEncodedOrigin.LeftBottom:
981 Rotated = new SKBitmap(Bitmap.Height, Bitmap.Width);
982
983 using (SKCanvas Surface = new(Rotated))
984 {
985 Surface.Translate(0, Rotated.Height);
986 Surface.RotateDegrees(270);
987 Surface.DrawBitmap(Bitmap, 0, 0);
988 }
989 break;
990
991 default:
992 return Bitmap;
993 }
994
995 return Rotated;
996 }
997
998 private void RemovePhoto(bool RemoveFileOnDisc)
999 {
1000 try
1001 {
1002 this.photo = null;
1003 this.Image = null;
1004 this.ImageBin = null;
1005 this.HasPhoto = false;
1006
1007 if (RemoveFileOnDisc && File.Exists(this.localPhotoFileName))
1008 File.Delete(this.localPhotoFileName);
1009 }
1010 catch (Exception ex)
1011 {
1012 ServiceRef.LogService.LogException(ex);
1013 }
1014 }
1015
1019 [RelayCommand(CanExecute = nameof(CanEdit))]
1020 private async Task PickPhoto()
1021 {
1022 try
1023 {
1024 FileResult? Result = await MediaPicker.Default.PickPhotoAsync(new MediaPickerOptions()
1025 {
1026 Title = ServiceRef.Localizer[nameof(AppResources.PickPhotoOfYourself)]
1027 });
1028
1029 if (Result is null)
1030 return;
1031
1032 Stream stream = await Result.OpenReadAsync();
1033 await this.AddPhoto(stream, Result.FullPath, true);
1034 }
1035 catch (Exception ex)
1036 {
1037 ServiceRef.LogService.LogException(ex);
1039 }
1040 }
1041
1045 [RelayCommand(CanExecute = nameof(CanRemovePhoto))]
1046 private void RemovePhoto()
1047 {
1048 this.RemovePhoto(true);
1049 }
1050
1051 private async Task LoadFeaturedPeerReviewers()
1052 {
1053 await ServiceRef.NetworkService.TryRequest(async () =>
1054 {
1055 this.peerReviewServices = await ServiceRef.XmppService.GetServiceProvidersForPeerReviewAsync();
1056
1057 MainThread.BeginInvokeOnMainThread(() =>
1058 {
1059 this.HasFeaturedPeerReviewers = this.peerReviewServices.Length > 0;
1060 });
1061 });
1062 }
1063
1067 [RelayCommand(CanExecute = nameof(CanRequestFeaturedPeerReviewer))]
1068 private async Task RequestReview()
1069 {
1070 if (this.peerReviewServices is null)
1071 await this.LoadFeaturedPeerReviewers();
1072
1073 if ((this.peerReviewServices?.Length ?? 0) > 0)
1074 {
1075 List<ServiceProviderWithLegalId> ServiceProviders = [.. this.peerReviewServices, new RequestFromPeer()];
1076
1077 ServiceProvidersNavigationArgs e = new([.. ServiceProviders],
1078 ServiceRef.Localizer[nameof(AppResources.RequestReview)],
1079 ServiceRef.Localizer[nameof(AppResources.SelectServiceProviderPeerReview)]);
1080
1082
1083 if (e.ServiceProvider is not null)
1084 {
1086
1088 !string.IsNullOrEmpty(ServiceProviderWithLegalId.LegalId))
1089 {
1091 {
1092 if (!await ServiceRef.NetworkService.TryRequest(async () =>
1093 await ServiceRef.XmppService.SelectPeerReviewService(ServiceProvider.Id, ServiceProvider.Type)))
1094 {
1095 return;
1096 }
1097 }
1098
1099 await this.SendPeerReviewRequest(ServiceProviderWithLegalId.LegalId);
1100 return;
1101 }
1102 else if (ServiceProvider is null)
1103 return;
1104 }
1105 }
1106
1107 await this.ScanQrCode();
1108 }
1109
1110 #endregion
1111 }
1112}
The Application class, representing an instance of the Neuro-Access app.
Definition: App.xaml.cs:69
static Task< bool > AuthenticateUser(AuthenticationPurpose Purpose, bool Force=false)
Authenticates the user using the configured authentication method.
Definition: App.xaml.cs:981
const string Jpeg
The JPEG MIME type.
Definition: Constants.cs:237
static bool StartsWithIdScheme(string Url)
Checks if the specified code starts with the IoT ID scheme.
Definition: Constants.cs:177
static ? string RemoveScheme(string Url)
Removes the URI Schema from an URL.
Definition: Constants.cs:218
const string IotId
The IoT ID URI Scheme (iotid)
Definition: Constants.cs:99
A set of never changing property constants and helpful values.
Definition: Constants.cs:7
Conversion between Country Names and ISO-3166-1 country codes.
Definition: ISO_3166_1.cs:10
static bool TryGetCountryByCode(string? CountryCode, [NotNullWhen(true)] out ISO_3166_Country? Country)
Tries to get the country, given its country code.
Definition: ISO_3166_1.cs:71
static ISO_3166_Country[] Countries
This collection built from Wikipedia entry on ISO3166-1 on 9th Feb 2016
Definition: ISO_3166_1.cs:37
Static class containing ISO 5218 gender codes
Definition: ISO_5218.cs:9
static readonly ISO_5218_Gender[] Genders
Available gender codes
Definition: ISO_5218.cs:58
string? PersonalNumber
String representation of the personal number.
Personal Number Schemes available in different countries.
static async Task< NumberInformation > Validate(string CountryCode, string PersonalNumber)
Checks if a personal number is valid, in accordance with registered personal number schemes.
Base class that references services in the app.
Definition: ServiceRef.cs:31
static ILogService LogService
Log service.
Definition: ServiceRef.cs:91
static INetworkService NetworkService
Network service.
Definition: ServiceRef.cs:103
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:55
static IAttachmentCacheService AttachmentCacheService
AttachmentCache service.
Definition: ServiceRef.cs:151
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:79
static IStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:235
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:67
The view model to bind to for when displaying the an application for a Personal ID.
async Task AddPhoto(Stream InputStream, string FilePath, bool SaveLocalCopy)
Adds a photo from a filestream to use as a profile photo.
async Task AddPhoto(byte[] Bin, string ContentType, int Rotation, bool saveLocalCopy, bool showAlert)
Adds a photo from the specified path to use as a profile photo.
override void SetIsBusy(bool IsBusy)
Sets the IsBusy property.
override async Task Apply()
Executes the application command.
override Task OnDispose()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
override async Task OnInitialize()
Method called when view is initialized for the first time. Use this method to implement registration ...
ApplyIdViewModel()
Creates an instance of the ApplyIdViewModel class.
override void OnPropertyChanged(PropertyChangedEventArgs e)
override async Task XmppService_ConnectionStateChanged(object? Sender, XmppState NewState)
Listens to connection state changes from the XMPP server.
override async Task OnConnected()
Gets called when the app gets connected.
async Task SetProperties(LegalIdentity Identity, bool SetPhoto, bool ClearPropertiesNotFound, bool SetPersonalProperties, bool SetOrganizationalProperties, bool SetAppProperties)
Sets the properties of the view model.
A page to display when the user wants to view an identity.
Static class managing encoding and decoding of internet content.
static bool TryGetContentType(string FileExtension, out string ContentType)
Tries to get the content type of an item, given its file extension.
Contains a reference to an attachment assigned to a legal object.
Definition: Attachment.cs:9
string ContentType
Internet Content Type of binary attachment.
Definition: Attachment.cs:47
string Url
URL to retrieve attachment, if provided.
Definition: Attachment.cs:65
Contains information about a service provider.
ServiceProvider()
Contains information about a service provider.
The requesting entity does not possess the necessary permissions to perform an action that only certa...
Task DisplayException(Exception Exception, string? Title=null)
Displays an alert/message box to the user.
Task GoToAsync(string Route, BackMethod BackMethod=BackMethod.Inherited, string? UniqueId=null)
Navigates the AppShell to the specified route, with page arguments to match.
Task< bool > DisplayAlert(string Title, string Message, string? Accept=null, string? Cancel=null)
Displays an alert/message box to the user.
Interface for information about a service provider.
class ISO_3166_Country(string Name, string Alpha2, string Alpha3, int NumericCode, string DialCode, EmojiInfo? EmojiInfo=null)
Representation of an ISO3166-1 Country
class ISO_5218_Gender(string Gender, int Code, string Letter, string LocalizedNameId, char Unicode)
Contains one record of the ISO 5218 data set.
class Photo(byte[] Binary, int Rotation)
Class containing information about a photo.
Definition: Photo.cs:8
BackMethod
Navigation Back Method
Definition: BackMethod.cs:7
class ApplyIdNavigationArgs(bool Personal, bool ReusePhoto)
Navigation arguments for the ApplyIdPage and ApplyIdViewModel.
AuthenticationPurpose
Purpose for requesting the user to authenticate itself.
SignWith
Options on what keys to use when signing data.
Definition: Enumerations.cs:84
XmppState
State of XMPP connection.
Definition: XmppState.cs:7
Definition: App.xaml.cs:4