3using System.ComponentModel;
4using System.Globalization;
6using System.Threading.Tasks;
7using CommunityToolkit.Mvvm.ComponentModel;
8using CommunityToolkit.Mvvm.Input;
26 private bool codeVerified;
34 this.numberIsValid =
true;
35 this.typeIsValid =
true;
36 this.lengthIsValid =
true;
42 await base.OnInitializeAsync();
48 this.CountDownTimer =
App.
Current.Dispatcher.CreateTimer();
49 this.CountDownTimer.Interval = TimeSpan.FromMilliseconds(1000);
50 this.CountDownTimer.Tick += this.CountDownEventHandler;
54 if (
string.IsNullOrEmpty(ExistingNumber))
63 new KeyValuePair<string, string>(
"Accept",
"application/json"));
66 if ((Result.
Decoded is Dictionary<string, object> Response) &&
67 Response.TryGetValue(
"CountryCode", out
object? CcObj) &&
68 CcObj is
string CountryCodeStr &&
71 this.SelectedCountry = Country;
81 this.ApplyStoredPhoneNumber(ExistingNumber);
90 if (this.CountDownTimer is not
null)
92 this.CountDownTimer.Stop();
93 this.CountDownTimer.Tick -= this.CountDownEventHandler;
94 this.CountDownTimer =
null;
97 await base.OnDisposeAsync();
105 this.OnPropertyChanged(nameof(this.Title));
106 this.OnPropertyChanged(nameof(this.Description));
107 this.OnPropertyChanged(nameof(this.LocalizedSendCodeText));
112 #region Observable Properties
124 [NotifyCanExecuteChangedFor(nameof(SendCodeCommand))]
126 private bool numberIsValid;
134 private bool typeIsValid =
true;
142 private bool lengthIsValid =
true;
148 [NotifyPropertyChangedFor(nameof(LocalizedSendCodeText))]
149 [NotifyCanExecuteChangedFor(nameof(SendCodeCommand))]
150 [NotifyCanExecuteChangedFor(nameof(ResendCodeCommand))]
151 private int countDownSeconds;
157 private IDispatcherTimer? countDownTimer;
163 [NotifyCanExecuteChangedFor(nameof(SendCodeCommand))]
166 private string phoneNumber =
string.Empty;
172 private bool isPhoneReadOnly;
176 #region Localization Helpers
181 public string LocalizedSendCodeText
185 if (this.CountDownSeconds > 0)
198 if (!this.TypeIsValid)
200 if (!this.LengthIsValid)
213 #region State & Derived
218 public bool CanSendCode => this.NumberIsValid && this.PhoneNumber.Length > 0 && this.CountDownSeconds <= 0 && !this.IsBusy;
219 public bool CanResendCode => this.CountDownSeconds <= 0;
220 public bool CanContinue => this.codeVerified;
224 private void ApplyStoredPhoneNumber(
string StoredNumber)
226 string Trimmed = StoredNumber.Trim();
231 ResolvedCountry = CountryFromProfile;
235 ResolvedCountry = CountryFromNumber;
238 if (ResolvedCountry is not
null)
240 this.SelectedCountry = ResolvedCountry;
241 string Digits = Trimmed;
242 if (Digits.StartsWith(
"+", StringComparison.Ordinal))
244 Digits = Digits[1..];
247 if (Digits.StartsWith(ResolvedCountry.DialCode, StringComparison.Ordinal))
249 Digits = Digits[ResolvedCountry.DialCode.Length..];
252 this.PhoneNumber = Digits;
256 this.PhoneNumber = Trimmed.TrimStart(
'+');
260 internal override Task OnActivatedAsync()
264 if (Coordinator is not
null)
267 bool HasAccount = !
string.IsNullOrEmpty(Profile.
Account);
270 this.IsPhoneReadOnly =
true;
274 this.IsPhoneReadOnly =
false;
283 return base.OnActivatedAsync();
286 partial
void OnPhoneNumberChanged(
string value)
288 if (this.IsPhoneReadOnly)
297 bool TypeValid = value.All(
char.IsDigit);
298 bool LengthValid = value.Length >= 4;
299 this.TypeIsValid = TypeValid;
300 this.LengthIsValid = LengthValid;
301 this.NumberIsValid = TypeValid && LengthValid;
302 this.SendCodeCommand.NotifyCanExecuteChanged();
304 this.OnPropertyChanged(nameof(this.ShowValidationError));
310 private async Task SelectPhoneCode()
315 if (Result is not
null)
316 this.SelectedCountry = Result;
320 private async Task SendCode()
322 if (!this.NumberIsValid)
336 string FullPhoneNumber =
"+" + this.SelectedCountry.DialCode + this.PhoneNumber;
337 if (this.SelectedCountry.DialCode ==
"46")
338 FullPhoneNumber =
"+" + this.SelectedCountry.DialCode + this.PhoneNumber.TrimStart(
'0');
342 new Dictionary<string, object>()
344 {
"Nr", FullPhoneNumber },
345 {
"AppName", Constants.Application.Name },
346 {
"Language", CultureInfo.CurrentCulture.TwoLetterISOLanguageName }
350 new KeyValuePair<string, string>(
"Accept",
"application/json"));
353 if (SendResult.
Decoded is Dictionary<string, object> SendResponse &&
354 SendResponse.TryGetValue(
"Status", out
object? Obj) && Obj is
bool SendStatus && SendStatus &&
355 SendResponse.TryGetValue(
"IsTemporary", out Obj) && Obj is
bool SendIsTemporary)
366 VerifyCodeNavigationArgs
NavigationArgs =
new(
this, FullPhoneNumber);
369 if (!
string.IsNullOrEmpty(Code))
371 await this.VerifyCodeAsync(Code, FullPhoneNumber).ConfigureAwait(
false);
395 [RelayCommand(CanExecute = nameof(CanResendCode))]
396 private async Task ResendCode()
407 string FullPhoneNumber =
"+" + this.SelectedCountry.DialCode + this.PhoneNumber;
408 if (this.SelectedCountry.DialCode ==
"46")
409 FullPhoneNumber =
"+" + this.SelectedCountry.DialCode + this.PhoneNumber.TrimStart(
'0');
413 new Dictionary<string, object>()
415 {
"Nr", FullPhoneNumber },
416 {
"AppName", Constants.Application.Name },
417 {
"Language", CultureInfo.CurrentCulture.TwoLetterISOLanguageName }
421 new KeyValuePair<string, string>(
"Accept",
"application/json"));
424 if (SendResult.
Decoded is Dictionary<string, object> SendResponse &&
425 SendResponse.TryGetValue(
"Status", out
object? Obj) &&
426 Obj is
bool SendStatus && SendStatus)
448 #region Verification Logic
450 private async Task VerifyCodeAsync(
string code,
string fullPhoneNumber)
455 bool IsTest = Purpose == PurposeUse.Educational || Purpose ==
PurposeUse.Experimental;
459 new Dictionary<string, object>()
461 {
"Nr", fullPhoneNumber },
462 {
"Code", int.Parse(code, NumberStyles.None, CultureInfo.InvariantCulture) },
467 new KeyValuePair<string, string>(
"Accept",
"application/json"));
470 if (VerifyResult.
Decoded is Dictionary<string, object> VerifyResponse &&
471 VerifyResponse.TryGetValue(
"Status", out
object? Obj) && Obj is
bool VerifyStatus && VerifyStatus &&
472 VerifyResponse.TryGetValue(
"Domain", out Obj) && Obj is
string VerifyDomain &&
473 VerifyResponse.TryGetValue(
"Key", out Obj) && Obj is
string VerifyKey &&
474 VerifyResponse.TryGetValue(
"Secret", out Obj) && Obj is
string VerifySecret &&
475 VerifyResponse.TryGetValue(
"Temporary", out Obj) && Obj is
bool VerifyIsTemporary)
479 ServiceRef.TagProfile.TestOtpTimestamp = VerifyIsTemporary ? DateTime.Now :
null;
483 bool DefaultConnectivity;
486 (
string HostName,
int PortNumber,
bool IsIpAddress) = await
ServiceRef.
NetworkService.LookupXmppHostnameAndPort(VerifyDomain);
491 DefaultConnectivity =
false;
496 this.codeVerified =
true;
497 this.OnPropertyChanged(nameof(this.CanContinue));
499 if (this.CoordinatorViewModel is not
null)
501 OnboardingStep? NextStep = this.CoordinatorViewModel.GetNextActiveStep(this.Step);
503 await this.CoordinatorViewModel.GoToStepCommand.ExecuteAsync(TargetStep);
528 private void StartTimer()
530 if (this.CountDownTimer is not
null)
533 this.CountDownSeconds = 10;
535 this.CountDownSeconds = 300;
537 if (!this.CountDownTimer.IsRunning)
538 this.CountDownTimer.Start();
542 private void CountDownEventHandler(
object? Sender, EventArgs E)
544 if (this.CountDownTimer is not
null)
546 if (this.CountDownSeconds > 0)
547 this.CountDownSeconds--;
549 this.CountDownTimer.Stop();
Represents an instance of the Neuro-Access app.
static void ValidateCertificateCallback(object? Sender, RemoteCertificateEventArgs Args)
Callback for validating SSL certificates. This method is called when a remote certificate is received...
static new? App Current
Gets the current application instance.
const string IdDomain
Neuro-Access domain.
A set of never changing property constants and helpful values.
A strongly-typed resource class, for looking up localized strings, etc.
static string PhoneValidationDigits
Looks up a localized string similar to Only digits are allowed.
static string SwitchingToTestPhoneNumberNotAllowed
Looks up a localized string similar to Changing the phone number to a test one is not allowed....
static string OnboardingPhonePageTitle
Looks up a localized string similar to Please verify phone number.
static string OnboardingPhonePageDetails
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 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 PhoneValidationLength
Looks up a localized string similar to Minimum length 4 is required.
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.
Conversion between Country Names and ISO-3166-1 country codes.
static bool TryGetCountryByCode(string? CountryCode, [NotNullWhen(true)] out ISO_3166_Country? Country)
Tries to get the country, given its country code.
static ISO_3166_Country DefaultCountry
This collection built from Wikipedia entry on ISO3166-1 on 9th Feb 2016
static bool TryGetCountryByPhone(string PhoneNumber, [NotNullWhen(true)] out ISO_3166_Country? Country)
Tries to get the country, given its country and phone codes.
Base class that references services in the app.
static ILogService LogService
Log service.
static INetworkService NetworkService
Network 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 ITagProfile TagProfile
TAG Profile service.
static IReportingStringLocalizer Localizer
Localization service
An base class holding page specific navigation parameters.
Onboarding step ViewModel for validating the phone number. Updated to mirror registration validation ...
override async Task OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
bool CanSendCode
Button enabled if number is valid and timer allows.
ValidatePhoneOnboardingStepViewModel()
Creates a new instance of the ValidatePhoneOnboardingStepViewModel class.
bool ShowValidationError
True if a validation error should be displayed.
void LocalizationManagerEventHandler(object? Sender, PropertyChangedEventArgs E)
Handles localization manager property changes to refresh localized strings.
override async Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
string LocalizedValidationError
Localized validation error text.
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).
Class containing credentials for an XMPP client connection.
const int DefaultPort
Default XMPP Server port.
The TAG Profile is the heart of the digital identity for a specific user/device. Use this instance to...
string? Account
The account name for this profile
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
PurposeUse Purpose
Purpose for using the app.
void SetPhone(string Country, string PhoneNumber)
Sets the phone number used for contacting the user.
string? PhoneNumber
Verified phone number.
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.
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.
class ISO_3166_Country(string Name, string Alpha2, string Alpha3, int NumericCode, string DialCode, EmojiInfo? EmojiInfo=null)
Representation of an ISO3166-1 Country
PurposeUse
For what purpose the app will be used
BackMethod
Navigation Back Method
OnboardingScenario
Distinct onboarding scenarios.
OnboardingStep
Unified onboarding steps matching the registration journey.