1using CommunityToolkit.Mvvm.ComponentModel;
2using CommunityToolkit.Mvvm.Input;
14using System.Collections.ObjectModel;
15using System.ComponentModel;
16using System.Globalization;
35 private readonly Dictionary<string, PresenceEventArgs> presences =
new(StringComparer.InvariantCultureIgnoreCase);
46 this.navigationArguments = Args;
47 this.thing = Args?.
Thing;
50 this.Notifications = [];
52 if (this.thing?.MetaData is not
null)
54 foreach (
Property Tag
in this.thing.MetaData)
58 this.InContacts = !
string.IsNullOrEmpty(this.thing?.ObjectId);
59 this.IsOwner = this.thing?.Owner ??
false;
60 this.IsSensor = this.thing?.IsSensor ??
false;
61 this.IsActuator = this.thing?.IsActuator ??
false;
62 this.IsConcentrator = this.thing?.IsConcentrator ??
false;
63 this.IsNodeInConcentrator = !
string.IsNullOrEmpty(this.thing?.NodeId) || !
string.IsNullOrEmpty(this.thing?.SourceId) || !
string.IsNullOrEmpty(this.thing?.Partition);
64 this.SupportsSensorEvents = this.thing?.SupportsSensorEvents ??
false;
66 this.InContactsAndNotOwner = this.InContacts && !this.IsOwner;
67 this.NotInContacts = !this.InContacts;
68 this.IsConnectedAndOwner = this.IsConnected && this.IsOwner;
69 this.IsConnectedAndSensor = this.IsConnected && this.IsSensor;
70 this.IsConnectedAndActuator = this.IsConnected && this.IsActuator;
71 this.IsConnectedAndNotConcentrator = this.IsConnected && !this.IsConcentrator;
77 await base.OnInitialize();
79 if (this.navigationArguments?.Events is not
null)
88 await Event.GetCategoryIcon(),
89 await Event.GetDescription(),
96 this.NrPendingChatMessages = c;
97 this.HasPendingChatMessages = c > 0;
100 this.HasNotifications = this.Notifications.Count > 0;
102 await this.CalcThingIsOnline();
104 ServiceRef.XmppService.OnPresence += this.Xmpp_OnPresence;
105 ServiceRef.XmppService.OnRosterItemAdded += this.Xmpp_OnRosterItemAdded;
106 ServiceRef.XmppService.OnRosterItemUpdated += this.Xmpp_OnRosterItemUpdated;
107 ServiceRef.XmppService.OnRosterItemRemoved += this.Xmpp_OnRosterItemRemoved;
108 ServiceRef.TagProfile.Changed += this.TagProfile_Changed;
109 ServiceRef.NotificationService.OnNewNotification += this.NotificationService_OnNewNotification;
110 ServiceRef.NotificationService.OnNotificationsDeleted += this.NotificationService_OnNotificationsDeleted;
112 if (this.IsConnected && this.IsThingOnline)
113 await this.CheckCapabilities();
121 base.XmppService_ConnectionStateChanged(_, NewState);
123 MainThread.BeginInvokeOnMainThread(async () => await this.CalcThingIsOnline());
125 return Task.CompletedTask;
128 private async Task CheckCapabilities()
130 if (this.InContacts &&
131 this.thing is not
null &&
132 (!this.thing.IsSensor.HasValue ||
133 !
this.thing.IsActuator.HasValue ||
134 !
this.thing.IsConcentrator.HasValue ||
135 !
this.thing.SupportsSensorEvents.HasValue))
137 string? FullJid = this.GetFullJid();
139 if (!
string.IsNullOrEmpty(FullJid))
144 if (!this.InContacts)
152 if (this.InContacts && !
string.IsNullOrEmpty(this.thing.ObjectId))
155 MainThread.BeginInvokeOnMainThread(() =>
157 this.IsSensor = this.thing.IsSensor ??
false;
158 this.IsActuator = this.thing.IsActuator ??
false;
159 this.IsConcentrator = this.thing.IsConcentrator ??
false;
160 this.SupportsSensorEvents = this.thing.SupportsSensorEvents ??
false;
174 ServiceRef.XmppService.OnPresence -= this.Xmpp_OnPresence;
175 ServiceRef.XmppService.OnRosterItemAdded -= this.Xmpp_OnRosterItemAdded;
176 ServiceRef.XmppService.OnRosterItemUpdated -= this.Xmpp_OnRosterItemUpdated;
177 ServiceRef.XmppService.OnRosterItemRemoved -= this.Xmpp_OnRosterItemRemoved;
178 ServiceRef.TagProfile.Changed -= this.TagProfile_Changed;
179 ServiceRef.NotificationService.OnNewNotification -= this.NotificationService_OnNewNotification;
180 ServiceRef.NotificationService.OnNotificationsDeleted -= this.NotificationService_OnNotificationsDeleted;
182 await base.OnDispose();
185 private async Task Xmpp_OnPresence(
object? Sender, PresenceEventArgs e)
190 this.presences[e.FromBareJID] = e;
192 if (!this.InContacts &&
string.Equals(e.FromBareJID,
this.thing?.BareJid, StringComparison.OrdinalIgnoreCase))
194 if (
string.IsNullOrEmpty(this.thing?.ObjectId))
197 MainThread.BeginInvokeOnMainThread(async () =>
199 this.InContacts =
true;
203 await this.CalcThingIsOnline();
208 this.presences.Remove(e.FromBareJID);
213 private async Task CalcThingIsOnline()
216 if (this.thing is
null)
217 MainThread.BeginInvokeOnMainThread(() => this.IsThingOnline =
false);
222 await MainThread.InvokeOnMainThreadAsync(() => this.IsThingOnline = this.IsOnline(this.thing.BareJid));
223 if (this.IsThingOnline)
224 await this.CheckCapabilities();
235 async partial
void OnInContactsChanged(
bool value)
237 await this.CalcThingIsOnline();
239 private bool IsOnline(
string BareJid)
241 if (this.presences.TryGetValue(BareJid, out PresenceEventArgs? e))
246 if (
Item is not
null &&
Item.HasLastPresence)
247 return Item.LastPresence.IsOnline;
257 private string? GetFullJid()
259 if (this.thing is
null)
263 if (this.presences.TryGetValue(
this.thing.BareJid, out PresenceEventArgs? e))
264 return (e?.IsOnline ==
true) ? e.From :
null;
270 if (
Item is
null || !
Item.HasLastPresence || !
Item.LastPresence.IsOnline)
273 return Item.LastPresenceFullJid;
283 private void TagProfile_Changed(
object? Sender, PropertyChangedEventArgs e)
285 MainThread.BeginInvokeOnMainThread(async () => await this.CalcThingIsOnline());
293 public ObservableCollection<HumanReadableTag>
Tags {
get; }
304 [NotifyCanExecuteChangedFor(nameof(RemoveFromListCommand))]
305 [NotifyCanExecuteChangedFor(nameof(AddToListCommand))]
306 private bool inContacts;
312 [NotifyCanExecuteChangedFor(nameof(RemoveFromListCommand))]
313 [NotifyCanExecuteChangedFor(nameof(AddToListCommand))]
314 private bool notInContacts;
317 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
319 base.OnPropertyChanged(e);
320 MainThread.BeginInvokeOnMainThread(() =>
323 switch (e.PropertyName)
325 case nameof(this.IsBusy):
326 this.ReadSensorCommand.NotifyCanExecuteChanged();
327 this.ControlActuatorCommand.NotifyCanExecuteChanged();
328 this.ChatCommand.NotifyCanExecuteChanged();
333 switch (e.PropertyName)
335 case nameof(this.InContacts):
336 case nameof(this.IsOwner):
337 this.InContactsAndNotOwner = this.InContacts && !this.IsOwner;
338 this.NotInContacts = !this.InContacts;
342 switch (e.PropertyName)
344 case nameof(this.IsConnected):
345 case nameof(this.IsOwner):
346 this.IsConnectedAndOwner = this.IsConnected && this.IsOwner;
350 switch (e.PropertyName)
352 case nameof(this.IsConnected):
353 case nameof(this.IsSensor):
354 this.IsConnectedAndSensor = this.IsConnected && this.IsSensor;
358 switch (e.PropertyName)
360 case nameof(this.IsConnected):
361 case nameof(this.IsActuator):
362 this.IsConnectedAndActuator = this.IsConnected && this.IsActuator;
366 switch (e.PropertyName)
368 case nameof(this.IsConnected):
369 case nameof(this.IsConcentrator):
370 this.IsConnectedAndNotConcentrator = this.IsConnected && !this.IsConcentrator;
376 public string? FriendlyName => this.thing?.FriendlyName ?? this.thing?.BareJid ??
string.Empty;
382 private bool isOwner;
388 [NotifyCanExecuteChangedFor(nameof(RemoveFromListCommand))]
389 [NotifyCanExecuteChangedFor(nameof(AddToListCommand))]
390 private bool inContactsAndNotOwner;
396 [NotifyCanExecuteChangedFor(nameof(DeleteRulesCommand))]
397 [NotifyCanExecuteChangedFor(nameof(DisownThingCommand))]
398 private bool isConnectedAndOwner;
404 [NotifyCanExecuteChangedFor(nameof(ReadSensorCommand))]
405 private bool isConnectedAndSensor;
411 [NotifyCanExecuteChangedFor(nameof(ControlActuatorCommand))]
412 private bool isConnectedAndActuator;
418 private bool isConnectedAndNotConcentrator;
424 private bool isThingOnline;
430 [NotifyCanExecuteChangedFor(nameof(ReadSensorCommand))]
431 private bool isSensor;
437 [NotifyCanExecuteChangedFor(nameof(ControlActuatorCommand))]
438 private bool isActuator;
444 private bool isConcentrator;
450 private bool isNodeInConcentrator;
456 private bool supportsSensorEvents;
461 private bool hasNotifications;
467 private bool hasPendingChatMessages;
473 private int nrPendingChatMessages;
481 private static Task Click(
object obj)
484 return ViewClaimThing.ViewClaimThingViewModel.LabelClicked(Tag.Name, Tag.Value, Tag.LocalizedValue);
485 else if (obj is
string s)
486 return ViewClaimThing.ViewClaimThingViewModel.LabelClicked(
string.Empty, s, s);
488 return Task.CompletedTask;
495 private async Task CopyQr(
object Item)
501 await Clipboard.SetTextAsync(this.
Link);
520 [RelayCommand(CanExecute = nameof(IsConnectedAndOwner))]
521 private async Task DeleteRules()
523 if (this.thing is
null)
538 TaskCompletionSource<bool> Result =
new();
540 ServiceRef.
XmppService.DeleteDeviceRules(this.thing.RegistryJid,
this.thing.BareJid,
this.thing.NodeId,
541 this.thing.SourceId,
this.thing.Partition, (sender, e) =>
544 Result.TrySetResult(
true);
545 else if (e.StanzaError is not
null)
546 Result.TrySetException(e.StanzaError);
548 Result.TrySetResult(
false);
550 return Task.CompletedTask;
554 if (!await Result.Task)
570 [RelayCommand(CanExecute = nameof(IsConnectedAndOwner))]
571 private async Task DisownThing()
573 if (this.thing is
null)
589 ServiceRef.
XmppService.Disown(this.thing.RegistryJid,
this.thing.BareJid,
this.thing.SourceId,
this.thing.Partition,
this.thing.NodeId));
596 if (!
string.IsNullOrEmpty(this.thing?.ObjectId))
601 this.thing.ObjectId =
null;
604 if (this.thing is not
null)
605 this.thing.ObjectId =
null;
607 this.InContacts =
false;
624 [RelayCommand(CanExecute = nameof(NotInContacts))]
625 private async Task AddToList()
627 if (this.thing is
null)
640 IdXml =
string.Empty;
643 StringBuilder Xml =
new();
645 IdXml = Xml.ToString();
649 MainThread.BeginInvokeOnMainThread(() => this.NotInContacts =
false);
651 await MainThread.InvokeOnMainThreadAsync(async () =>
653 if (!this.InContacts)
655 if (
string.IsNullOrEmpty(this.thing.ObjectId))
658 this.InContacts =
true;
661 await this.CalcThingIsOnline();
675 [RelayCommand(CanExecute = nameof(InContactsAndNotOwner))]
676 private async Task RemoveFromList()
678 if (this.thing is
null)
688 if (!
string.IsNullOrEmpty(this.thing.ObjectId))
691 this.thing.ObjectId =
null;
708 private bool CanReadSensor => !this.IsBusy && this.IsConnectedAndSensor;
713 [RelayCommand(CanExecute = nameof(CanReadSensor))]
714 private async Task ReadSensor()
716 await MainThread.InvokeOnMainThreadAsync(() => this.
SetIsBusy(
true));
718 if (this.thing is
null)
725 await MainThread.InvokeOnMainThreadAsync(() => this.
SetIsBusy(
false));
729 private bool CanControlActuator => !this.IsBusy && this.IsConnectedAndActuator;
734 [RelayCommand(CanExecute = nameof(CanControlActuator))]
735 private async Task ControlActuator()
737 await MainThread.InvokeOnMainThreadAsync(() => this.
SetIsBusy(
true));
739 if (this.thing is
null)
744 string? FullJid = this.GetFullJid();
745 if (
string.IsNullOrEmpty(FullJid))
750 if (
string.IsNullOrEmpty(this.thing.NodeId) &&
string.IsNullOrEmpty(
this.thing.SourceId) &&
string.IsNullOrEmpty(
this.thing.Partition))
751 ServiceRef.
XmppService.GetControlForm(FullJid, SelectedLanguage.Name,
this.ControlFormCallback,
null);
754 ThingReference ThingRef =
new(this.thing.NodeId, this.thing.SourceId, this.thing.Partition);
755 ServiceRef.
XmppService.GetControlForm(FullJid, SelectedLanguage.Name,
this.ControlFormCallback,
null, ThingRef);
768 MainThread.BeginInvokeOnMainThread(async () =>
777 MainThread.BeginInvokeOnMainThread(() => this.
SetIsBusy(
false));
778 return Task.CompletedTask;
782 private bool CanChat => !this.IsBusy && this.IsConnectedAndNotConcentrator;
787 [RelayCommand(CanExecute = nameof(CanChat))]
788 private async Task Chat()
790 await MainThread.InvokeOnMainThreadAsync(() => this.
SetIsBusy(
true));
792 if (this.thing is
null)
797 string LegalId = this.thing.LegalId;
798 string FriendlyName = this.thing.FriendlyName;
809 await MainThread.InvokeOnMainThreadAsync(() => this.
SetIsBusy(
false));
816 if (this.thing is
null)
819 MainThread.BeginInvokeOnMainThread(() =>
821 bool IsNode = this.IsNodeInConcentrator;
822 string Key = this.thing.ThingNotificationCategoryKey;
823 int NrChatMessagesRemoved = 0;
829 case NotificationEventType.Contacts:
833 if (Event.Category != this.thing.BareJid)
837 case NotificationEventType.Things:
838 if (Event.Category != Key)
850 if (Model.Event.ObjectId == Event.ObjectId)
852 this.Notifications.RemoveAt(i);
854 if (Event.Type == NotificationEventType.Contacts)
855 NrChatMessagesRemoved++;
864 this.NrPendingChatMessages -= NrChatMessagesRemoved;
865 this.HasNotifications = this.Notifications.Count > 0;
866 this.HasPendingChatMessages = this.NrPendingChatMessages > 0;
872 MainThread.BeginInvokeOnMainThread(async () =>
876 await this.CalcThingIsOnline();
878 switch (e.Event.Type)
881 if (this.IsNodeInConcentrator)
884 if (e.Event.Category !=
this.thing?.BareJid)
889 if (e.Event.Category !=
this.thing?.ThingNotificationCategoryKey)
898 await e.Event.GetCategoryIcon(),
899 await e.Event.GetDescription(),
902 this.HasNotifications =
true;
906 this.NrPendingChatMessages++;
907 this.HasPendingChatMessages =
true;
917 private Task Xmpp_OnRosterItemRemoved(
object? Sender,
RosterItem Item)
919 this.presences.Remove(
Item.BareJid);
920 MainThread.BeginInvokeOnMainThread(async () => await this.CalcThingIsOnline());
921 return Task.CompletedTask;
924 private Task Xmpp_OnRosterItemUpdated(
object? Sender,
RosterItem Item)
926 MainThread.BeginInvokeOnMainThread(async () => await this.CalcThingIsOnline());
927 return Task.CompletedTask;
930 private Task Xmpp_OnRosterItemAdded(
object? Sender,
RosterItem Item)
932 MainThread.BeginInvokeOnMainThread(async () => await this.CalcThingIsOnline());
933 return Task.CompletedTask;
936 #region ILinkableView
941 public override bool IsLinkable =>
true;
946 public override bool EncodeAppLinks =>
true;
951 public override string Link
955 StringBuilder sb =
new();
957 bool HasSourceId =
false;
958 bool HasPartition =
false;
959 bool HasNodeId =
false;
960 bool HasRegistry =
false;
962 sb.Append(
"iotdisco:");
964 if (this.thing is not
null)
966 if (this.thing.MetaData is not
null)
968 foreach (
Property P
in this.thing.MetaData)
971 switch (P.
Name.ToUpper(CultureInfo.InvariantCulture))
1000 sb.Append(Uri.EscapeDataString(P.
Name));
1002 sb.Append(Uri.EscapeDataString(P.
Value));
1011 sb.Append(Uri.EscapeDataString(
this.thing.BareJid));
1014 if (!HasSourceId && !
string.IsNullOrEmpty(this.thing.SourceId))
1017 sb.Append(Uri.EscapeDataString(
this.thing.SourceId));
1020 if (!HasPartition && !
string.IsNullOrEmpty(this.thing.Partition))
1023 sb.Append(Uri.EscapeDataString(
this.thing.Partition));
1026 if (!HasNodeId && !
string.IsNullOrEmpty(this.thing.NodeId))
1029 sb.Append(Uri.EscapeDataString(
this.thing.NodeId));
1032 if (!HasRegistry && !
string.IsNullOrEmpty(this.thing.RegistryJid))
1035 sb.Append(Uri.EscapeDataString(
this.thing.RegistryJid));
1039 return sb.ToString();
1046 public override Task<string> Title => Task.FromResult(this.thing?.FriendlyName ??
string.Empty);
1051 public override bool HasMedia =>
false;
1061 public override string? MediaContentType =>
null;
The Application class, representing an instance of the Neuro-Access app.
static LanguageInfo SelectedLanguage
Selected language.
static Task< bool > AuthenticateUser(AuthenticationPurpose Purpose, bool Force=false)
Authenticates the user using the configured authentication method.
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.
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 IStringLocalizer 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.
The view model to bind to when displaying the list of things.
static ? NotificationEvent[] GetNotificationEvents(ContactInfo Thing)
Gets available notification events related to a thing.
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.
ViewThingViewModel(ViewThingNavigationArgs? Args)
Creates an instance of the ViewThingViewModel class.
override async Task OnDispose()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
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 OnInitialize()
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.
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 DisplayException(Exception Exception, string? Title=null)
Displays an alert/message box to the user.
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.
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.