Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ValidateEmailOnboardingStepViewModel.cs
1using System;
3using System.ComponentModel;
4using System.Globalization;
5using System.Text.RegularExpressions;
6using System.Threading.Tasks;
7using CommunityToolkit.Mvvm.ComponentModel;
8using CommunityToolkit.Mvvm.Input;
15using Waher.Content;
16
18{
23 {
24 private static readonly Regex EmailRegex = new Regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$", RegexOptions.IgnoreCase | RegexOptions.Compiled);
25 private bool codeVerified;
26
30 public ValidateEmailOnboardingStepViewModel() : base(OnboardingStep.ValidateEmail) { }
31
32 #region Lifecycle
33
35 public override async Task OnInitializeAsync()
36 {
37 await base.OnInitializeAsync();
38
39 LocalizationManager.Current.PropertyChanged += this.LocalizationManagerEventHandler;
40
41 if (App.Current is not null)
42 {
43 this.CountDownTimer = App.Current.Dispatcher.CreateTimer();
44 this.CountDownTimer.Interval = TimeSpan.FromSeconds(1);
45 this.CountDownTimer.Tick += this.CountDownEventHandler;
46 }
47
48 if (!string.IsNullOrEmpty(ServiceRef.TagProfile.EMail))
49 {
50 this.EmailText = ServiceRef.TagProfile.EMail;
51 this.EmailIsValid = EmailRegex.IsMatch(this.EmailText);
52 this.SendCodeCommand.NotifyCanExecuteChanged();
53 }
54 }
55
57 public override async Task OnDisposeAsync()
58 {
59 LocalizationManager.Current.PropertyChanged -= this.LocalizationManagerEventHandler;
60
61 if (this.CountDownTimer is not null)
62 {
63 this.CountDownTimer.Stop();
64 this.CountDownTimer.Tick -= this.CountDownEventHandler;
65 this.CountDownTimer = null;
66 }
67
68 await base.OnDisposeAsync();
69 }
70
71 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
72 {
73 base.OnPropertyChanged(e);
74 if (e.PropertyName == nameof(this.IsBusy))
75 this.SendCodeCommand.NotifyCanExecuteChanged();
76 }
77
78 private void LocalizationManagerEventHandler(object? sender, PropertyChangedEventArgs e)
79 {
80 this.OnPropertyChanged(nameof(this.LocalizedSendCodeText));
81 this.OnPropertyChanged(nameof(this.LocalizedValidationError));
82 this.OnPropertyChanged(nameof(this.ShowValidationError));
83 }
84
85 #endregion
86
87 #region Observable Properties
88
92 [ObservableProperty]
93 [NotifyCanExecuteChangedFor(nameof(SendCodeCommand))]
94 [NotifyPropertyChangedFor(nameof(LocalizedValidationError))]
95 [NotifyPropertyChangedFor(nameof(ShowValidationError))]
96 private bool emailIsValid = true;
97
101 [ObservableProperty]
102 private bool isEmailReadOnly;
103
107 [ObservableProperty]
108 [NotifyPropertyChangedFor(nameof(LocalizedSendCodeText))]
109 [NotifyCanExecuteChangedFor(nameof(SendCodeCommand))]
110 [NotifyCanExecuteChangedFor(nameof(ResendCodeCommand))]
111 private int countDownSeconds;
112
116 [ObservableProperty]
117 private IDispatcherTimer? countDownTimer;
118
122 [ObservableProperty]
123 [NotifyPropertyChangedFor(nameof(ShowValidationError))]
124 [NotifyPropertyChangedFor(nameof(LocalizedValidationError))]
125 [NotifyCanExecuteChangedFor(nameof(SendCodeCommand))]
126 private string emailText = string.Empty;
127
128 #endregion
129
130 #region Localization & Validation
131
136 {
137 get
138 {
139 if (this.CountDownSeconds > 0)
140 return ServiceRef.Localizer[nameof(AppResources.SendCodeSeconds), this.CountDownSeconds];
142 }
143 }
144
149 {
150 get
151 {
152 if (!this.EmailIsValid && this.EmailText.Length > 0)
154 return string.Empty;
155 }
156 }
157
161 public bool ShowValidationError => !this.EmailIsValid && this.EmailText.Length > 0;
162
163 #endregion
164
165 #region Computed State
166
170 public bool CanSendCode => this.EmailIsValid && !this.IsBusy && this.EmailText.Length > 0 && this.CountDownSeconds <= 0;
171
175 public bool CanResendCode => this.CountDownSeconds <= 0;
176
180 public bool CanContinue => this.codeVerified;
181
182 public override string Title => ServiceRef.Localizer[nameof(AppResources.OnboardingEmailPageTitle)];
183 public override string Description => ServiceRef.Localizer[nameof(AppResources.OnboardingEmailPageDetails)];
184
185 #endregion
186
187 #region Property Change Hooks
188
189 partial void OnEmailTextChanged(string value)
190 {
191 bool WasValid = this.EmailIsValid;
192 bool IsValid = string.IsNullOrWhiteSpace(value) || EmailRegex.IsMatch(value);
193 this.EmailIsValid = IsValid;
194 if (WasValid != IsValid)
195 {
196 this.OnPropertyChanged(nameof(this.LocalizedValidationError));
197 this.OnPropertyChanged(nameof(this.ShowValidationError));
198 }
199 this.SendCodeCommand.NotifyCanExecuteChanged();
200 }
201
202 #endregion
203
204 #region Commands
205
209 [RelayCommand(CanExecute = nameof(CanSendCode))]
210 private async Task SendCode()
211 {
212 if (!this.EmailIsValid)
213 return;
214
215 this.IsBusy = true;
216 try
217 {
218 if (!ServiceRef.NetworkService.IsOnline)
219 {
223 return;
224 }
225
226 ContentResponse SendResult = await InternetContent.PostAsync(
227 new Uri("https://" + Constants.Domains.IdDomain + "/ID/SendVerificationMessage.ws"),
228 new Dictionary<string, object>()
229 {
230 { "EMail", this.EmailText },
231 { "AppName", Constants.Application.Name },
232 { "Language", CultureInfo.CurrentCulture.TwoLetterISOLanguageName }
233 },
234 null,
236 new KeyValuePair<string, string>("Accept", "application/json"));
237 SendResult.AssertOk();
238
239 if (SendResult.Decoded is Dictionary<string, object> SendResponse &&
240 SendResponse.TryGetValue("Status", out object? Obj) && Obj is bool SendStatus && SendStatus)
241 {
242 OnboardingViewModel? Coordinator = this.CoordinatorViewModel;
243 if (Coordinator is not null)
244 {
246 bool HasAccount = !string.IsNullOrEmpty(Profile.Account);
247 if (Coordinator.Scenario == OnboardingScenario.ReverifyIdentity || (Coordinator.Scenario == OnboardingScenario.FullSetup && HasAccount))
248 {
249 this.IsEmailReadOnly = true;
250 }
251 else
252 {
253 this.IsEmailReadOnly = false;
254 }
255 }
256
257 this.StartTimer();
258 VerifyCodeNavigationArgs NavigationArgs = new(this, this.EmailText);
260 string? Code = await NavigationArgs.VarifyCode!.Task;
261 if (!string.IsNullOrEmpty(Code))
262 {
263 await this.VerifyCodeAsync(Code).ConfigureAwait(false);
264 }
265 }
266 else
267 {
271 }
272 }
273 catch (Exception Ex)
274 {
275 ServiceRef.LogService.LogException(Ex);
277 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)], Ex.Message,
279 }
280 finally
281 {
282 this.IsBusy = false;
283 }
284 }
285
289 [RelayCommand(CanExecute = nameof(CanResendCode))]
290 private async Task ResendCode()
291 {
292 try
293 {
294 if (!ServiceRef.NetworkService.IsOnline)
295 {
299 return;
300 }
301
302 ContentResponse SendResult = await InternetContent.PostAsync(
303 new Uri("https://" + Constants.Domains.IdDomain + "/ID/SendVerificationMessage.ws"),
304 new Dictionary<string, object>()
305 {
306 { "EMail", this.EmailText },
307 { "AppName", Constants.Application.Name },
308 { "Language", CultureInfo.CurrentCulture.TwoLetterISOLanguageName }
309 },
310 null,
312 new KeyValuePair<string, string>("Accept", "application/json"));
313 SendResult.AssertOk();
314
315 if (SendResult.Decoded is Dictionary<string, object> SendResponse &&
316 SendResponse.TryGetValue("Status", out object? Obj) && Obj is bool SendStatus && SendStatus)
317 {
318 this.StartTimer();
319 }
320 else
321 {
325 }
326 }
327 catch (Exception Ex)
328 {
329 ServiceRef.LogService.LogException(Ex);
331 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)], Ex.Message,
333 }
334 }
335
336 #endregion
337
338 #region Verification
339
340 private async Task VerifyCodeAsync(string code)
341 {
342 try
343 {
344 this.IsBusy = true;
345 this.CoordinatorViewModel?.SetIsBusy(true);
346 ContentResponse VerifyResult = await InternetContent.PostAsync(
347 new Uri("https://" + Constants.Domains.IdDomain + "/ID/VerifyNumber.ws"),
348 new Dictionary<string, object>()
349 {
350 { "EMail", this.EmailText },
351 { "Code", int.Parse(code, NumberStyles.None, CultureInfo.InvariantCulture) }
352 },
353 null,
355 new KeyValuePair<string, string>("Accept", "application/json"));
356 VerifyResult.AssertOk();
357
358 if (VerifyResult.Decoded is Dictionary<string, object> VerifyResponse &&
359 VerifyResponse.TryGetValue("Status", out object? Obj) && Obj is bool VerifyStatus && VerifyStatus)
360 {
361 ServiceRef.TagProfile.EMail = this.EmailText;
362 this.codeVerified = true;
363 this.OnPropertyChanged(nameof(this.CanContinue));
364
365 if (this.CoordinatorViewModel is not null)
366 {
367 if (this.CoordinatorViewModel.HasPendingTransfer)
368 {
369 bool Connected = await this.CoordinatorViewModel.TryFinalizeTransferAsync(true).ConfigureAwait(false);
370 ServiceRef.LogService.LogInformational("Transfer connection attempted after email verification.",
371 new KeyValuePair<string, object?>("Success", Connected));
372 if (Connected)
373 {
374 await this.CoordinatorViewModel.GoToStepCommand.ExecuteAsync(OnboardingStep.DefinePassword).ConfigureAwait(false);
375 return;
376 }
377 }
378
379 await this.CoordinatorViewModel.GoToStepCommand.ExecuteAsync(OnboardingStep.NameEntry).ConfigureAwait(false);
380 }
381 }
382 else
383 {
388 }
389 }
390 catch (Exception Ex)
391 {
392 ServiceRef.LogService.LogException(Ex);
397 }
398 finally
399 {
400 this.CoordinatorViewModel?.SetIsBusy(false);
401 this.IsBusy = false;
402 }
403 }
404
405 #endregion
406
407 #region Timer
408
409 private void StartTimer()
410 {
411 if (this.CountDownTimer is not null)
412 {
413 this.CountDownSeconds = 30;
414 if (!this.CountDownTimer.IsRunning)
415 this.CountDownTimer.Start();
416 }
417 }
418
419 private void CountDownEventHandler(object? sender, EventArgs e)
420 {
421 if (this.CountDownTimer is not null)
422 {
423 if (this.CountDownSeconds > 0)
424 this.CountDownSeconds--;
425 else
426 this.CountDownTimer.Stop();
427 }
428 }
429
430 #endregion
431 }
432}
Represents an instance of the Neuro-Access app.
Definition: App.xaml.cs:125
static void ValidateCertificateCallback(object? Sender, RemoteCertificateEventArgs Args)
Callback for validating SSL certificates. This method is called when a remote certificate is received...
Definition: App.xaml.cs:1135
static new? App Current
Gets the current application instance.
Definition: App.xaml.cs:169
const string IdDomain
Neuro-Access domain.
Definition: Constants.cs:307
A set of never changing property constants and helpful values.
Definition: Constants.cs:24
A strongly-typed resource class, for looking up localized strings, etc.
static string EmailValidationFormat
Looks up a localized string similar to Please enter a valid email.
static string OnboardingEmailPageDetails
Looks up a localized string similar to We’ll send you a verification code.
static string SendCode
Looks up a localized string similar to Send Code.
static string SendCodeSeconds
Looks up a localized string similar to Send Code ({0}).
static string NetworkSeemsToBeMissing
Looks up a localized string similar to Network seems to be missing. Please check your configuration a...
static string OnboardingEmailPageTitle
Looks up a localized string similar to Please verify email.
static string Ok
Looks up a localized string similar to OK.
static string UnableToVerifyCode
Looks up a localized string similar to The code was not correct..
static string SomethingWentWrongWhenSendingPhoneCode
Looks up a localized string similar to Something went wrong when sending a verification code to this ...
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
Base class that references services in the app.
Definition: ServiceRef.cs:43
static ILogService LogService
Log service.
Definition: ServiceRef.cs:214
static INetworkService NetworkService
Network service.
Definition: ServiceRef.cs:226
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:130
static INavigationService NavigationService
The navigation service for navigating between pages.
Definition: ServiceRef.cs:178
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:202
static IReportingStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:370
An base class holding page specific navigation parameters.
Onboarding step ViewModel for validating e-mail. Updated to mirror registration validation (continuou...
ValidateEmailOnboardingStepViewModel()
Initializes a new instance of the ValidateEmailOnboardingStepViewModel class.
override async Task OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
override async Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
Contains information about a response to a content request.
object Decoded
Decoded object.
void AssertOk()
Asserts response is OK.
Static class managing encoding and decoding of internet content.
static Task< ContentResponse > PostAsync(Uri Uri, object Data, params KeyValuePair< string, string >[] Headers)
Posts to a resource, using a Uniform Resource Identifier (or Locator).
The TAG Profile is the heart of the digital identity for a specific user/device. Use this instance to...
Definition: ITagProfile.cs:18
string? Account
The account name for this profile
Definition: ITagProfile.cs:102
string? EMail
Verified e-mail address.
Definition: ITagProfile.cs:97
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.
BackMethod
Navigation Back Method
Definition: BackMethod.cs:7
OnboardingScenario
Distinct onboarding scenarios.
OnboardingStep
Unified onboarding steps matching the registration journey.