3using System.Globalization;
6using System.Text.RegularExpressions;
8using System.Threading.Tasks;
9using CommunityToolkit.Mvvm.ComponentModel;
10using CommunityToolkit.Mvvm.Input;
22 private CancellationTokenSource? cooldownCts;
23 private bool disposed;
26 [NotifyPropertyChangedFor(nameof(IsInCooldown))]
27 [NotifyPropertyChangedFor(nameof(LocalizedContinueText))]
28 private int cooldownSecondsLeft;
31 private string? username;
34 [NotifyCanExecuteChangedFor(nameof(CreateAccountCommand))]
35 private bool usernameIsValid;
38 private string? alternativeName;
41 private string? localizedValidationMessage;
49 await base.OnInitializeAsync();
51 ServiceRef.XmppService.ConnectionStateChanged += this.XmppService_ConnectionStateChanged;
55 await this.CoordinatorViewModel.GoToStepCommand.ExecuteAsync(
OnboardingStep.CreateAccount);
61 ServiceRef.XmppService.ConnectionStateChanged -= this.XmppService_ConnectionStateChanged;
64 await base.OnDisposeAsync();
67 private Task XmppService_ConnectionStateChanged(
object?
_,
XmppState __)
69 this.CreateAccountCommand.NotifyCanExecuteChanged();
70 return Task.CompletedTask;
73 public bool IsInCooldown => this.CooldownSecondsLeft > 0;
75 public bool IsXmppConnected => ServiceRef.XmppService.State ==
XmppState.Connected;
77 public string LocalizedContinueText
81 if (this.CooldownSecondsLeft > 0)
88 public bool CanCreateAccount => this.UsernameIsValid &&
89 !
string.IsNullOrEmpty(this.Username) &&
90 string.IsNullOrEmpty(this.AlternativeName) &&
94 partial
void OnUsernameChanged(
string? value)
96 this.UsernameIsValid = IsValidUsernameString(value);
97 if (this.UsernameIsValid)
99 this.AlternativeName =
string.Empty;
100 this.LocalizedValidationMessage =
string.Empty;
105 this.AlternativeName = this.GenerateUsername(value);
108 this.CreateAccountCommand.NotifyCanExecuteChanged();
111 partial
void OnAlternativeNameChanged(
string? value)
113 this.CreateAccountCommand.NotifyCanExecuteChanged();
117 private void SelectName(
string? name)
119 if (
string.IsNullOrEmpty(name))
122 this.Username = name;
123 this.AlternativeName =
string.Empty;
126 [RelayCommand(CanExecute = nameof(CanCreateAccount))]
127 private async Task CreateAccount()
132 string? Account = this.Username;
133 if (
string.IsNullOrEmpty(Account))
153 if (this.CoordinatorViewModel is not
null)
154 await this.CoordinatorViewModel.GoToStepCommand.ExecuteAsync(
OnboardingStep.CreateAccount);
158 bool InternalServerErrorRetried =
false;
164 (
bool Succeeded,
string? ErrorMessage,
string[]? Alternatives) = await
ServiceRef.
XmppService.TryConnectAndCreateAccount(
174 typeof(
App).Assembly,
177 if (Alternatives is not
null && Alternatives.Length > 0)
179 Random Rnd =
new Random();
180 this.UsernameIsValid =
false;
181 this.AlternativeName = Alternatives[Rnd.Next(0, Alternatives.Length)];
184 else if (!Succeeded && !
string.IsNullOrEmpty(ErrorMessage))
196 if (!InternalServerErrorRetried)
198 InternalServerErrorRetried =
true;
199 ServiceRef.
LogService.LogWarning(
"Internal server error during account creation. Retrying once.");
220 await this.StartCooldownAsync();
227 bool changed = this.IsBusy != isBusy;
228 base.SetIsBusy(isBusy);
230 this.CreateAccountCommand.NotifyCanExecuteChanged();
233 private async Task StartCooldownAsync(
int seconds = 3)
235 this.cooldownCts?.Cancel();
236 this.cooldownCts?.Dispose();
237 this.cooldownCts =
new CancellationTokenSource();
239 this.CooldownSecondsLeft = seconds;
240 this.CreateAccountCommand.NotifyCanExecuteChanged();
244 while (this.CooldownSecondsLeft > 0)
246 await Task.Delay(1000, this.cooldownCts.Token);
247 this.CooldownSecondsLeft--;
250 catch (TaskCanceledException)
256 if (this.CooldownSecondsLeft != 0)
258 this.CooldownSecondsLeft = 0;
260 this.CreateAccountCommand.NotifyCanExecuteChanged();
264 private static bool IsValidUsernameString(
string? input)
266 if (
string.IsNullOrEmpty(input))
269 foreach (
char c
in input)
325 private string? GenerateUsername(
string? source)
327 if (
string.IsNullOrWhiteSpace(source))
330 string normalizedSource = source.Normalize(NormalizationForm.FormC).ToLowerInvariant();
332 IEnumerable<string?> processedParts = normalizedSource
333 .Split(
' ', StringSplitOptions.RemoveEmptyEntries)
334 .Select(ReplaceInvalidUsernameCharacters)
335 .Where(processedPart => !
string.IsNullOrEmpty(processedPart));
337 if (!processedParts.Any())
340 string result =
string.Join(
".", processedParts);
342 string randomDigits =
new Random().Next(0, 10000).ToString(
"D4", CultureInfo.InvariantCulture);
343 result += randomDigits;
345 result =
System.Text.RegularExpressions.Regex.Replace(result,
@"\.+",
".");
350 private static string ReplaceInvalidUsernameCharacters(
string input)
352 StringBuilder sb =
new(input.Length);
353 foreach (
char c
in input)
409 return sb.ToString();
418 GC.SuppressFinalize(
this);
425 protected virtual void Dispose(
bool disposing)
429 this.disposed =
true;
433 this.cooldownCts?.Cancel();
434 this.cooldownCts?.Dispose();
435 this.cooldownCts =
null;
Represents an instance of the Neuro-Access app.
const string Default
The default language code.
A set of never changing property constants and helpful values.
A strongly-typed resource class, for looking up localized strings, etc.
static string UsernameNameAlreadyTaken
Looks up a localized string similar to Username already exists. Please try another....
static string ContinueSecondsFormat
Looks up a localized string similar to Continue ({0}).
static string Continue
Looks up a localized string similar to Continue.
static string Ok
Looks up a localized string similar to OK.
static string InvalidUsernameCharacters
Looks up a localized string similar to Username contains invalid characters..
static string UnableToConnect
Looks up a localized string similar to Unable to connect..
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
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 ITagProfile TagProfile
TAG Profile service.
static ICryptoService CryptoService
Crypto service.
static IReportingStringLocalizer Localizer
Localization service
static IXmppService XmppService
The XMPP service for XMPP communication.
override void SetIsBusy(bool isBusy)
Sets the IsBusy property.
virtual void Dispose(bool disposing)
Disposes resources used by the NameEntryOnboardingStepViewModel.
void Dispose()
Disposes resources used by the NameEntryOnboardingStepViewModel.
override async Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
override async Task OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
The server has experienced a misconfiguration or other internal error that prevents it from processin...
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
string PasswordHashMethod
Password hash method.
string PasswordHash
Hash value of password. Depends on method used to authenticate user.
string? Account
The account name for this profile
string? ApiKey
API Key, for creating new account.
string? ApiSecret
API Secret, for creating new account.
bool NeedsUpdating()
Returns true if the current ITagProfile needs to have its values updated, false otherwise.
void SetAccount(string AccountName, string ClientPasswordHash, string ClientPasswordHashMethod)
Set the account name and password for a new account.
string? Domain
The domain this profile is connected to.
OnboardingStep
Unified onboarding steps matching the registration journey.
XmppState
State of XMPP connection.