1using CommunityToolkit.Mvvm.ComponentModel;
2using CommunityToolkit.Mvvm.Input;
15using System.Collections.ObjectModel;
16using System.ComponentModel;
17using System.Globalization;
39 private readonly Dictionary<string, PresenceEventArgs> presences =
new(StringComparer.InvariantCultureIgnoreCase);
50 this.navigationArguments = Args;
51 this.thing = Args?.
Thing;
54 this.Notifications = [];
56 if (this.thing?.MetaData is not
null)
58 foreach (
Property Tag
in this.thing.MetaData)
62 this.InContacts = !
string.IsNullOrEmpty(this.thing?.ObjectId);
63 this.IsOwner = this.thing?.Owner ??
false;
64 this.IsSensor = this.thing?.IsSensor ??
false;
65 this.IsActuator = this.thing?.IsActuator ??
false;
66 this.IsConcentrator = this.thing?.IsConcentrator ??
false;
67 this.IsNodeInConcentrator = !
string.IsNullOrEmpty(this.thing?.NodeId) || !
string.IsNullOrEmpty(this.thing?.SourceId) || !
string.IsNullOrEmpty(this.thing?.Partition);
68 this.SupportsSensorEvents = this.thing?.SupportsSensorEvents ??
false;
70 this.InContactsAndNotOwner = this.InContacts && !this.IsOwner;
71 this.NotInContacts = !this.InContacts;
72 this.IsConnectedAndOwner = this.IsConnected && this.IsOwner;
73 this.IsConnectedAndSensor = this.IsConnected && this.IsSensor;
74 this.IsConnectedAndActuator = this.IsConnected && this.IsActuator;
75 this.IsConnectedAndNotConcentrator = this.IsConnected && !this.IsConcentrator;
81 await base.OnInitializeAsync();
83 if (this.navigationArguments?.Events is not
null)
92 await Event.GetCategoryIcon(),
93 await Event.GetDescription(),
100 this.NrPendingChatMessages = c;
101 this.HasPendingChatMessages = c > 0;
104 this.HasNotifications = this.Notifications.Count > 0;
106 await this.CalcThingIsOnline();
108 ServiceRef.XmppService.OnPresence += this.Xmpp_OnPresence;
109 ServiceRef.XmppService.OnRosterItemAdded += this.Xmpp_OnRosterItemAdded;
110 ServiceRef.XmppService.OnRosterItemUpdated += this.Xmpp_OnRosterItemUpdated;
111 ServiceRef.XmppService.OnRosterItemRemoved += this.Xmpp_OnRosterItemRemoved;
112 ServiceRef.TagProfile.Changed += this.TagProfile_Changed;
113 ServiceRef.NotificationService.OnNewNotification += this.NotificationService_OnNewNotification;
114 ServiceRef.NotificationService.OnNotificationsDeleted += this.NotificationService_OnNotificationsDeleted;
116 if (this.IsConnected && this.IsThingOnline)
117 await this.CheckCapabilities();
125 base.XmppService_ConnectionStateChanged(
_, NewState);
127 MainThread.BeginInvokeOnMainThread(async () => await this.CalcThingIsOnline());
129 return Task.CompletedTask;
132 private async Task CheckCapabilities()
134 if (this.InContacts &&
135 this.thing is not
null &&
136 (!this.thing.IsSensor.HasValue ||
137 !
this.thing.IsActuator.HasValue ||
138 !
this.thing.IsConcentrator.HasValue ||
139 !
this.thing.SupportsSensorEvents.HasValue))
141 string? FullJid = this.GetFullJid();
143 if (!
string.IsNullOrEmpty(FullJid))
148 if (!this.InContacts)
156 if (this.InContacts && !
string.IsNullOrEmpty(this.thing.ObjectId))
159 MainThread.BeginInvokeOnMainThread(() =>
161 this.IsSensor = this.thing.IsSensor ??
false;
162 this.IsActuator = this.thing.IsActuator ??
false;
163 this.IsConcentrator = this.thing.IsConcentrator ??
false;
164 this.SupportsSensorEvents = this.thing.SupportsSensorEvents ??
false;
178 ServiceRef.XmppService.OnPresence -= this.Xmpp_OnPresence;
179 ServiceRef.XmppService.OnRosterItemAdded -= this.Xmpp_OnRosterItemAdded;
180 ServiceRef.XmppService.OnRosterItemUpdated -= this.Xmpp_OnRosterItemUpdated;
181 ServiceRef.XmppService.OnRosterItemRemoved -= this.Xmpp_OnRosterItemRemoved;
182 ServiceRef.TagProfile.Changed -= this.TagProfile_Changed;
183 ServiceRef.NotificationService.OnNewNotification -= this.NotificationService_OnNewNotification;
184 ServiceRef.NotificationService.OnNotificationsDeleted -= this.NotificationService_OnNotificationsDeleted;
186 await base.OnDisposeAsync();
196 if (!this.InContacts &&
string.Equals(e.
FromBareJID,
this.thing?.BareJid, StringComparison.OrdinalIgnoreCase))
198 if (
string.IsNullOrEmpty(this.thing?.ObjectId))
201 MainThread.BeginInvokeOnMainThread(() =>
203 this.InContacts =
true;
207 await this.CalcThingIsOnline();
217 private async Task CalcThingIsOnline()
220 if (this.thing is
null)
221 MainThread.BeginInvokeOnMainThread(() => this.IsThingOnline =
false);
226 await MainThread.InvokeOnMainThreadAsync(() => this.IsThingOnline = this.IsOnline(this.thing.BareJid));
227 if (this.IsThingOnline)
228 await this.CheckCapabilities();
239 async partial
void OnInContactsChanged(
bool value)
241 await this.CalcThingIsOnline();
243 private bool IsOnline(
string BareJid)
250 if (
Item is not
null &&
Item.HasLastPresence)
251 return Item.LastPresence.IsOnline;
261 private string? GetFullJid()
263 if (this.thing is
null)
268 return (e?.IsOnline ==
true) ? e.From :
null;
274 if (
Item is
null || !
Item.HasLastPresence || !
Item.LastPresence.IsOnline)
277 return Item.LastPresenceFullJid;
287 private void TagProfile_Changed(
object? Sender, PropertyChangedEventArgs e)
289 MainThread.BeginInvokeOnMainThread(async () => await this.CalcThingIsOnline());
297 public ObservableCollection<HumanReadableTag>
Tags {
get; }
308 [NotifyCanExecuteChangedFor(nameof(RemoveFromListCommand))]
309 [NotifyCanExecuteChangedFor(nameof(AddToListCommand))]
310 private bool inContacts;
316 [NotifyCanExecuteChangedFor(nameof(RemoveFromListCommand))]
317 [NotifyCanExecuteChangedFor(nameof(AddToListCommand))]
318 private bool notInContacts;
321 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
323 base.OnPropertyChanged(e);
324 MainThread.BeginInvokeOnMainThread(() =>
327 switch (e.PropertyName)
329 case nameof(this.IsBusy):
330 this.ReadSensorCommand.NotifyCanExecuteChanged();
331 this.ControlActuatorCommand.NotifyCanExecuteChanged();
332 this.ChatCommand.NotifyCanExecuteChanged();
337 switch (e.PropertyName)
339 case nameof(this.InContacts):
340 case nameof(this.IsOwner):
341 this.InContactsAndNotOwner = this.InContacts && !this.IsOwner;
342 this.NotInContacts = !this.InContacts;
346 switch (e.PropertyName)
348 case nameof(this.IsConnected):
349 case nameof(this.IsOwner):
350 this.IsConnectedAndOwner = this.IsConnected && this.IsOwner;
354 switch (e.PropertyName)
356 case nameof(this.IsConnected):
357 case nameof(this.IsSensor):
358 this.IsConnectedAndSensor = this.IsConnected && this.IsSensor;
362 switch (e.PropertyName)
364 case nameof(this.IsConnected):
365 case nameof(this.IsActuator):
366 this.IsConnectedAndActuator = this.IsConnected && this.IsActuator;
370 switch (e.PropertyName)
372 case nameof(this.IsConnected):
373 case nameof(this.IsConcentrator):
374 this.IsConnectedAndNotConcentrator = this.IsConnected && !this.IsConcentrator;
380 public string? FriendlyName => this.thing?.FriendlyName ?? this.thing?.BareJid ??
string.Empty;
386 private bool isOwner;
392 [NotifyCanExecuteChangedFor(nameof(RemoveFromListCommand))]
393 [NotifyCanExecuteChangedFor(nameof(AddToListCommand))]
394 private bool inContactsAndNotOwner;
400 [NotifyCanExecuteChangedFor(nameof(DeleteRulesCommand))]
401 [NotifyCanExecuteChangedFor(nameof(DisownThingCommand))]
402 private bool isConnectedAndOwner;
408 [NotifyCanExecuteChangedFor(nameof(ReadSensorCommand))]
409 private bool isConnectedAndSensor;
415 [NotifyCanExecuteChangedFor(nameof(ControlActuatorCommand))]
416 private bool isConnectedAndActuator;
422 private bool isConnectedAndNotConcentrator;
428 private bool isThingOnline;
434 [NotifyCanExecuteChangedFor(nameof(ReadSensorCommand))]
435 private bool isSensor;
441 [NotifyCanExecuteChangedFor(nameof(ControlActuatorCommand))]
442 private bool isActuator;
448 private bool isConcentrator;
454 private bool isNodeInConcentrator;
460 private bool supportsSensorEvents;
465 private bool hasNotifications;
471 private bool hasPendingChatMessages;
477 private int nrPendingChatMessages;
485 private static Task Click(
object obj)
488 return ViewClaimThing.ViewClaimThingViewModel.LabelClicked(Tag.Name, Tag.Value, Tag.LocalizedValue);
489 else if (obj is
string s)
490 return ViewClaimThing.ViewClaimThingViewModel.LabelClicked(
string.Empty, s, s);
492 return Task.CompletedTask;
499 private async Task CopyQr(
object Item)
505 await Clipboard.SetTextAsync(this.
Link);
524 [RelayCommand(CanExecute = nameof(IsConnectedAndOwner))]
525 private async Task DeleteRules()
527 if (this.thing is
null)
539 if (!await this.authenticationService.AuthenticateUserAsync(
AuthenticationPurpose.DeleteRules,
true))
542 TaskCompletionSource<bool> Result =
new();
544 ServiceRef.
XmppService.DeleteDeviceRules(this.thing.RegistryJid,
this.thing.BareJid,
this.thing.NodeId,
545 this.thing.SourceId,
this.thing.Partition, (sender, e) =>
548 Result.TrySetResult(
true);
549 else if (e.StanzaError is not
null)
550 Result.TrySetException(e.StanzaError);
552 Result.TrySetResult(
false);
554 return Task.CompletedTask;
558 if (!await Result.Task)
574 [RelayCommand(CanExecute = nameof(IsConnectedAndOwner))]
575 private async Task DisownThing()
577 if (this.thing is
null)
589 if (!await this.authenticationService.AuthenticateUserAsync(
AuthenticationPurpose.DisownThing,
true))
593 ServiceRef.
XmppService.Disown(this.thing.RegistryJid,
this.thing.BareJid,
this.thing.SourceId,
this.thing.Partition,
this.thing.NodeId));
600 if (!
string.IsNullOrEmpty(this.thing?.ObjectId))
605 this.thing.ObjectId =
null;
608 if (this.thing is not
null)
609 this.thing.ObjectId =
null;
611 this.InContacts =
false;
628 [RelayCommand(CanExecute = nameof(NotInContacts))]
629 private async Task AddToList()
631 if (this.thing is
null)
635 if (!await this.authenticationService.AuthenticateUserAsync(
AuthenticationPurpose.AddToListOfThings))
644 IdXml =
string.Empty;
647 StringBuilder Xml =
new();
649 IdXml = Xml.ToString();
653 MainThread.BeginInvokeOnMainThread(() => this.NotInContacts =
false);
655 await MainThread.InvokeOnMainThreadAsync(async () =>
657 if (!this.InContacts)
659 if (
string.IsNullOrEmpty(this.thing.ObjectId))
662 this.InContacts =
true;
665 await this.CalcThingIsOnline();
679 [RelayCommand(CanExecute = nameof(InContactsAndNotOwner))]
680 private async Task RemoveFromList()
682 if (this.thing is
null)
687 if (!await this.authenticationService.AuthenticateUserAsync(
AuthenticationPurpose.RemoveFromListOfThings))
692 if (!
string.IsNullOrEmpty(this.thing.ObjectId))
695 this.thing.ObjectId =
null;
712 private bool CanReadSensor => !this.IsBusy && this.IsConnectedAndSensor;
717 [RelayCommand(CanExecute = nameof(CanReadSensor))]
718 private async Task ReadSensor()
720 await MainThread.InvokeOnMainThreadAsync(() => this.
SetIsBusy(
true));
722 if (this.thing is
null)
725 ViewThingNavigationArgs Args =
new(this.thing, MyThingsViewModel.GetNotificationEvents(this.thing) ?? []);
729 await MainThread.InvokeOnMainThreadAsync(() => this.
SetIsBusy(
false));
733 private bool CanControlActuator => !this.IsBusy && this.IsConnectedAndActuator;
738 [RelayCommand(CanExecute = nameof(CanControlActuator))]
739 private async Task ControlActuator()
741 await MainThread.InvokeOnMainThreadAsync(() => this.
SetIsBusy(
true));
743 if (this.thing is
null)
748 string? FullJid = this.GetFullJid();
749 if (
string.IsNullOrEmpty(FullJid))
754 if (
string.IsNullOrEmpty(this.thing.NodeId) &&
string.IsNullOrEmpty(
this.thing.SourceId) &&
string.IsNullOrEmpty(
this.thing.Partition))
755 ServiceRef.
XmppService.GetControlForm(FullJid, SelectedLanguage.Name,
this.ControlFormCallback,
null);
758 ThingReference ThingRef =
new(this.thing.NodeId, this.thing.SourceId, this.thing.Partition);
759 ServiceRef.
XmppService.GetControlForm(FullJid, SelectedLanguage.Name,
this.ControlFormCallback,
null, ThingRef);
772 MainThread.BeginInvokeOnMainThread(async () =>
781 MainThread.BeginInvokeOnMainThread(() => this.
SetIsBusy(
false));
782 return Task.CompletedTask;
786 private bool CanChat => !this.IsBusy && this.IsConnectedAndNotConcentrator;
791 [RelayCommand(CanExecute = nameof(CanChat))]
792 private async Task Chat()
794 await MainThread.InvokeOnMainThreadAsync(() => this.
SetIsBusy(
true));
796 if (this.thing is
null)
801 string LegalId = this.thing.LegalId;
802 string FriendlyName = this.thing.FriendlyName;
813 await MainThread.InvokeOnMainThreadAsync(() => this.
SetIsBusy(
false));
820 if (this.thing is not
null)
822 MainThread.BeginInvokeOnMainThread(() =>
824 bool IsNode = this.IsNodeInConcentrator;
825 string Key = this.thing.ThingNotificationCategoryKey;
826 int NrChatMessagesRemoved = 0;
832 case NotificationEventType.Contacts:
836 if (Event.Category != this.thing.BareJid)
840 case NotificationEventType.Things:
841 if (Event.Category != Key)
853 if (Model.Event.ObjectId == Event.ObjectId)
855 this.Notifications.RemoveAt(i);
857 if (Event.Type == NotificationEventType.Contacts)
858 NrChatMessagesRemoved++;
867 this.NrPendingChatMessages -= NrChatMessagesRemoved;
868 this.HasNotifications = this.Notifications.Count > 0;
869 this.HasPendingChatMessages = this.NrPendingChatMessages > 0;
873 return Task.CompletedTask;
878 MainThread.BeginInvokeOnMainThread(async () =>
882 await this.CalcThingIsOnline();
884 switch (e.Event.Type)
887 if (this.IsNodeInConcentrator)
890 if (e.Event.Category !=
this.thing?.BareJid)
895 if (e.Event.Category !=
this.thing?.ThingNotificationCategoryKey)
904 await e.Event.GetCategoryIcon(),
905 await e.Event.GetDescription(),
908 this.HasNotifications =
true;
912 this.NrPendingChatMessages++;
913 this.HasPendingChatMessages =
true;
922 return Task.CompletedTask;
925 private Task Xmpp_OnRosterItemRemoved(
object? Sender,
RosterItem Item)
927 this.presences.Remove(
Item.BareJid);
928 MainThread.BeginInvokeOnMainThread(async () => await this.CalcThingIsOnline());
929 return Task.CompletedTask;
932 private Task Xmpp_OnRosterItemUpdated(
object? Sender,
RosterItem Item)
934 MainThread.BeginInvokeOnMainThread(async () => await this.CalcThingIsOnline());
935 return Task.CompletedTask;
938 private Task Xmpp_OnRosterItemAdded(
object? Sender,
RosterItem Item)
940 MainThread.BeginInvokeOnMainThread(async () => await this.CalcThingIsOnline());
941 return Task.CompletedTask;
944 #region ILinkableView
949 public override bool IsLinkable =>
true;
954 public override bool EncodeAppLinks =>
true;
959 public override string Link
963 StringBuilder sb =
new();
965 bool HasSourceId =
false;
966 bool HasPartition =
false;
967 bool HasNodeId =
false;
968 bool HasRegistry =
false;
970 sb.Append(
"iotdisco:");
972 if (this.thing is not
null)
974 if (this.thing.MetaData is not
null)
976 foreach (
Property P
in this.thing.MetaData)
979 switch (P.
Name.ToUpper(CultureInfo.InvariantCulture))
1008 sb.Append(Uri.EscapeDataString(P.
Name));
1010 sb.Append(Uri.EscapeDataString(P.
Value));
1019 sb.Append(Uri.EscapeDataString(
this.thing.BareJid));
1022 if (!HasSourceId && !
string.IsNullOrEmpty(this.thing.SourceId))
1025 sb.Append(Uri.EscapeDataString(
this.thing.SourceId));
1028 if (!HasPartition && !
string.IsNullOrEmpty(this.thing.Partition))
1031 sb.Append(Uri.EscapeDataString(
this.thing.Partition));
1034 if (!HasNodeId && !
string.IsNullOrEmpty(this.thing.NodeId))
1037 sb.Append(Uri.EscapeDataString(
this.thing.NodeId));
1040 if (!HasRegistry && !
string.IsNullOrEmpty(this.thing.RegistryJid))
1043 sb.Append(Uri.EscapeDataString(
this.thing.RegistryJid));
1047 return sb.ToString();
1054 public override Task<string> Title => Task.FromResult(this.thing?.FriendlyName ??
string.Empty);
1059 public override bool HasMedia =>
false;
1069 public override string? MediaContentType =>
null;
Represents an instance of the Neuro-Access app.
static LanguageInfo SelectedLanguage
Gets the selected language.
XMPP Protocol Properties.
const string Partition
Partition
const string SourceId
Source ID
const string Latitude
Latitude
const string Registry
Registry
const string NodeId
Node ID
const string Altitude
Altitude
const string Jid
Jabber ID
const string Version
Version
const string Longitude
Longitude
A set of never changing property constants and helpful values.
A strongly-typed resource class, for looking up localized strings, etc.
static string Yes
Looks up a localized string similar to Yes.
static string Cancel
Looks up a localized string similar to Cancel.
static string ThingDisowned
Looks up a localized string similar to This has been successfully disowned. It can now be claimed by ...
static string Question
Looks up a localized string similar to Question.
static string DeleteRulesQuestion
Looks up a localized string similar to Do you want to delete all provisioning rules for the thing?...
static string ARequestHasBeenSentToTheOwner
Looks up a localized string similar to A request has been sent to the owner of the device....
static string RulesDeleted
Looks up a localized string similar to Provisioning rules have been deleted..
static string DisownThingQuestion
Looks up a localized string similar to Do you want to disown the thing? This will remove the thing fr...
static string SuccessTitle
Looks up a localized string similar to Success.
Base class that references services in the app.
static IServiceProvider Provider
The service provider for the app. This is set before the app is started, and will be used to resolve ...
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 ITagProfile TagProfile
TAG Profile service.
static IReportingStringLocalizer Localizer
Localization service
static IXmppService XmppService
The XMPP service for XMPP communication.
virtual async Task GoBack()
Method called when user wants to navigate to the previous screen.
virtual void SetIsBusy(bool IsBusy)
Sets the IsBusy property.
Holds navigation parameters specific to views displaying a list of contacts.
string? BareJid
Bare JID of remote chat party
A page that displays a list of the current user's contacts.
A page that displays an XMPP Form to the user.
A view model that holds the XMPP state.
void GenerateQrCode(string Uri)
Generates a QR-code
Class used to present a meta-data tag in a human interface.
A page that displays sensor data from a sensor.
Holds navigation parameters specific to viewing things.
ContactInfo? Thing
Thing information.
The view model to bind to when displaying a thing.
override async Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
ViewThingViewModel(ViewThingNavigationArgs? Args)
Creates an instance of the ViewThingViewModel class.
override string Link
Link to the current view
override Task XmppService_ConnectionStateChanged(object? _, XmppState NewState)
Listens to connection state changes from the XMPP server.
override async Task OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
ObservableCollection< EventModel > Notifications
Holds a list of notifications.
ObservableCollection< HumanReadableTag > Tags
Holds a list of meta-data tags associated with a thing.
Implements an XMPP concentrator server interface.
static readonly string[] NamespacesConcentrator
Supported concentrator namespaces.
string Name
Name of property
string Value
Property value
Implements an XMPP control client interface.
static readonly string[] NamespacesControl
Supported control namespaces
bool Ok
If the response is an OK result response (true), or an error response (false).
XmppException StanzaError
Any stanza error returned.
Event arguments for presence events.
string FromBareJID
Bare JID of resource sending the presence.
PresenceType Type
Type of presence received.
Maintains information about an item in the roster.
Implements an XMPP sensor client interface.
static readonly string[] NamespacesSensorEvents
Supported sensor event namespaces.
static readonly string[] NamespacesSensorData
Supported sensor-data namespaces.
Contains information about an item of an entity.
Event arguments for service discovery responses.
bool HasAnyFeature(params string[] Features)
Checks if the remote entity supports any of a set of features.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
static IDatabaseProvider Provider
Registered database provider.
static async Task Update(object Object)
Updates an object in the database.
static async Task Delete(object Object)
Deletes an object in the database.
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Contains a reference to a thing
Task GoToAsync(string Route)
Navigates to the specified route and pushes the page onto the navigation stack.
Task DisplayException(Exception Exception, string? Title=null)
Displays an alert/message box to the user.
Task< bool > DisplayAlert(string Title, string Message, string? Accept=null, string? Cancel=null)
Displays an alert/message box to the user.
Task Flush()
Persists any pending changes.
abstract class NotificationEvent()
Abstract base class of notification events.
class NotificationEventsArgs(NotificationEvent[] Events)
Event argument for notification events.
class NotificationEventArgs(NotificationEvent Event)
Event argument for notification events.
NotificationEventType
Button on which event is to be displayed.
BackMethod
Navigation Back Method
AuthenticationPurpose
Purpose for requesting the user to authenticate itself.
Notifications
Determines if the resource is observable, and how notifications are sent.
PresenceType
Type of presence received.
SubscriptionState
State of a presence subscription.
XmppState
State of XMPP connection.