Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ValidatePhoneViewModel.cs
1using System.ComponentModel;
2using System.Globalization;
3using CommunityToolkit.Mvvm.ComponentModel;
4using CommunityToolkit.Mvvm.Input;
5using Mopups.Services;
14using Waher.Content;
15
17{
18 public partial class ValidatePhoneViewModel : BaseRegistrationViewModel, ICodeVerification
19 {
21 : base(RegistrationStep.ValidatePhone)
22 {
23 }
24
26 protected override async Task OnInitialize()
27 {
28 await base.OnInitialize();
29
30 LocalizationManager.Current.PropertyChanged += this.LocalizationManagerEventHandler;
31
32 if (App.Current is not null)
33 {
34 this.CountDownTimer = App.Current.Dispatcher.CreateTimer();
35 this.CountDownTimer.Interval = TimeSpan.FromMilliseconds(1000);
36 this.CountDownTimer.Tick += this.CountDownEventHandler;
37 }
38
39 if (string.IsNullOrEmpty(ServiceRef.TagProfile.PhoneNumber))
40 {
41 try
42 {
43 object Result = await InternetContent.PostAsync(
44 new Uri("https://" + Constants.Domains.IdDomain + "/ID/CountryCode.ws"), string.Empty,
45 new KeyValuePair<string, string>("Accept", "application/json"));
46
47 if ((Result is Dictionary<string, object> Response) &&
48 Response.TryGetValue("CountryCode", out object? cc) &&
49 (cc is string CountryCode))
50 {
51 if (ISO_3166_1.TryGetCountryByCode(CountryCode, out ISO_3166_Country? Country))
52 this.SelectedCountry = Country;
53 }
54 }
55 catch (Exception ex)
56 {
57 ServiceRef.LogService.LogException(ex);
58 }
59 }
60 }
61
63 protected override async Task OnDispose()
64 {
65 LocalizationManager.Current.PropertyChanged -= this.LocalizationManagerEventHandler;
66
67 if (this.CountDownTimer is not null)
68 {
69 this.CountDownTimer.Stop();
70 this.CountDownTimer.Tick -= this.CountDownEventHandler;
71 this.CountDownTimer = null;
72 }
73
74 await base.OnDispose();
75 }
76
78 public override async Task DoAssignProperties()
79 {
80 await base.DoAssignProperties();
81
82 string? PhoneNumber = ServiceRef.TagProfile.PhoneNumber;
83
84 if (string.IsNullOrEmpty(PhoneNumber))
85 {
86 try
87 {
88 object Result = await InternetContent.PostAsync(
89 new Uri("https://" + Constants.Domains.IdDomain + "/ID/CountryCode.ws"), string.Empty,
90 new KeyValuePair<string, string>("Accept", "application/json"));
91
92 if ((Result is Dictionary<string, object> Response) &&
93 Response.TryGetValue("CountryCode", out object? cc) &&
94 (cc is string CountryCode))
95 {
96 if (ISO_3166_1.TryGetCountryByCode(CountryCode, out ISO_3166_Country? Country))
97 this.SelectedCountry = Country;
98 }
99 }
100 catch (Exception ex)
101 {
102 ServiceRef.LogService.LogException(ex);
103 }
104 }
105 else
106 {
107 string SelectedCountry = ServiceRef.TagProfile.SelectedCountry!;
108
109 if (ISO_3166_1.TryGetCountryByCode(SelectedCountry, out ISO_3166_Country? Country))
110 {
111 this.SelectedCountry = Country;
112 this.PhoneNumber = PhoneNumber[(Country.DialCode.Length + 1)..];
113 this.SendCodeCommand.NotifyCanExecuteChanged();
114 }
115 }
116 }
117
118 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
119 {
120 base.OnPropertyChanged(e);
121
122 if (e.PropertyName == nameof(this.IsBusy))
123 this.SendCodeCommand.NotifyCanExecuteChanged();
124 }
125
126 public void LocalizationManagerEventHandler(object? sender, PropertyChangedEventArgs e)
127 {
128 this.OnPropertyChanged(nameof(this.LocalizedSendCodeText));
129 this.OnPropertyChanged(nameof(this.LocalizedValidationError));
130 }
131
132 [ObservableProperty]
134
135 [ObservableProperty]
136 [NotifyCanExecuteChangedFor(nameof(SendCodeCommand))]
137 bool numberIsValid;
138
139 [ObservableProperty]
140 [NotifyPropertyChangedFor(nameof(LocalizedValidationError))]
141 bool typeIsValid;
142
143 [ObservableProperty]
144 [NotifyPropertyChangedFor(nameof(LocalizedValidationError))]
145 bool lengthIsValid;
146
147 [ObservableProperty]
148 [NotifyPropertyChangedFor(nameof(LocalizedSendCodeText))]
149 [NotifyCanExecuteChangedFor(nameof(SendCodeCommand))]
150 [NotifyCanExecuteChangedFor(nameof(ResendCodeCommand))]
151 private int countDownSeconds;
152
153 [ObservableProperty]
154 private IDispatcherTimer? countDownTimer;
155
156 public string LocalizedSendCodeText
157 {
158 get
159 {
160 if (this.CountDownSeconds > 0)
161 return ServiceRef.Localizer[nameof(AppResources.SendCodeSeconds), this.CountDownSeconds];
162
163 return ServiceRef.Localizer[nameof(AppResources.SendCode)];
164 }
165 }
166
167 public string LocalizedValidationError
168 {
169 get
170 {
171 if (!this.TypeIsValid)
172 return ServiceRef.Localizer[nameof(AppResources.PhoneValidationDigits)];
173
174 if (!this.LengthIsValid)
175 return ServiceRef.Localizer[nameof(AppResources.PhoneValidationLength)];
176
177 return string.Empty;
178 }
179 }
180
184 [ObservableProperty]
185 private string phoneNumber = string.Empty;
186
187 public bool CanSendCode => this.NumberIsValid && !this.IsBusy &&
188 (this.PhoneNumber.Length > 0) && (this.CountDownSeconds <= 0);
189
190 public bool CanResendCode => this.CountDownSeconds <= 0;
191
192 [RelayCommand]
193 private async Task SelectPhoneCode()
194 {
195 SelectPhoneCodePopup Page = new();
196 await MopupService.Instance.PushAsync(Page);
197
198 ISO_3166_Country? Result = await Page.Result;
199
200 if (Result is not null)
201 this.SelectedCountry = Result;
202
203 return;
204 }
205
206 [RelayCommand(CanExecute = nameof(CanSendCode))]
207 private async Task SendCode()
208 {
209 this.IsBusy = true;
210
211 try
212 {
213 if (!ServiceRef.NetworkService.IsOnline)
214 {
216 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
217 ServiceRef.Localizer[nameof(AppResources.NetworkSeemsToBeMissing)]);
218 return;
219 }
220 string FullPhoneNumber = $"+{this.SelectedCountry.DialCode}{this.PhoneNumber}";
221
222 if (this.SelectedCountry.DialCode == "46")
223 FullPhoneNumber = $"+{this.SelectedCountry.DialCode}{this.PhoneNumber.TrimStart('0')}";
224
225 object SendResult = await InternetContent.PostAsync(
226 new Uri("https://" + Constants.Domains.IdDomain + "/ID/SendVerificationMessage.ws"),
227 new Dictionary<string, object>()
228 {
229 { "Nr", FullPhoneNumber },
230 { "AppName", Constants.Application.Name },
231 { "Language", CultureInfo.CurrentCulture.TwoLetterISOLanguageName }
232 }, new KeyValuePair<string, string>("Accept", "application/json"));
233
234 if (SendResult is Dictionary<string, object> SendResponse &&
235 SendResponse.TryGetValue("Status", out object? Obj) && Obj is bool SendStatus && SendStatus &&
236 SendResponse.TryGetValue("IsTemporary", out Obj) && Obj is bool SendIsTemporary)
237 {
238 if (!string.IsNullOrEmpty(ServiceRef.TagProfile.PhoneNumber) && (ServiceRef.TagProfile.TestOtpTimestamp is null) && SendIsTemporary)
239 {
241 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
242 ServiceRef.Localizer[nameof(AppResources.SwitchingToTestPhoneNumberNotAllowed)]);
243 }
244 else
245 {
246 this.StartTimer();
247
248 VerifyCodeNavigationArgs NavigationArgs = new(this, FullPhoneNumber);
250 string? Code = await NavigationArgs.VarifyCode!.Task;
251
252 if (!string.IsNullOrEmpty(Code))
253 {
255 // bool IsTest = this.PurposeRequired ? this.IsEducationalPurpose || this.IsExperimentalPurpose : this.TagProfile.IsTest;
256 // PurposeUse Purpose = this.PurposeRequired ? (PurposeUse)this.PurposeNr : this.TagProfile.Purpose;
257
259 bool IsTest = (Purpose == PurposeUse.Educational) || (Purpose == PurposeUse.Experimental);
260
261 object VerifyResult = await InternetContent.PostAsync(
262 new Uri("https://" + Constants.Domains.IdDomain + "/ID/VerifyNumber.ws"),
263 new Dictionary<string, object>()
264 {
265 { "Nr", FullPhoneNumber },
266 { "Code", int.Parse(Code, NumberStyles.None, CultureInfo.InvariantCulture) },
267 { "Test", IsTest }
268 }, new KeyValuePair<string, string>("Accept", "application/json"));
269
270
271 if (VerifyResult is Dictionary<string, object> VerifyResponse &&
272 VerifyResponse.TryGetValue("Status", out Obj) && Obj is bool VerifyStatus && VerifyStatus &&
273 VerifyResponse.TryGetValue("Domain", out Obj) && Obj is string VerifyDomain &&
274 VerifyResponse.TryGetValue("Key", out Obj) && Obj is string VerifyKey &&
275 VerifyResponse.TryGetValue("Secret", out Obj) && Obj is string VerifySecret &&
276 VerifyResponse.TryGetValue("Temporary", out Obj) && Obj is bool VerifyIsTemporary)
277 {
278 ServiceRef.TagProfile.SetPhone(this.SelectedCountry.Alpha2, FullPhoneNumber);
279 ServiceRef.TagProfile.SetPurpose(IsTest, Purpose);
280 ServiceRef.TagProfile.TestOtpTimestamp = VerifyIsTemporary ? DateTime.Now : null;
281
284
285
286 if (string.IsNullOrEmpty(ServiceRef.TagProfile.Domain))
287 {
288 bool DefaultConnectivity;
289
290 try
291 {
292 (string HostName, int PortNumber, bool IsIpAddress) = await ServiceRef.NetworkService.LookupXmppHostnameAndPort(VerifyDomain);
293 DefaultConnectivity = HostName == VerifyDomain && PortNumber == Waher.Networking.XMPP.XmppCredentials.DefaultPort;
294 }
295 catch (Exception)
296 {
297 DefaultConnectivity = false;
298 }
299
300 ServiceRef.TagProfile.SetDomain(VerifyDomain, DefaultConnectivity, VerifyKey, VerifySecret);
301 }
302 if (VerifyIsTemporary)
303 GoToRegistrationStep(RegistrationStep.NameEntry);
304 else
305 GoToRegistrationStep(RegistrationStep.ValidateEmail);
306 }
307 else
308 {
310 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
311 ServiceRef.Localizer[nameof(AppResources.UnableToVerifyCode)],
312 ServiceRef.Localizer[nameof(AppResources.Ok)]);
313 }
314 }
315 }
316 }
317 else
318 {
320 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
321 ServiceRef.Localizer[nameof(AppResources.SomethingWentWrongWhenSendingPhoneCode)]);
322 }
323 }
324 catch (Exception ex)
325 {
326 ServiceRef.LogService.LogException(ex);
327
329 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)], ex.Message,
330 ServiceRef.Localizer[nameof(AppResources.Ok)]);
331 }
332 finally
333 {
334 this.IsBusy = false;
335 }
336 }
337
338 [RelayCommand(CanExecute = nameof(CanResendCode))]
339 private async Task ResendCode()
340 {
341 try
342 {
343 if (!ServiceRef.NetworkService.IsOnline)
344 {
346 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
347 ServiceRef.Localizer[nameof(AppResources.NetworkSeemsToBeMissing)]);
348 return;
349 }
350
351 string FullPhoneNumber = $"+{this.SelectedCountry.DialCode}{this.PhoneNumber}";
352
353 if (this.SelectedCountry.DialCode == "46") //If swedish phone nr
354 FullPhoneNumber = $"+{this.SelectedCountry.DialCode}{this.PhoneNumber.TrimStart('0')}";
355
356 object SendResult = await InternetContent.PostAsync(
357 new Uri("https://" + Constants.Domains.IdDomain + "/ID/SendVerificationMessage.ws"),
358 new Dictionary<string, object>()
359 {
360 { "Nr", FullPhoneNumber },
361 { "AppName", Constants.Application.Name },
362 { "Language", CultureInfo.CurrentCulture.TwoLetterISOLanguageName }
363 }, new KeyValuePair<string, string>("Accept", "application/json"));
364
365 if (SendResult is Dictionary<string, object> SendResponse &&
366 SendResponse.TryGetValue("Status", out object? Obj) && Obj is bool SendStatus && SendStatus)
367 {
368 this.StartTimer();
369 }
370 else
371 {
373 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
374 ServiceRef.Localizer[nameof(AppResources.SomethingWentWrongWhenSendingPhoneCode)]);
375 }
376 }
377 catch (Exception ex)
378 {
379 ServiceRef.LogService.LogException(ex);
380
382 ServiceRef.Localizer[nameof(AppResources.ErrorTitle)], ex.Message,
383 ServiceRef.Localizer[nameof(AppResources.Ok)]);
384 }
385 }
386
387 private void StartTimer()
388 {
389 if (this.CountDownTimer is not null)
390 {
391 this.CountDownSeconds = 300;
392
393 if (!this.CountDownTimer.IsRunning)
394 this.CountDownTimer.Start();
395 }
396 }
397
398 private void CountDownEventHandler(object? sender, EventArgs e)
399 {
400 if (this.CountDownTimer is not null)
401 {
402 if (this.CountDownSeconds > 0)
403 this.CountDownSeconds--;
404 else
405 this.CountDownTimer.Stop();
406 }
407 }
408 }
409}
The Application class, representing an instance of the Neuro-Access app.
Definition: App.xaml.cs:69
static new? App Current
Gets the current application, type casted to App.
Definition: App.xaml.cs:99
const string IdDomain
Neuro-Access domain.
Definition: Constants.cs:253
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 DefaultCountry
This collection built from Wikipedia entry on ISO3166-1 on 9th Feb 2016
Definition: ISO_3166_1.cs:32
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 ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:79
static IStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:235
An base class holding page specific navigation parameters.
Task< ISO_3166_Country?> Result
Task waiting for result. null means dialog was closed without selection.
Static class managing encoding and decoding of internet content.
static Task< object > PostAsync(Uri Uri, object Data, params KeyValuePair< string, string >[] Headers)
Posts to a resource, using a Uniform Resource Identifier (or Locator).
Class containing credentials for an XMPP client connection.
const int DefaultPort
Default XMPP Server port.
void SetDomain(string DomainName, bool DefaultXmppConnectivity, string Key, string Secret)
Set the domain name to connect to.
string? SelectedCountry
Selected country. Some countries have the same phone code, so we want to save the selected country
Definition: ITagProfile.cs:71
PurposeUse Purpose
Purpose for using the app.
Definition: ITagProfile.cs:201
void SetPhone(string Country, string PhoneNumber)
Sets the phone number used for contacting the user.
string? PhoneNumber
Verified phone number.
Definition: ITagProfile.cs:91
void SetPurpose(bool IsTest, PurposeUse Purpose)
Set if the user choose the educational or experimental purpose.
string? Domain
The domain this profile is connected to.
Definition: ITagProfile.cs:51
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.
class ISO_3166_Country(string Name, string Alpha2, string Alpha3, int NumericCode, string DialCode, EmojiInfo? EmojiInfo=null)
Representation of an ISO3166-1 Country
RegistrationStep
The different steps of a TAG Profile registration journey.
PurposeUse
For what purpose the app will be used
Definition: PurposeUse.cs:7
BackMethod
Navigation Back Method
Definition: BackMethod.cs:7
Definition: App.xaml.cs:4