Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
SettingsViewModel.cs
1using System.ComponentModel;
2using System.Globalization;
3using System.Reflection;
5using System.Text;
6using System.Xml;
7using CommunityToolkit.Mvvm.ComponentModel;
8using CommunityToolkit.Mvvm.Input;
9using CommunityToolkit.Mvvm.Messaging;
26
28{
32 public partial class SettingsViewModel : XmppViewModel
33 {
34 private readonly IAuthenticationService authenticationService = ServiceRef.Provider.GetRequiredService<IAuthenticationService>();
35
36 private const string allowed = "Allowed";
37 private const string prohibited = "Prohibited";
38
39 private readonly bool initializing = false;
40
45 : base()
46 {
47 this.initializing = true;
48 try
49 {
50 this.CanProhibitScreenCapture = ServiceRef.PlatformSpecific.CanProhibitScreenCapture;
51 this.ScreenCaptureMode = ServiceRef.PlatformSpecific.ProhibitScreenCapture ? prohibited : allowed;
52
54 this.CanUseAlternativeAuthenticationMethods = this.CanUseFingerprint;
55 this.AuthenticationMethod = ServiceRef.TagProfile.AuthenticationMethod.ToString();
56 this.ApprovedAuthenticationMethod = this.AuthenticationMethod;
57
58 this.DisplayMode = CurrentDisplayMode.ToString();
59
60 // App and Hardware information
61 this.VersionNumber = AppInfo.VersionString;
62 this.BuildNumber = AppInfo.BuildString;
63 this.BuildTime = GetBuildTime();
64 this.DeviceManufactorer = DeviceInfo.Manufacturer.ToString();
65 this.DeviceModel = DeviceInfo.Model.ToString();
66 this.DevicePlatform = DeviceInfo.Platform.ToString();
67 this.DeviceVersion = DeviceInfo.Version.ToString();
68 }
69 finally
70 {
71 this.initializing = false;
72 }
73 }
74
81 internal SettingsPage? Page { get; set; }
82
83 public override async Task OnInitializeAsync()
84 {
85 await base.OnInitializeAsync();
86 this.NotifyCommandsCanExecuteChanged();
87 }
88
90 protected override Task XmppService_ConnectionStateChanged(object? Sender, XmppState NewState)
91 {
92 return MainThread.InvokeOnMainThreadAsync(async () =>
93 {
94 await base.XmppService_ConnectionStateChanged(Sender, NewState);
95
96 this.NotifyCommandsCanExecuteChanged();
97 });
98 }
99
101 public override void SetIsBusy(bool IsBusy)
102 {
103 base.SetIsBusy(IsBusy);
104 this.NotifyCommandsCanExecuteChanged();
105 }
106
107 private void NotifyCommandsCanExecuteChanged()
108 {
109 this.RevokeCommand.NotifyCanExecuteChanged();
110 this.CompromiseCommand.NotifyCanExecuteChanged();
111 this.TransferCommand.NotifyCanExecuteChanged();
112 this.ChangePasswordCommand.NotifyCanExecuteChanged();
113 }
114
115 public bool IsBetaEnabled
116 {
118 set
119 {
121 {
122 ServiceRef.TagProfile.HasBetaFeatures = value;
123 }
124 }
125 }
126
127 #region Properties
128
132 [ObservableProperty]
133 private bool canProhibitScreenCapture;
134
138 [ObservableProperty]
139 private string screenCaptureMode;
140
144 [ObservableProperty]
145 private string displayMode;
146
150 [ObservableProperty]
151 private bool restartNeeded;
152
156 [ObservableProperty]
157 private bool canUseFingerprint;
158
162 [ObservableProperty]
163 private bool canUseAlternativeAuthenticationMethods;
164
168 [ObservableProperty]
169 private string authenticationMethod;
170
174 [ObservableProperty]
175 private string approvedAuthenticationMethod;
176
180 [ObservableProperty]
181 private string versionNumber;
182
186 [ObservableProperty]
187 private string buildNumber;
188
192 [ObservableProperty]
193 private string deviceManufactorer;
194
198 [ObservableProperty]
199 private string deviceModel;
200
204 [ObservableProperty]
205 private string devicePlatform;
206
210 [ObservableProperty]
211 private string deviceVersion;
212
216 [ObservableProperty]
217 private string buildTime;
218
222 public static AppTheme CurrentDisplayMode
223 {
224 get
225 {
226 AppTheme? Result = Application.Current?.UserAppTheme;
227
228 if (!Result.HasValue)
229 Result = Application.Current?.PlatformAppTheme;
230
231 return Result ?? AppTheme.Unspecified;
232 }
233 }
234
238 public bool CanExecuteCommands => !this.IsBusy && this.IsConnected;
239
243 protected override async void OnPropertyChanged(PropertyChangedEventArgs e)
244 {
245 try
246 {
247 switch (e.PropertyName)
248 {
249 case nameof(this.DisplayMode):
250 if (!this.initializing && Enum.TryParse(this.DisplayMode, out AppTheme Theme) && Theme != CurrentDisplayMode)
251 {
252 ServiceRef.ThemeService.SetTheme(Theme);
253 }
254 break;
255
256 case nameof(this.ScreenCaptureMode):
257 if (!this.initializing)
258 {
259 switch (this.ScreenCaptureMode)
260 {
261 case allowed:
262 await PermitScreenCapture();
263 break;
264
265 case prohibited:
266 await ProhibitScreenCapture();
267 break;
268 }
269 }
270 break;
271
272 case nameof(this.AuthenticationMethod):
273 if (!this.initializing &&
274 this.AuthenticationMethod != this.ApprovedAuthenticationMethod &&
275 Enum.TryParse(this.AuthenticationMethod, out AuthenticationMethod AuthenticationMethod))
276 {
277 if (await this.authenticationService.AuthenticateUserAsync(AuthenticationPurpose.ChangeAuthenticationMethod, true))
278 {
279 ServiceRef.TagProfile.AuthenticationMethod = AuthenticationMethod;
280 this.ApprovedAuthenticationMethod = this.AuthenticationMethod;
281 }
282 else
283 {
284 this.AuthenticationMethod = this.ApprovedAuthenticationMethod;
285
286 if (this.Page is not null)
287 {
288 // Needed to propagate radio-button states, as the current version of Maui does not
289 // handle these properly (at the time of writing).
290 //
291 // TODO: Check if this has been fixed after updating Maui and related components.
292
293 switch (Enum.Parse<AuthenticationMethod>(this.AuthenticationMethod))
294 {
295 case AuthenticationMethod.Password:
296 this.Page.Fingerprint.IsChecked = false;
297 this.Page.UsePassword.IsChecked = true;
298 break;
299
300 case AuthenticationMethod.Fingerprint:
301 this.Page.UsePassword.IsChecked = false;
302 this.Page.Fingerprint.IsChecked = true;
303 break;
304 }
305 }
306 }
307 }
308 break;
309 }
310 }
311 catch (Exception ex)
312 {
313 ServiceRef.LogService.LogException(ex);
314 }
315 }
316
321 private static string GetBuildTime()
322 {
323 Assembly Assembly = Assembly.GetExecutingAssembly();
324
325 const string BuildVersionMetadataPrefix = "+build";
326
327 AssemblyInformationalVersionAttribute? Attribute = Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
328 if (Attribute?.InformationalVersion is not null)
329 {
330 string Value = Attribute.InformationalVersion;
331
332 int DatePosition = Value.IndexOf(BuildVersionMetadataPrefix, System.StringComparison.OrdinalIgnoreCase);
333 if (DatePosition > 0)
334 {
335 Value = Value[(DatePosition + BuildVersionMetadataPrefix.Length)..];
336
337 return Value;
338 }
339 }
340
341 return string.Empty;
342 }
343
344 #endregion
345
346 #region Commands
347
348 [RelayCommand(CanExecute = nameof(CanExecuteCommands))]
349 internal async Task ChangePassword()
350 {
351 try
352 {
353 //Authenticate user
354 await this.authenticationService.CheckUserBlockingAsync();
355 if (await this.authenticationService.AuthenticateUserAsync(AuthenticationPurpose.ChangePassword, true) == false)
356 return;
357
358 //Update the network password
359 await ServiceRef.XmppService.TryGenerateAndChangePassword();
360
361 //Update the local password
362 await ServiceRef.NavigationService.GoToAsync(nameof(OnboardingPage), new OnboardingNavigationArgs() { Scenario = OnboardingScenario.ChangePin });
363
364 //Listen for completed event
365 WeakReferenceMessenger.Default.Register<RegistrationPageMessage>(this, this.HandleRegistrationPageMessage);
366 }
367 catch (Exception Ex)
368 {
369 ServiceRef.LogService.LogException(Ex);
370 await ServiceRef.UiService.DisplayException(Ex);
371 }
372 }
373
374 private async void HandleRegistrationPageMessage(object recipient, RegistrationPageMessage msg)
375 {
376 if (msg.Step != RegistrationStep.Complete)
377 return;
378 await ServiceRef.UiService.DisplayAlert(
381 WeakReferenceMessenger.Default.Unregister<RegistrationPageMessage>(this);
382 }
383
384 private static async Task PermitScreenCapture()
385 {
387 return;
388
389 if (!await ServiceRef.Provider.GetRequiredService<IAuthenticationService>().AuthenticateUserAsync(AuthenticationPurpose.PermitScreenCapture))
390 return;
391
392 ServiceRef.PlatformSpecific.ProhibitScreenCapture = false;
393 }
394
395 private static async Task ProhibitScreenCapture()
396 {
398 return;
399
400 if (!await ServiceRef.Provider.GetRequiredService<IAuthenticationService>().AuthenticateUserAsync(AuthenticationPurpose.ProhibitScreenCapture))
401 return;
402
403 ServiceRef.PlatformSpecific.ProhibitScreenCapture = true;
404 }
405
407 public override async Task GoBack()
408 {
409 if (this.RestartNeeded)
410 await App.StopAsync();
411 else
412 await base.GoBack();
413 }
414
415 [RelayCommand(CanExecute = nameof(CanExecuteCommands))]
416 private async Task Revoke()
417 {
419 return;
420
421 try
422 {
424 return;
425
426 if (!await this.authenticationService.AuthenticateUserAsync(AuthenticationPurpose.RevokeIdentity, true))
427 return;
428
429 (bool succeeded, LegalIdentity? RevokedIdentity) = await ServiceRef.NetworkService.TryRequest(async () =>
430 {
431 try
432 {
433 return await ServiceRef.XmppService.ObsoleteLegalIdentity(ServiceRef.TagProfile.LegalIdentity.Id);
434 }
435 catch (ForbiddenException)
436 {
437 return null;
438 }
439 });
440
441 if (succeeded)
442 {
443 if (RevokedIdentity is not null)
444 await ServiceRef.TagProfile.RevokeLegalIdentity(RevokedIdentity);
445 else
447 await ServiceRef.NavigationService.GoToAsync(nameof(OnboardingPage), new OnboardingNavigationArgs() { Scenario = OnboardingScenario.ReverifyIdentity });
448 }
449 }
450 catch (Exception ex)
451 {
452 ServiceRef.LogService.LogException(ex);
453 await ServiceRef.UiService.DisplayException(ex);
454 }
455 }
456
457 [RelayCommand(CanExecute = nameof(CanExecuteCommands))]
458 private async Task Compromise()
459 {
461 return;
462
463 try
464 {
466 return;
467
468 if (!await this.authenticationService.AuthenticateUserAsync(AuthenticationPurpose.ReportAsCompromized, true))
469 return;
470
471 (bool succeeded, LegalIdentity? CompromisedIdentity) = await ServiceRef.NetworkService.TryRequest(
472 () => ServiceRef.XmppService.CompromiseLegalIdentity(ServiceRef.TagProfile.LegalIdentity.Id));
473
474 if (succeeded && CompromisedIdentity is not null)
475 {
476 await ServiceRef.TagProfile.CompromiseLegalIdentity(CompromisedIdentity);
477 await ServiceRef.NavigationService.GoToAsync(nameof(OnboardingPage), new OnboardingNavigationArgs() { Scenario = OnboardingScenario.ReverifyIdentity });
478 }
479 }
480 catch (Exception ex)
481 {
482 ServiceRef.LogService.LogException(ex);
483 await ServiceRef.UiService.DisplayException(ex);
484 }
485 }
486
487 [RelayCommand(CanExecute = nameof(CanExecuteCommands))]
488 private async Task Transfer()
489 {
491 return;
492
493 try
494 {
495
496
497 if (!await ServiceRef.UiService.DisplayAlert(
502 {
503 return;
504 }
505
506 string? Password = await this.authenticationService.InputPasswordAsync(AuthenticationPurpose.TransferIdentity);
507 if (Password is null)
508 return;
509
510 this.SetIsBusy(true);
511
512 try
513 {
514 StringBuilder Xml = new();
515 XmlWriterSettings Settings = XML.WriterSettings(false, true);
516
517 using (XmlWriter Output = XmlWriter.Create(Xml, Settings))
518 {
519 Output.WriteStartElement("Transfer", ContractsClient.NamespaceOnboarding);
520
521 await ServiceRef.XmppService.ExportSigningKeys(Output);
522
523 Output.WriteStartElement("Pin");
524 Output.WriteAttributeString("pin", Password);
525 Output.WriteEndElement();
526
527 Output.WriteStartElement("Account", ContractsClient.NamespaceOnboarding);
528 Output.WriteAttributeString("domain", ServiceRef.TagProfile.Domain);
529 Output.WriteAttributeString("userName", ServiceRef.TagProfile.Account);
530 Output.WriteAttributeString("password", ServiceRef.TagProfile.XmppPasswordHash);
531
532 if (!string.IsNullOrEmpty(ServiceRef.TagProfile.XmppPasswordHashMethod))
533 {
534 Output.WriteAttributeString("passwordMethod", ServiceRef.TagProfile.XmppPasswordHashMethod);
535 }
536
537 Output.WriteEndElement();
538 Output.WriteEndElement();
539 }
540
541 using RandomNumberGenerator Rnd = RandomNumberGenerator.Create();
542 byte[] Data = Encoding.UTF8.GetBytes(Xml.ToString());
543 byte[] Key = new byte[16];
544 byte[] IV = new byte[16];
545
546 Rnd.GetBytes(Key);
547 Rnd.GetBytes(IV);
548
549 using Aes Aes = Aes.Create();
550 Aes.BlockSize = 128;
551 Aes.KeySize = 256;
552 Aes.Mode = CipherMode.CBC;
553 Aes.Padding = PaddingMode.PKCS7;
554
555 using ICryptoTransform Transform = Aes.CreateEncryptor(Key, IV);
556 byte[] Encrypted = Transform.TransformFinalBlock(Data, 0, Data.Length);
557
558 Xml.Clear();
559
560 using (XmlWriter Output = XmlWriter.Create(Xml, Settings))
561 {
562 Output.WriteStartElement("Info", ContractsClient.NamespaceOnboarding);
563 Output.WriteAttributeString("base64", Convert.ToBase64String(Encrypted));
564 Output.WriteAttributeString("once", "true");
565 Output.WriteAttributeString("expires", XML.Encode(DateTime.UtcNow.AddMinutes(1)));
566 Output.WriteEndElement();
567 }
568
569 XmlElement Response = await ServiceRef.XmppService.IqSetAsync(Constants.Domains.OnboardingDomain, Xml.ToString());
570
571 foreach (XmlNode N in Response.ChildNodes)
572 {
573 if (N is XmlElement Info && Info.LocalName == "Code" && Info.NamespaceURI == ContractsClient.NamespaceOnboarding)
574 {
575 string Code = XML.Attribute(Info, "code");
576 string Url = "obinfo:" + Constants.Domains.IdDomain + ":" + Code + ":" +
577 Convert.ToBase64String(Key) + ":" + Convert.ToBase64String(IV);
578
579 await ServiceRef.XmppService.AddTransferCode(Code);
580 await ServiceRef.NavigationService.GoToAsync(nameof(TransferIdentityPage), new TransferIdentityNavigationArgs(Url));
581 return;
582 }
583 }
584
585 await ServiceRef.UiService.DisplayAlert(
588 }
589 finally
590 {
591 this.SetIsBusy(false);
592 }
593 }
594 catch (Exception ex)
595 {
596 ServiceRef.LogService.LogException(ex);
597 await ServiceRef.UiService.DisplayException(ex);
598 }
599 }
600
601 [RelayCommand]
602 private static async Task ChangeLanguage()
603 {
605 }
606
607 [RelayCommand]
608 private void ToggleDarkMode()
609 {
610 this.DisplayMode = "Dark";
611 }
612
613 [RelayCommand]
614 private async Task ClearCacheAsync()
615 {
616 try
617 {
618 // Internet cache
620 new FilterFieldGreaterOrEqualTo("Url", string.Empty));
621 await Database.Provider.Flush();
622
623 // Branding and KYC invalidations
624 string? domain = ServiceRef.TagProfile.Domain;
625 string? pubSub = ServiceRef.TagProfile.PubSubJid;
627 if (!string.IsNullOrWhiteSpace(domain))
628 await invalidation.InvalidateByParentId($"KycProcess:{domain}", scope: "Kyc");
629 if (!string.IsNullOrWhiteSpace(pubSub))
630 await invalidation.InvalidateByParentId(pubSub, scope: "Branding");
631
632 // ThemeService local cache
633 await ServiceRef.ThemeService.ClearBrandingCacheForCurrentDomain();
634
635
636 // Remove KYC drafts/current application (delete all, robustly)
637 IEnumerable<KycReference> drafts = Array.Empty<KycReference>();
638 try { drafts = await Database.Find<KycReference>(); } catch { /* ignore */ }
639 foreach (KycReference draft in drafts)
640 {
641 try { await Database.Delete(draft); } catch { /* ignore individual failures */ }
642 }
643 await Database.Provider.Flush();
644
645 await ServiceRef.UiService.DisplayAlert(
649 }
650 catch (Exception ex)
651 {
652 ServiceRef.LogService.LogException(ex);
653 await ServiceRef.UiService.DisplayException(ex);
654 }
655 }
656
657
658 #endregion
659
660 public void SetBetaFeaturesEnabled(bool Enabled)
661 {
662 this.IsBetaEnabled = Enabled;
663 }
664 }
665}
Represents an instance of the Neuro-Access app.
Definition: App.xaml.cs:125
const string OnboardingDomain
Neuro-Access onboarding domain.
Definition: Constants.cs:312
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 Yes
Looks up a localized string similar to Yes.
static string UnexpectedResponse
Looks up a localized string similar to An unexpected response was received..
static string AreYouSureYouWantToRevokeYourLegalIdentity
Looks up a localized string similar to Are you sure you want to revoke your identity from the applica...
static string CacheCleared
Looks up a localized string similar to Cache cleared.
static string AreYouSureYouWantToTransferYourLegalIdentity
Looks up a localized string similar to Are you sure you want to transfer your identity from this devi...
static string PasswordChanged
Looks up a localized string similar to PIN has been successfully changed..
static string Confirm
Looks up a localized string similar to Confirm.
static string Ok
Looks up a localized string similar to OK.
static string AreYouSureYouWantToReportYourLegalIdentityAsCompromized
Looks up a localized string similar to Are you sure you want to report your identity as compromised,...
static string No
Looks up a localized string similar to No.
static string SuccessTitle
Looks up a localized string similar to Success.
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
Contains information about a file in the local cache.
Definition: CacheEntry.cs:15
Contains a local reference to a KYC process.
Definition: KycReference.cs:22
Base class that references services in the app.
Definition: ServiceRef.cs:43
static IServiceProvider Provider
The service provider for the app. This is set before the app is started, and will be used to resolve ...
Definition: ServiceRef.cs:48
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 IPopupService PopupService
Popup service for presenting application popups.
Definition: ServiceRef.cs:142
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:202
static IReportingStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:370
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:190
static IPlatformSpecific PlatformSpecific
Localization service
Definition: ServiceRef.cs:383
static async Task< bool > AreYouSure(string Message)
Asks the user to confirm an action.
A page to display when the user wants to transfer an identity.
The view model to bind to for when displaying the settings page.
override void SetIsBusy(bool IsBusy)
Sets the IsBusy property.
override async Task GoBack()
Method called when user wants to navigate to the previous screen.
SettingsViewModel()
Creates an instance of the SettingsViewModel class.
static AppTheme CurrentDisplayMode
Current display mode
override async void OnPropertyChanged(PropertyChangedEventArgs e)
override Task XmppService_ConnectionStateChanged(object? Sender, XmppState NewState)
Listens to connection state changes from the XMPP server.
bool CanExecuteCommands
Used to find out if a command can execute
override async Task OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
Navigation arguments for onboarding flow. Scenario determines dynamic starting step.
A view model that holds the XMPP state.
Helps with common XML-related tasks.
Definition: XML.cs:21
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:1062
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
static XmlWriterSettings WriterSettings(bool Indent, bool OmitXmlDeclaration)
Gets an XML writer settings object.
Definition: XML.cs:1351
Adds support for legal identities, smart contracts and signatures to an XMPP client.
const string NamespaceOnboarding
http://waher.se/schema/Onboarding/v1.xsd
The requesting entity does not possess the necessary permissions to perform an action that only certa...
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static Task< IEnumerable< object > > FindDelete(string Collection, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Definition: Database.cs:1442
static IDatabaseProvider Provider
Registered database provider.
Definition: Database.cs:59
static async Task Delete(object Object)
Deletes an object in the database.
Definition: Database.cs:1291
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
This filter selects objects that have a named field greater or equal to a given value.
Static class that dynamically manages types and interfaces available in the runtime environment.
Definition: Types.cs:15
static object InstantiateDefault(Type Type, params object[] Arguments)
Returns an instance of the type Type . If one needs to be created, it is. If the constructor requires...
Definition: Types.cs:1566
bool CanProhibitScreenCapture
If screen capture prohibition is supported
bool SupportsFingerprintAuthentication
If the device supports authenticating the user using fingerprints.
bool HasBetaFeatures
If the user has Beta features enabled
Definition: ITagProfile.cs:262
AuthenticationMethod AuthenticationMethod
How the user authenticates itself with the App.
Definition: ITagProfile.cs:202
string? Account
The account name for this profile
Definition: ITagProfile.cs:102
Task CompromiseLegalIdentity(LegalIdentity compromisedIdentity)
Sets the current LegalIdentity to the compromised identity, and reverses the Step property.
string? XmppPasswordHash
A hash of the current XMPP password.
Definition: ITagProfile.cs:107
Task ClearLegalIdentity()
Revert the Set LegalIdentity
string? XmppPasswordHashMethod
The hash method used for hashing the XMPP password.
Definition: ITagProfile.cs:112
Task RevokeLegalIdentity(LegalIdentity revokedIdentity)
Sets the current LegalIdentity to the revoked identity, and reverses the Step property.
LegalIdentity? LegalIdentity
The legal identity of the current user/profile.
Definition: ITagProfile.cs:222
string? Domain
The domain this profile is connected to.
Definition: ITagProfile.cs:52
string? PubSubJid
The XMPP server's PubSub JID.
Definition: ITagProfile.cs:157
Task Flush()
Persists any pending changes.
AuthenticationMethod
How the user authenticates itself with the App.
RegistrationStep
The different steps of a TAG Profile registration journey.
AuthenticationPurpose
Purpose for requesting the user to authenticate itself.
class RegistrationPageMessage(RegistrationStep Step)
RegistrationPage view change message
Definition: Messages.cs:9
XmppState
State of XMPP connection.
Definition: XmppState.cs:7
Definition: App.xaml.cs:4