Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ChatViewModel.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using CommunityToolkit.Mvvm.Input;
3using EDaler;
4using EDaler.Uris;
5using Mopups.Services;
24using NeuroFeatures;
25using SkiaSharp;
26using System.ComponentModel;
27using System.Globalization;
28using System.Security.Cryptography;
29using System.Text;
30using System.Timers;
31using System.Xml;
32using Waher.Content;
41
43{
48 {
49 private readonly SortedDictionary<string, LinkedListNode<MessageRecord>> messagesByObjectId = [];
50 private readonly LinkedList<MessageRecord> messages = [];
51 private readonly LinkedList<MessageFrame> frames = [];
52 private readonly ChatPage page;
53 private readonly object synchObject = new();
54 private TaskCompletionSource<bool> waitUntilBound = new();
55 private IView? scrollTo;
56
57 private class MessageRecord(ChatMessage Message, LinkedListNode<MessageFrame> FrameNode)
58 {
59 public ChatMessage Message = Message;
60 public LinkedListNode<MessageFrame> FrameNode = FrameNode;
61
62 public DateTime Created => this.Message.Created;
63 public MessageType MessageType => this.Message.MessageType;
64 }
65
72 : base()
73 {
74 this.page = Page;
75 this.LegalId = Args?.LegalId ?? string.Empty;
76 this.BareJid = Args?.BareJid ?? string.Empty;
77 this.FriendlyName = Args?.FriendlyName ?? string.Empty;
78 this.UniqueId = Args?.UniqueId;
79
80 this.page.OnAfterAppearing += this.Page_OnAfterAppearing;
81
82 this.XmppUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.Xmpp));
83 this.IotIdUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.IotId));
84 this.IotScUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.IotSc));
85 this.NeuroFeatureUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.NeuroFeature));
86 this.IotDiscoUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.IotDisco));
87 this.EDalerUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.EDaler));
88 this.HyperlinkClicked = new Command(async Parameter => await ExecuteHyperlinkClicked(Parameter));
89 }
90
94 public Command XmppUriClicked { get; }
95
99 public Command IotIdUriClicked { get; }
100
104 public Command IotScUriClicked { get; }
105
109 public Command NeuroFeatureUriClicked { get; }
110
114 public Command IotDiscoUriClicked { get; }
115
119 public Command EDalerUriClicked { get; }
120
124 public Command HyperlinkClicked { get; }
125
126 private static async Task ExecuteHyperlinkClicked(object Parameter)
127 {
128 if (Parameter is not string Url)
129 return;
130
131 await App.OpenUrlAsync(Url);
132 }
133
134
136 protected override async Task OnInitialize()
137 {
138 await base.OnInitialize();
139
140 this.scrollTo = await this.LoadMessagesAsync(false);
141
142 this.waitUntilBound.TrySetResult(true);
143
145
146 }
147
149 protected override async Task OnDispose()
150 {
151 await base.OnDispose();
152
153 this.waitUntilBound = new TaskCompletionSource<bool>();
154 }
155
156 private Task Page_OnAfterAppearing(object Sender, EventArgs e)
157 {
158 if (this.scrollTo is Element)
159 {
160 // await Task.Delay(100); // TODO: Why is this necessary? ScrollToAsync does not scroll to end element without it...
161
162 // await this.page.ScrollView.ScrollToAsync(this.page.Bottom, ScrollToPosition.End, false);
163 this.scrollTo = null;
164 }
165
166 return Task.CompletedTask;
167 }
168
172 [ObservableProperty]
173 private string? uniqueId;
174
178 [ObservableProperty]
179 private string? bareJid;
180
184 [ObservableProperty]
185 private string? legalId;
186
190 [ObservableProperty]
191 private string? friendlyName;
192
196 [ObservableProperty]
197 [NotifyCanExecuteChangedFor(nameof(SendCommand))]
198 [NotifyCanExecuteChangedFor(nameof(CancelCommand))]
199 private string markdownInput = string.Empty;
200
202 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
203 {
204 base.OnPropertyChanged(e);
205
206 switch (e.PropertyName)
207 {
208 case nameof(this.MarkdownInput):
209 this.IsWriting = !string.IsNullOrEmpty(this.MarkdownInput);
210 break;
211
212 case nameof(this.MessageId):
213 this.IsWriting = !string.IsNullOrEmpty(this.MessageId);
214 break;
215
216 case nameof(this.IsWriting):
217 this.IsButtonExpanded = false;
218 break;
219
220 case nameof(this.IsRecordingAudio):
221 // TODO: Audio
222 //
223 //this.IsRecordingPaused = audioRecorder.Value.IsPaused;
224 this.IsWriting = this.IsRecordingAudio;
225
226 //if (audioRecorderTimer is null)
227 //{
228 // audioRecorderTimer = new System.Timers.Timer(100);
229 // audioRecorderTimer.Elapsed += this.OnAudioRecorderTimer;
230 // audioRecorderTimer.AutoReset = true;
231 //}
232 //
233 //audioRecorderTimer.Enabled = this.IsRecordingAudio;
234
235 this.OnPropertyChanged(nameof(RecordingTime));
236 this.CancelCommand.NotifyCanExecuteChanged();
237 break;
238
239 case nameof(this.IsConnected):
240 this.SendCommand.NotifyCanExecuteChanged();
241 this.CancelCommand.NotifyCanExecuteChanged();
242 this.RecordAudioCommand.NotifyCanExecuteChanged();
243 this.TakePhotoCommand.NotifyCanExecuteChanged();
244 this.EmbedFileCommand.NotifyCanExecuteChanged();
245 this.EmbedIdCommand.NotifyCanExecuteChanged();
246 this.EmbedContractCommand.NotifyCanExecuteChanged();
247 this.EmbedMoneyCommand.NotifyCanExecuteChanged();
248 this.EmbedTokenCommand.NotifyCanExecuteChanged();
249 this.EmbedThingCommand.NotifyCanExecuteChanged();
250 break;
251 }
252 }
253
257 [ObservableProperty]
258 private string? messageId;
259
263 [ObservableProperty]
264 [NotifyCanExecuteChangedFor(nameof(LoadMoreMessagesCommand))]
265 private bool existsMoreMessages;
266
270 [ObservableProperty]
271 [NotifyCanExecuteChangedFor(nameof(RecordAudioCommand))]
272 [NotifyCanExecuteChangedFor(nameof(TakePhotoCommand))]
273 [NotifyCanExecuteChangedFor(nameof(EmbedFileCommand))]
274 [NotifyCanExecuteChangedFor(nameof(EmbedIdCommand))]
275 [NotifyCanExecuteChangedFor(nameof(EmbedContractCommand))]
276 [NotifyCanExecuteChangedFor(nameof(EmbedMoneyCommand))]
277 [NotifyCanExecuteChangedFor(nameof(EmbedTokenCommand))]
278 [NotifyCanExecuteChangedFor(nameof(EmbedThingCommand))]
279 private bool isWriting;
280
284 [ObservableProperty]
285 [NotifyCanExecuteChangedFor(nameof(SendCommand))]
286 private bool isRecordingAudio;
287
291 [ObservableProperty]
292 private bool isRecordingPaused;
293
297 public static string RecordingTime
298 {
299 get
300 {
301 // TODO: Audio
302 //
303 //double Milliseconds = audioRecorder.Value.TotalAudioTimeout.TotalMilliseconds - audioRecorder.Value.RecordingTime.TotalMilliseconds;
304 //return (Milliseconds > 0) ? string.Format(CultureInfo.CurrentCulture, "{0:F0}s left", Math.Ceiling(Milliseconds / 1000.0)) : "TIMEOUT";
305
306 return string.Empty;
307 }
308 }
309
314 public async Task MessageAddedAsync(ChatMessage Message)
315 {
316 if(Message.ParsedXaml is null)
317 await Message.GenerateXaml(this);
318
319 MainThread.BeginInvokeOnMainThread(async () =>
320 {
321
322
323 IView? View = await this.MessageAddedMainThread(Message, true);
324
325 if (View is Element)
326 {
327 await Task.Delay(25);
328 double x = this.page.ScrollView.ScrollX;
329 double y = this.page.ScrollView.ContentSize.Height;
330 await this.page.ScrollView.ScrollToAsync(x, y, true);
331 }
332 });
333 }
334
335 private async Task<IView?> MessageAddedMainThread(ChatMessage Message, bool Historic)
336 {
337 this.HasMessages = true;
338
339 TaskCompletionSource<IView?> Result = new();
340
341 try
342 {
343 if(Message.ParsedXaml is null)
344 await Message.GenerateXaml(this); // Makes sure XAML is generated
345
346 lock (this.synchObject)
347 {
348 LinkedListNode<MessageRecord>? MessageNode = Historic ? this.messages.Last : this.messages.First;
349 LinkedListNode<MessageFrame>? FrameNode;
350 MessageFrame? Frame;
351 MessageRecord Rec;
352 IView? View;
353 // int i;
354 if (MessageNode is null)
355 {
356
357 Frame = MessageFrame.Create(Message);
358 View = Frame.AddLast(Message);
359 this.page.Messages.Add(Frame);
360
361 FrameNode = this.frames.AddLast(Frame);
362
363 Rec = new(Message, FrameNode);
364 MessageNode = this.messages.AddLast(Rec);
365 }
366 else if (Historic)
367 {
368 while (MessageNode is not null && Message.Created > MessageNode.Value.Created)
369 MessageNode = MessageNode.Next;
370 if (MessageNode is null)
371 {
372 FrameNode = this.frames.Last!;
373
374 if (FrameNode.Value.MessageType != Message.MessageType)
375 {
376 FrameNode = this.frames.AddLast(MessageFrame.Create(Message));
377 this.page.Messages.Add(FrameNode.Value);
378 }
379
380 View = FrameNode.Value.AddLast(Message);
381
382 Rec = new(Message, FrameNode);
383 MessageNode = this.messages.AddLast(Rec);
384 }
385 else
386 {
387 View = null;
388 // TODO
389 }
390 }
391 else
392 {
393 while (MessageNode is not null && Message.Created < MessageNode.Value.Created)
394 MessageNode = MessageNode.Previous;
395
396 if (MessageNode is null)
397 {
398 FrameNode = this.frames.First!;
399
400 if (FrameNode.Value.MessageType != Message.MessageType)
401 {
402 FrameNode = this.frames.AddFirst(MessageFrame.Create(Message));
403 this.page.Messages.Insert(0, FrameNode.Value);
404 }
405
406 View = FrameNode.Value.AddFirst(Message);
407
408 Rec = new(Message, FrameNode);
409 MessageNode = this.messages.AddFirst(Rec);
410 }
411 else
412 {
413 View = null;
414 // TODO
415 }
416 }
417
418 Result.TrySetResult(View);
419
420 {
421
422
423 //else if (MessageNode.Value.ParsedXaml is IView PrevMessageXaml &&
424 // (i = this.page.Messages.IndexOf(PrevMessageXaml)) >= 0)
425 //{
426 // if (MessageNode.Value.ObjectId == Message.ObjectId)
427 // {
428 // this.page.Messages.RemoveAt(i);
429 // this.page.Messages.Insert(i, MessageXaml);
430 //
431 // MessageNode.Value = Message;
432 // }
433 // else
434 // {
435 // this.page.Messages.Insert(i + 1, MessageXaml);
436 // MessageNode = this.messages.AddAfter(MessageNode, Message);
437 // }
438 //}
439 //else
440 //{
441 // MessageNode = this.messages.AddLast(Message);
442 // this.page.Messages.Children.Add(MessageXaml);
443 //}
444 }
445
446 this.messagesByObjectId[Message.ObjectId ?? string.Empty] = MessageNode;
447 }
448 }
449 catch (Exception ex)
450 {
451 Result.TrySetException(ex);
452 }
453
454 return await Result.Task;
455 }
456
461 public async Task MessageUpdatedAsync(ChatMessage Message)
462 {
463 try
464 {
465 await Message.GenerateXaml(this);
466 }
467 catch (Exception ex)
468 {
469 ServiceRef.LogService.LogException(ex);
470 return;
471 }
472
473 if (Message.ParsedXaml is not IView MessageXaml || string.IsNullOrEmpty(Message.ObjectId))
474 return;
475
476 MainThread.BeginInvokeOnMainThread(() =>
477 {
478 try
479 {
480 lock (this.synchObject)
481 {
482 int i;
483
484 if (this.messagesByObjectId.TryGetValue(Message.ObjectId, out LinkedListNode<MessageRecord>? Node) &&
485 Node.Value.Message.ParsedXaml is IView PrevMessageXaml &&
486 PrevMessageXaml.Parent is VerticalStackLayout Parent &&
487 (i = Parent.IndexOf(PrevMessageXaml)) >= 0)
488 {
489 Parent.RemoveAt(i);
490 Parent.Insert(i, MessageXaml);
491
492 Node.Value.Message = Message;
493
494 // TODO: Update XAML
495 }
496 }
497 }
498 catch (Exception ex)
499 {
500 ServiceRef.LogService.LogException(ex);
501 }
502 });
503 }
504
505 private async Task<IView?> LoadMessagesAsync(bool LoadMore = true)
506 {
507 IEnumerable<ChatMessage>? Messages = null;
509 DateTime LastTime;
510 ChatMessage[] A;
511
512 try
513 {
514 this.ExistsMoreMessages = false;
515
516 lock (this.synchObject)
517 {
518 LastTime = LoadMore && this.messages.First is not null ? this.messages.First.Value.Created : DateTime.MaxValue;
519 }
520
522 new FilterFieldEqualTo("RemoteBareJid", this.BareJid),
523 new FilterFieldLesserThan("Created", LastTime)), "-Created");
524
525 A = [.. Messages];
526 c -= A.Length;
527
528 if (!LoadMore)
529 Array.Reverse(A);
530 }
531 catch (Exception ex)
532 {
533 ServiceRef.LogService.LogException(ex);
534 this.ExistsMoreMessages = false;
535 return null;
536 }
537
538 TaskCompletionSource<IView?> Result = new();
539
540 MainThread.BeginInvokeOnMainThread(async () =>
541 {
542 try
543 {
544 IView? Last = null;
545
546 foreach (ChatMessage Message in A)
547 Last = await this.MessageAddedMainThread(Message, true);
548
549 this.ExistsMoreMessages = c <= 0;
550
551 Result.TrySetResult(Last);
552 }
553 catch (Exception ex)
554 {
555 Result.TrySetException(ex);
556 }
557 });
558
559 return await Result.Task;
560 }
561
562 [ObservableProperty]
563 private bool hasMessages = false;
564
568 [ObservableProperty]
569 private bool isButtonExpanded;
570
574 [RelayCommand]
575 private void ExpandButtons()
576 {
577 this.IsButtonExpanded = !this.IsButtonExpanded;
578 }
579
583 [RelayCommand(CanExecute = nameof(CanExecuteLoadMoreMessages))]
584 private Task<IView?> LoadMoreMessages()
585 {
586 return this.LoadMessagesAsync(true);
587 }
588
589 private bool CanExecuteLoadMoreMessages()
590 {
591 return this.ExistsMoreMessages && this.page.Messages.Count > 0;
592 }
593
594 private bool CanExecuteSendMessage()
595 {
596 return this.IsConnected && (!string.IsNullOrEmpty(this.MarkdownInput) || this.IsRecordingAudio);
597 }
598
602 [RelayCommand(CanExecute = nameof(CanExecuteSendMessage))]
603 private async Task Send()
604 {
605 if (this.IsRecordingAudio)
606 {
607 // TODO: Audio
608 //
609 // try
610 // {
611 // await audioRecorder.Value.StopRecording();
612 // string audioPath = await this.audioRecorderTask!;
613 //
614 // if (audioPath is not null)
615 // await this.EmbedMedia(audioPath, true);
616 // }
617 // catch (Exception ex)
618 // {
619 // ServiceRef.LogService.LogException(ex);
620 // }
621 // finally
622 // {
623 // this.IsRecordingAudio = false;
624 // }
625 }
626 else
627 {
628 await this.ExecuteSendMessage(this.MessageId, this.MarkdownInput);
629 await this.Cancel();
630 }
631 }
632
633 private Task ExecuteSendMessage(string? ReplaceObjectId, string MarkdownInput)
634 {
635 return ExecuteSendMessage(ReplaceObjectId, MarkdownInput, this.BareJid!, this);
636 }
637
644 public static Task ExecuteSendMessage(string? ReplaceObjectId, string MarkdownInput, string BareJid)
645 {
646 return ExecuteSendMessage(ReplaceObjectId, MarkdownInput, BareJid, null);
647 }
648
656 public static async Task ExecuteSendMessage(string? ReplaceObjectId, string MarkdownInput, string BareJid, ChatViewModel? ChatViewModel)
657 {
658 try
659 {
660 if (string.IsNullOrEmpty(MarkdownInput))
661 return;
662
663 MarkdownSettings Settings = new()
664 {
665 AllowScriptTag = false,
666 EmbedEmojis = false, // TODO: Emojis
667 AudioAutoplay = false,
668 AudioControls = false,
669 ParseMetaData = false,
670 VideoAutoplay = false,
671 VideoControls = false
672 };
673
674 MarkdownDocument Doc = await MarkdownDocument.CreateAsync(MarkdownInput, Settings);
675
676 ChatMessage Message = new()
677 {
678 Created = DateTime.UtcNow,
679 RemoteBareJid = BareJid,
680 RemoteObjectId = string.Empty,
681
683 Html = HtmlDocument.GetBody(await Doc.GenerateHTML()),
684 PlainText = (await Doc.GeneratePlainText()).Trim(),
685 Markdown = MarkdownInput
686 };
687
688 StringBuilder Xml = new();
689
690 Xml.Append("<content xmlns=\"urn:xmpp:content\" type=\"text/markdown\">");
691 Xml.Append(XML.Encode(MarkdownInput));
692 Xml.Append("</content><html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>");
693
694 HtmlDocument HtmlDoc = new("<root>" + Message.Html + "</root>");
695
696 foreach (HtmlNode N in (HtmlDoc.Body ?? HtmlDoc.Root).Children)
697 N.Export(Xml);
698
699 Xml.Append("</body></html>");
700
701 if (!string.IsNullOrEmpty(ReplaceObjectId))
702 {
703 Xml.Append("<replace id='");
704 Xml.Append(ReplaceObjectId);
705 Xml.Append("' xmlns='urn:xmpp:message-correct:0'/>");
706 }
707
708 if (string.IsNullOrEmpty(ReplaceObjectId))
709 {
710 await Database.Insert(Message);
711
712 if (ChatViewModel is not null)
713 await ChatViewModel.MessageAddedAsync(Message);
714 }
715 else
716 {
717 ChatMessage Old = await Database.TryLoadObject<ChatMessage>(ReplaceObjectId);
718
719 if (Old is null)
720 {
721 ReplaceObjectId = null;
722 await Database.Insert(Message);
723
724 if (ChatViewModel is not null)
725 await ChatViewModel.MessageAddedAsync(Message);
726 }
727 else
728 {
729 Old.Updated = Message.Created;
730 Old.Html = Message.Html;
731 Old.PlainText = Message.PlainText;
732 Old.Markdown = Message.Markdown;
733
734 await Database.Update(Old);
735
736 Message = Old;
737
738 if (ChatViewModel is not null)
739 await ChatViewModel.MessageUpdatedAsync(Message);
740 }
741 }
742 ServiceRef.XmppService.SendMessage(QoSLevel.Unacknowledged, Waher.Networking.XMPP.MessageType.Chat, Message.ObjectId ?? string.Empty,
743 BareJid, Xml.ToString(), Message.PlainText, string.Empty, string.Empty, string.Empty, string.Empty, null, null);
744 }
745 catch (Exception ex)
746 {
748 }
749 }
750
754 [RelayCommand(CanExecute = nameof(CanExecutePauseResume))]
755 private Task PauseResume()
756 {
757 // TODO: Audio
758 //
759 // if (audioRecorder.Value.IsPaused)
760 // await audioRecorder.Value.Resume();
761 // else
762 // await audioRecorder.Value.Pause();
763 //
764 // this.IsRecordingPaused = audioRecorder.Value.IsPaused;
765
766 return Task.CompletedTask;
767 }
768
769 private static bool CanExecutePauseResume()
770 {
771 // TODO: Audio
772 //
773 // return this.IsRecordingAudio && audioRecorder.Value.IsRecording;
774 return false;
775 }
776
777 private bool CanExecuteCancelMessage()
778 {
779 return this.IsConnected && (!string.IsNullOrEmpty(this.MarkdownInput) || this.IsRecordingAudio);
780 }
781
785 [RelayCommand(CanExecute = nameof(CanExecuteCancelMessage))]
786 private Task Cancel()
787 {
788 if (this.IsRecordingAudio)
789 {
790 // TODO: Audio
791 //
792 // try
793 // {
794 // return audioRecorder.Value.StopRecording();
795 // }
796 // catch (Exception ex)
797 // {
798 // ServiceRef.LogService.LogException(ex);
799 // }
800 // finally
801 // {
802 // this.IsRecordingAudio = false;
803 // }
804 }
805 else
806 {
807 this.MarkdownInput = string.Empty;
808 this.MessageId = string.Empty;
809 }
810
811 return Task.CompletedTask;
812 }
813
814 // TODO: Audio
815 //
816 // private static System.Timers.Timer audioRecorderTimer;
817 //
818 // private static readonly Lazy<AudioRecorderService> audioRecorder = new(() =>
819 // {
820 // return new AudioRecorderService()
821 // {
822 // StopRecordingOnSilence = false,
823 // StopRecordingAfterTimeout = true,
824 // TotalAudioTimeout = TimeSpan.FromSeconds(60)
825 // };
826 // }, LazyThreadSafetyMode.PublicationOnly);
827 //
828 //private readonly Task<string>? audioRecorderTask = null;
829
830 private bool CanExecuteRecordAudio()
831 {
832 return this.IsConnected && !this.IsWriting && ServiceRef.XmppService.FileUploadIsSupported;
833 }
834
835 private void OnAudioRecorderTimer(object? source, ElapsedEventArgs e)
836 {
837 this.OnPropertyChanged(nameof(RecordingTime));
838
839 // TODO: Audio
840 //
841 // this.IsRecordingPaused = audioRecorder.Value.IsPaused;
842 }
843
844 [RelayCommand(CanExecute = nameof(CanExecuteRecordAudio))]
845 private async Task RecordAudio()
846 {
847 if (!ServiceRef.XmppService.FileUploadIsSupported)
848 {
849 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.TakePhoto)],
850 ServiceRef.Localizer[nameof(AppResources.ServerDoesNotSupportFileUpload)]);
851 return;
852 }
853
854 try
855 {
856 PermissionStatus Status = await Permissions.RequestAsync<Permissions.Microphone>();
857
858 if (Status == PermissionStatus.Granted)
859 {
860 // TODO: Audio
861 //
862 // this.audioRecorderTask = await audioRecorder.Value.StartRecording();
863 // this.IsRecordingAudio = true;
864 }
865 }
866 catch (Exception ex)
867 {
868 ServiceRef.LogService.LogException(ex);
869 }
870 }
871
872 private bool CanExecuteTakePhoto()
873 {
874 return this.IsConnected && !this.IsWriting && ServiceRef.XmppService.FileUploadIsSupported;
875 }
876
880 [RelayCommand(CanExecute = nameof(CanExecuteTakePhoto))]
881 private async Task TakePhoto()
882 {
883 if (!ServiceRef.XmppService.FileUploadIsSupported)
884 {
885 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.TakePhoto)],
886 ServiceRef.Localizer[nameof(AppResources.ServerDoesNotSupportFileUpload)]);
887 return;
888 }
889
890 if (DeviceInfo.Platform == DevicePlatform.iOS)
891 {
892 FileResult? capturedPhoto;
893
894 try
895 {
896 capturedPhoto = await MediaPicker.Default.CapturePhotoAsync(new MediaPickerOptions()
897 {
898 Title = ServiceRef.Localizer[nameof(AppResources.TakePhotoToShare)]
899 });
900 }
901 catch (Exception ex)
902 {
903 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.TakePhoto)],
904 ServiceRef.Localizer[nameof(AppResources.TakingAPhotoIsNotSupported)] + ": " + ex.Message);
905 return;
906 }
907
908 if (capturedPhoto is not null)
909 {
910 try
911 {
912 await this.EmbedMedia(capturedPhoto.FullPath, true);
913 }
914 catch (Exception ex)
915 {
917 }
918 }
919 }
920 else
921 {
922 FileResult? capturedPhoto;
923
924 try
925 {
926 capturedPhoto = await MediaPicker.CapturePhotoAsync();
927 if (capturedPhoto is null)
928 return;
929 }
930 catch (Exception ex)
931 {
932 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.TakePhoto)],
933 ServiceRef.Localizer[nameof(AppResources.TakingAPhotoIsNotSupported)] + ": " + ex.Message);
934 return;
935 }
936
937 if (capturedPhoto is not null)
938 {
939 try
940 {
941 await this.EmbedMedia(capturedPhoto.FullPath, true);
942 }
943 catch (Exception ex)
944 {
946 }
947 }
948 }
949 }
950
951 private async Task EmbedMedia(string FilePath, bool DeleteFile)
952 {
953 try
954 {
955 byte[] Bin = File.ReadAllBytes(FilePath);
956 if (!InternetContent.TryGetContentType(Path.GetExtension(FilePath), out string ContentType))
957 ContentType = "application/octet-stream";
958
959 if (Bin.Length > ServiceRef.TagProfile.HttpFileUploadMaxSize)
960 {
961 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
962 ServiceRef.Localizer[nameof(AppResources.PhotoIsTooLarge)]);
963 return;
964 }
965
966 // Taking or picking photos switches to another app, so ID app has to reconnect again after.
967 if (!await ServiceRef.XmppService.WaitForConnectedState(Constants.Timeouts.XmppConnect))
968 {
969 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
970 ServiceRef.Localizer[nameof(AppResources.UnableToConnectTo), ServiceRef.TagProfile.Domain ?? string.Empty]);
971 return;
972 }
973
974 string FileName = Path.GetFileName(FilePath);
975
976 // Encrypting image
977
978 using RandomNumberGenerator Rnd = RandomNumberGenerator.Create();
979 byte[] Key = new byte[16];
980 byte[] IV = new byte[16];
981
982 Rnd.GetBytes(Key);
983 Rnd.GetBytes(IV);
984
985 Aes Aes = Aes.Create();
986 Aes.BlockSize = 128;
987 Aes.KeySize = 256;
988 Aes.Mode = CipherMode.CBC;
989 Aes.Padding = PaddingMode.PKCS7;
990
991 using ICryptoTransform Transform = Aes.CreateEncryptor(Key, IV);
992 Bin = Transform.TransformFinalBlock(Bin, 0, Bin.Length);
993
994 // Preparing File upload service that content uploaded next is encrypted, and can be stored in encrypted storage.
995
996 StringBuilder Xml = new();
997
998 Xml.Append("<prepare xmlns='http://waher.se/Schema/EncryptedStorage.xsd' filename='");
999 Xml.Append(XML.Encode(FileName));
1000 Xml.Append("' size='");
1001 Xml.Append(Bin.Length.ToString(CultureInfo.InvariantCulture));
1002 Xml.Append("' content-type='application/octet-stream'/>");
1003
1004 await ServiceRef.XmppService.IqSetAsync(ServiceRef.TagProfile.HttpFileUploadJid!, Xml.ToString());
1005 // Empty response expected. Errors cause an exception to be raised.
1006
1007 // Requesting upload slot
1008
1009 HttpFileUploadEventArgs Slot = await ServiceRef.XmppService.RequestUploadSlotAsync(
1010 FileName, "application/octet-stream", Bin.Length);
1011
1012 if (!Slot.Ok)
1013 throw Slot.StanzaError ?? new Exception(Slot.ErrorText);
1014
1015 // Uploading encrypted image
1016
1017 await Slot.PUT(Bin, "application/octet-stream", (int)Constants.Timeouts.UploadFile.TotalMilliseconds);
1018
1019 // Generating Markdown message to send to recipient
1020
1021 StringBuilder Markdown = new();
1022
1023 Markdown.Append("![");
1024 Markdown.Append(MarkdownDocument.Encode(FileName));
1025 Markdown.Append("](");
1026 Markdown.Append(Constants.UriSchemes.Aes256);
1027 Markdown.Append(':');
1028 Markdown.Append(Convert.ToBase64String(Key));
1029 Markdown.Append(':');
1030 Markdown.Append(Convert.ToBase64String(IV));
1031 Markdown.Append(':');
1032 Markdown.Append(ContentType);
1033 Markdown.Append(':');
1034 Markdown.Append(Slot.GetUrl);
1035
1036 SKImageInfo ImageInfo = SKBitmap.DecodeBounds(Bin);
1037 if (!ImageInfo.IsEmpty)
1038 {
1039 Markdown.Append(' ');
1040 Markdown.Append(ImageInfo.Width.ToString(CultureInfo.InvariantCulture));
1041 Markdown.Append(' ');
1042 Markdown.Append(ImageInfo.Height.ToString(CultureInfo.InvariantCulture));
1043 }
1044
1045 Markdown.Append(')');
1046
1047 await this.ExecuteSendMessage(string.Empty, Markdown.ToString());
1048
1049 // TODO: End-to-End encryption, or using Elliptic Curves of recipient together with sender to deduce shared secret.
1050
1051 if (DeleteFile)
1052 File.Delete(FilePath);
1053 }
1054 catch (Exception ex)
1055 {
1056 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)], ex.Message);
1057 ServiceRef.LogService.LogException(ex);
1058 return;
1059 }
1060 }
1061
1062 private bool CanExecuteEmbedFile()
1063 {
1064 return this.IsConnected && !this.IsWriting && ServiceRef.XmppService.FileUploadIsSupported;
1065 }
1066
1070 [RelayCommand(CanExecute = nameof(CanExecuteEmbedFile))]
1071 private async Task EmbedFile()
1072 {
1073 if (!ServiceRef.XmppService.FileUploadIsSupported)
1074 {
1075 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.PickPhoto)], ServiceRef.Localizer[nameof(AppResources.SelectingAPhotoIsNotSupported)]);
1076 return;
1077 }
1078
1079 FileResult? pickedPhoto = await MediaPicker.PickPhotoAsync();
1080
1081 if (pickedPhoto is not null)
1082 await this.EmbedMedia(pickedPhoto.FullPath, false);
1083 }
1084
1085 private bool CanExecuteEmbedId()
1086 {
1087 return this.IsConnected && !this.IsWriting;
1088 }
1089
1093 [RelayCommand(CanExecute = nameof(CanExecuteEmbedId))]
1094 private async Task EmbedId()
1095 {
1096 TaskCompletionSource<ContactInfoModel?> SelectedContact = new();
1097 ContactListNavigationArgs Args = new(ServiceRef.Localizer[nameof(AppResources.SelectContactToPay)], SelectedContact)
1098 {
1099 CanScanQrCode = true
1100 };
1101
1102 await ServiceRef.UiService.GoToAsync(nameof(MyContactsPage), Args, BackMethod.Pop);
1103
1104 ContactInfoModel? Contact = await SelectedContact.Task;
1105 if (Contact is null)
1106 return;
1107
1108 await this.waitUntilBound.Task; // Wait until view is bound again.
1109
1110 if (Contact.LegalIdentity is not null)
1111 {
1112 StringBuilder Markdown = new();
1113
1114 Markdown.Append("```");
1115 Markdown.AppendLine(Constants.UriSchemes.IotId);
1116
1117 Contact.LegalIdentity.Serialize(Markdown, true, true, true, true, true, true, true);
1118
1119 Markdown.AppendLine();
1120 Markdown.AppendLine("```");
1121
1122 await this.ExecuteSendMessage(string.Empty, Markdown.ToString());
1123 return;
1124 }
1125
1126 if (!string.IsNullOrEmpty(Contact.LegalId))
1127 {
1128 await this.ExecuteSendMessage(string.Empty, "![" + MarkdownDocument.Encode(Contact.FriendlyName) + "](" + ContractsClient.LegalIdUriString(Contact.LegalId) + ")");
1129 return;
1130 }
1131
1132 if (!string.IsNullOrEmpty(Contact.BareJid))
1133 {
1134 await this.ExecuteSendMessage(string.Empty, "![" + MarkdownDocument.Encode(Contact.FriendlyName) + "](xmpp:" + Contact.BareJid + "?subscribe)");
1135 return;
1136 }
1137 }
1138
1139 private bool CanExecuteEmbedContract()
1140 {
1141 return this.IsConnected && !this.IsWriting;
1142 }
1143
1147 [RelayCommand(CanExecute = nameof(CanExecuteEmbedContract))]
1148 private async Task EmbedContract()
1149 {
1150 TaskCompletionSource<Contract?> SelectedContract = new();
1151 MyContractsNavigationArgs Args = new(ContractsListMode.Contracts, SelectedContract);
1152
1153 await ServiceRef.UiService.GoToAsync(nameof(MyContractsPage), Args, BackMethod.Pop);
1154
1155 Contract? Contract = await SelectedContract.Task;
1156 if (Contract is null)
1157 return;
1158
1159 await this.waitUntilBound.Task; // Wait until view is bound again.
1160
1161 StringBuilder Markdown = new();
1162
1163 Markdown.Append("```");
1164 Markdown.AppendLine(Constants.UriSchemes.IotSc);
1165
1166 Contract.Serialize(Markdown, true, true, true, true, true, true, true);
1167
1168 Markdown.AppendLine();
1169 Markdown.AppendLine("```");
1170
1171 await this.ExecuteSendMessage(string.Empty, Markdown.ToString());
1172 }
1173
1174 private bool CanExecuteEmbedMoney()
1175 {
1176 return this.IsConnected && !this.IsWriting;
1177 }
1178
1182 [RelayCommand(CanExecute = nameof(CanExecuteEmbedMoney))]
1183 private async Task EmbedMoney()
1184 {
1185 StringBuilder sb = new();
1186
1187 sb.Append("edaler:");
1188
1189 if (!string.IsNullOrEmpty(this.LegalId))
1190 {
1191 sb.Append("ti=");
1192 sb.Append(this.LegalId);
1193 }
1194 else if (!string.IsNullOrEmpty(this.BareJid))
1195 {
1196 sb.Append("t=");
1197 sb.Append(this.BareJid);
1198 }
1199 else
1200 return;
1201
1202 Balance CurrentBalance = await ServiceRef.XmppService.GetEDalerBalance();
1203
1204 sb.Append(";cu=");
1205 sb.Append(CurrentBalance.Currency);
1206
1207 if (!EDalerUri.TryParse(sb.ToString(), out EDalerUri Parsed))
1208 return;
1209
1210 TaskCompletionSource<string?> UriToSend = new();
1211 EDalerUriNavigationArgs Args = new(Parsed, this.FriendlyName ?? string.Empty, UriToSend);
1212
1213 await ServiceRef.UiService.GoToAsync(nameof(SendPaymentPage), Args, BackMethod.Pop);
1214
1215 string? Uri = await UriToSend.Task;
1216 if (string.IsNullOrEmpty(Uri) || !EDalerUri.TryParse(Uri, out Parsed))
1217 return;
1218
1219 await this.waitUntilBound.Task; // Wait until view is bound again.
1220
1221 sb.Clear();
1222 sb.Append(MoneyToString.ToString(Parsed.Amount));
1223
1224 if (Parsed.AmountExtra.HasValue)
1225 {
1226 sb.Append(" (+");
1227 sb.Append(MoneyToString.ToString(Parsed.AmountExtra.Value));
1228 sb.Append(')');
1229 }
1230
1231 sb.Append(' ');
1232 sb.Append(Parsed.Currency);
1233
1234 await this.ExecuteSendMessage(string.Empty, "![" + sb.ToString() + "](" + Uri + ")");
1235 }
1236
1237 private bool CanExecuteEmbedToken()
1238 {
1239 return this.IsConnected && !this.IsWriting;
1240 }
1241
1245 [RelayCommand(CanExecute = nameof(CanExecuteEmbedToken))]
1246 private async Task EmbedToken()
1247 {
1248 MyTokensNavigationArgs Args = new();
1249
1250 await ServiceRef.UiService.GoToAsync(nameof(MyTokensPage), Args, BackMethod.Pop);
1251
1252 TokenItem? Selected = await Args.TokenItemProvider.Task;
1253
1254 if (Selected is null)
1255 return;
1256
1257 StringBuilder Markdown = new();
1258
1259 Markdown.AppendLine("```nfeat");
1260
1261 Selected.Token.Serialize(Markdown);
1262
1263 Markdown.AppendLine();
1264 Markdown.AppendLine("```");
1265
1266 await this.ExecuteSendMessage(string.Empty, Markdown.ToString());
1267 }
1268
1269 private bool CanExecuteEmbedThing()
1270 {
1271 return this.IsConnected && !this.IsWriting;
1272 }
1273
1277 [RelayCommand(CanExecute = nameof(CanExecuteEmbedThing))]
1278 private async Task EmbedThing()
1279 {
1280 TaskCompletionSource<ContactInfoModel?> ThingToShare = new();
1281 MyThingsNavigationArgs Args = new(ThingToShare);
1282
1283 await ServiceRef.UiService.GoToAsync(nameof(MyThingsPage), Args, BackMethod.Pop);
1284
1285 ContactInfoModel? Thing = await ThingToShare.Task;
1286 if (Thing is null)
1287 return;
1288
1289 await this.waitUntilBound.Task; // Wait until view is bound again.
1290
1291 StringBuilder sb = new();
1292
1293 sb.Append("![");
1294 sb.Append(MarkdownDocument.Encode(Thing.FriendlyName));
1295 sb.Append("](iotdisco:JID=");
1296 sb.Append(Thing.BareJid);
1297
1298 if (!string.IsNullOrEmpty(Thing.SourceId))
1299 {
1300 sb.Append(";SID=");
1301 sb.Append(Thing.SourceId);
1302 }
1303
1304 if (!string.IsNullOrEmpty(Thing.Partition))
1305 {
1306 sb.Append(";PT=");
1307 sb.Append(Thing.Partition);
1308 }
1309
1310 if (!string.IsNullOrEmpty(Thing.NodeId))
1311 {
1312 sb.Append(";NID=");
1313 sb.Append(Thing.NodeId);
1314 }
1315
1316 sb.Append(')');
1317
1318 await this.ExecuteSendMessage(string.Empty, sb.ToString());
1319 }
1320
1324 [RelayCommand]
1325 private Task MessageSelected(object Parameter)
1326 {
1327 if (Parameter is ChatMessage Message)
1328 {
1329 // TODO: Audio
1330 //
1331 // if (Message.ParsedXaml is View View)
1332 // {
1333 // AudioPlayerControl AudioPlayer = View.Descendants().OfType<AudioPlayerControl>().FirstOrDefault();
1334 // if (AudioPlayer is not null)
1335 // {
1336 // return Task.CompletedTask;
1337 // }
1338 // }
1339
1340 switch (Message.MessageType)
1341 {
1342
1343 case MessageType.Sent:
1344 this.MessageId = Message.ObjectId;
1345 this.MarkdownInput = Message.Markdown;
1346 break;
1347
1348
1349 case MessageType.Received:
1350 string s = Message.Markdown;
1351 if (string.IsNullOrEmpty(s))
1352 s = MarkdownDocument.Encode(Message.PlainText);
1353
1354 string[] Rows = s.Replace("\r\n", "\n").Replace("\r", "\n").Split('\n');
1355
1356 StringBuilder Quote = new();
1357
1358 foreach (string Row in Rows)
1359 {
1360 Quote.Append("> ");
1361 Quote.AppendLine(Row);
1362 }
1363
1364 Quote.AppendLine();
1365
1366 this.MessageId = string.Empty;
1367 this.MarkdownInput = Quote.ToString();
1368 break;
1369 }
1370 }
1371
1372 return Task.CompletedTask;
1373 }
1374
1380 public async Task ExecuteUriClicked(string Uri, UriScheme Scheme)
1381 {
1382 try
1383 {
1384 if (Scheme == UriScheme.Xmpp)
1385 await ProcessXmppUri(Uri);
1386 else
1387 {
1388 int i = Uri.IndexOf(':');
1389 if (i < 0)
1390 return;
1391
1392 string s = Uri[(i + 1)..].Trim();
1393 if (s.StartsWith('<') && s.EndsWith('>')) // XML
1394 {
1395 XmlDocument Doc = new()
1396 {
1397 PreserveWhitespace = true
1398 };
1399 Doc.LoadXml(s);
1400
1401 switch (Scheme)
1402 {
1403 case UriScheme.IotId:
1404 LegalIdentity Id = LegalIdentity.Parse(Doc.DocumentElement);
1405 ViewIdentityNavigationArgs ViewIdentityArgs = new(Id);
1406
1407 await ServiceRef.UiService.GoToAsync(nameof(ViewIdentityPage), ViewIdentityArgs, BackMethod.Pop);
1408 break;
1409
1410 case UriScheme.IotSc:
1411 ParsedContract ParsedContract = await Contract.Parse(Doc.DocumentElement, ServiceRef.XmppService.ContractsClient, true);
1412 ViewContractNavigationArgs ViewContractArgs = new(ParsedContract.Contract, false);
1413
1414 await ServiceRef.UiService.GoToAsync(nameof(ViewContractPage), ViewContractArgs, BackMethod.Pop);
1415 break;
1416
1417 case UriScheme.NeuroFeature:
1418 if (!Token.TryParse(Doc.DocumentElement, out Token ParsedToken))
1419 throw new Exception(ServiceRef.Localizer[nameof(AppResources.InvalidNeuroFeatureToken)]);
1420
1422 Events = [];
1423
1424 TokenDetailsNavigationArgs Args = new(new TokenItem(ParsedToken, Events));
1425
1426 await ServiceRef.UiService.GoToAsync(nameof(TokenDetailsPage), Args, BackMethod.Pop);
1427 break;
1428
1429 default:
1430 return;
1431 }
1432 }
1433 else
1434 await QrCode.OpenUrl(Uri);
1435 }
1436 }
1437 catch (Exception ex)
1438 {
1440 }
1441 }
1442
1448 public static async Task<bool> ProcessXmppUri(string Uri)
1449 {
1450 int i = Uri.IndexOf(':');
1451 if (i < 0)
1452 return false;
1453
1454 string Jid = Uri[(i + 1)..].TrimStart();
1455 string Command;
1456
1457 i = Jid.IndexOf('?');
1458 if (i < 0)
1459 Command = "subscribe";
1460 else
1461 {
1462 Command = Jid[(i + 1)..].TrimStart();
1463 Jid = Jid[..i].TrimEnd();
1464 }
1465
1466 Jid = System.Web.HttpUtility.UrlDecode(Jid);
1467 Jid = XmppClient.GetBareJID(Jid);
1468
1469 switch (Command.ToLower(CultureInfo.InvariantCulture))
1470 {
1471 case "subscribe":
1474
1475 await MopupService.Instance.PushAsync(SubscribeToPopup);
1476 bool? SubscribeTo = await SubscribeToViewModel.Result;
1477
1478 if (SubscribeTo.HasValue && SubscribeTo.Value)
1479 {
1480 string IdXml;
1481
1482 if (ServiceRef.TagProfile.LegalIdentity is null)
1483 IdXml = string.Empty;
1484 else
1485 {
1486 StringBuilder Xml = new();
1487 ServiceRef.TagProfile.LegalIdentity.Serialize(Xml, true, true, true, true, true, true, true);
1488 IdXml = Xml.ToString();
1489 }
1490
1491 ServiceRef.XmppService.RequestPresenceSubscription(Jid, IdXml);
1492 }
1493 return true;
1494
1495 case "unsubscribe":
1496 // TODO
1497 return false;
1498
1499 case "remove":
1500 ServiceRef.XmppService.GetRosterItem(Jid);
1501 // TODO
1502 return false;
1503
1504 default:
1505 return false;
1506 }
1507 }
1508
1509 #region ILinkableView
1510
1514 public bool IsLinkable => true;
1515
1519 public bool EncodeAppLinks => true;
1520
1524 public string Link => Constants.UriSchemes.Xmpp + ":" + this.BareJid;
1525
1529 public Task<string> Title => Task.FromResult<string>(this.FriendlyName ?? string.Empty);
1530
1534 public bool HasMedia => false;
1535
1539 public byte[]? Media => null;
1540
1544 public string? MediaContentType => null;
1545
1546 #endregion
1547
1548 }
1549}
Contains information about a balance.
Definition: Balance.cs:11
CaseInsensitiveString Currency
Currency of amount.
Definition: Balance.cs:54
Abstract base class for eDaler URIs
Definition: EDalerUri.cs:14
static bool TryParse(string Uri, out EDalerUri Result)
Tries to parse an eDaler URI
Definition: EDalerUri.cs:192
The Application class, representing an instance of the Neuro-Access app.
Definition: App.xaml.cs:69
static Task< bool > OpenUrlAsync(string Url)
Opens an URL in the application.
Definition: App.xaml.cs:919
const int MessageBatchSize
Number of messages to load in a single batch.
Definition: Constants.cs:769
static readonly TimeSpan XmppConnect
XMPP Connect timeout
Definition: Constants.cs:571
static readonly TimeSpan UploadFile
Upload file timeout
Definition: Constants.cs:581
const string IotSc
The IoT Smart Contract URI Scheme (iotsc)
Definition: Constants.cs:109
const string Aes256
AES-256-encrypted data.
Definition: Constants.cs:139
const string IotId
The IoT ID URI Scheme (iotid)
Definition: Constants.cs:99
A set of never changing property constants and helpful values.
Definition: Constants.cs:7
Base class that references services in the app.
Definition: ServiceRef.cs:31
static ILogService LogService
Log service.
Definition: ServiceRef.cs:91
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:55
static INotificationService NotificationService
Service for managing notifications for the user.
Definition: ServiceRef.cs:211
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:79
static IStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:235
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:67
string? UniqueId
An unique view identifier used to search the args of similar view types.
Helper class to perform scanning of QR Codes by displaying the UI and handling async results.
Definition: QrCode.cs:17
static Task< bool > OpenUrl(string Url)
Scans a QR Code, and depending on the actual result, takes different actions. This typically means na...
Definition: QrCode.cs:74
static string ToString(decimal Money)
Converts a monetary value to a string, removing any round-off errors.
async Task GenerateXaml(IChatView View)
Parses the XAML in the message.
Definition: ChatMessage.cs:163
DateTime Created
When message was created
Definition: ChatMessage.cs:90
Holds navigation parameters specific to views displaying a list of contacts.
A page that displays a list of the current user's contacts.
The view model to bind to when displaying the list of contacts.
byte?[] Media
Encoded media, if available.
Command XmppUriClicked
Command executed when a multi-media-link with the xmpp URI scheme is clicked.
static string RecordingTime
If the audio recording is paused
Command HyperlinkClicked
Command executed when a hyperlink in rendered markdown has been clicked.
bool IsLinkable
If the current view is linkable.
bool EncodeAppLinks
If App links should be encoded with the link.
Command EDalerUriClicked
Command executed when a multi-media-link with the edaler URI scheme is clicked.
bool HasMedia
If linkable view has media associated with link.
static async Task ExecuteSendMessage(string? ReplaceObjectId, string MarkdownInput, string BareJid, ChatViewModel? ChatViewModel)
Sends a Markdown-formatted chat message
override async Task OnInitialize()
Method called when view is initialized for the first time. Use this method to implement registration ...
override async Task OnDispose()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
Task< string > Title
Title of the current view
static async Task< bool > ProcessXmppUri(string Uri)
Processes an XMPP URI
Command IotScUriClicked
Command executed when a multi-media-link with the iotsc URI scheme is clicked.
Command NeuroFeatureUriClicked
Command executed when a multi-media-link with the nfeat URI scheme is clicked.
async Task ExecuteUriClicked(string Uri, UriScheme Scheme)
Called when a Multi-media URI link using the XMPP URI scheme.
async Task MessageAddedAsync(ChatMessage Message)
External message has been received
override void OnPropertyChanged(PropertyChangedEventArgs e)
static Task ExecuteSendMessage(string? ReplaceObjectId, string MarkdownInput, string BareJid)
Sends a Markdown-formatted chat message
async Task MessageUpdatedAsync(ChatMessage Message)
External message has been updated
string? MediaContentType
Content-Type of associated media.
ChatViewModel(ChatPage Page, ChatNavigationArgs? Args)
Creates an instance of the ChatViewModel class.
Command IotDiscoUriClicked
Command executed when a multi-media-link with the iotdisco URI scheme is clicked.
Command IotIdUriClicked
Command executed when a multi-media-link with the iotid URI scheme is clicked.
static MessageFrame Create(ChatMessage Message)
Creates a message frame for a given message.
Definition: MessageFrame.cs:34
IView AddLast(ChatMessage Message)
Adds a message to the frame.
Definition: MessageFrame.cs:61
Contact Information model, including related notification information.
CaseInsensitiveString? LegalId
Legal ID of contact.
CaseInsensitiveString? BareJid
Bare JID of contact.
Holds navigation parameters specific to views displaying a list of contacts.
A page that displays a list of the current user's contacts.
Holds navigation parameters specific to views displaying a list of contacts.
A page that displays a list of the current user's contracts.
A page to display when the user wants to view an identity.
Holds navigation parameters specific to viewing things.
A page that displays a list of the current user's things.
Holds navigation parameters specific to eDaler URIs.
TaskCompletionSource< TokenItem?> TokenItemProvider
Task completion source; can be used to wait for a result.
A page that allows the user to view its tokens.
A page that allows the user to realize payments.
A page that allows the user to view information about a token.
A view model that holds the XMPP state.
Asks the user if it wants to remove an existing presence subscription request as well.
Task< bool?> Result
Result will be provided here. If dialog is cancelled, null is returned.
Neuro-Feature Token
Definition: Token.cs:43
static bool TryParse(XmlElement Xml, out Token Token)
Serializes the Token, in normalized form.
Definition: Token.cs:523
void Serialize(StringBuilder Xml)
Serializes the Token, in normalized form.
Definition: Token.cs:922
static string GetBody(string Html)
Extracts the contents of the BODY element in a HTML string.
Base class for all HTML nodes.
Definition: HtmlNode.cs:11
abstract void Export(XmlWriter Output)
Exports the HTML document to XML.
Static class managing encoding and decoding of internet content.
static bool TryGetContentType(string FileExtension, out string ContentType)
Tries to get the content type of an item, given its file extension.
Contains a markdown document. This markdown document class supports original markdown,...
static string Encode(string s)
Encodes all special characters in a string so that it can be included in a markdown document without ...
async Task< string > GeneratePlainText()
Generates Plain Text from the markdown text.
async Task< string > GenerateHTML()
Generates HTML from the markdown text.
static Task< MarkdownDocument > CreateAsync(string MarkdownText, params Type[] TransparentExceptionTypes)
Contains a markdown document. This markdown document class supports original markdown,...
Contains settings that the Markdown parser uses to customize its behavior.
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:27
Contains the definition of a contract
Definition: Contract.cs:22
static Task< ParsedContract > Parse(XmlDocument Xml)
Validates a contract XML Document, and returns the contract definition in it.
Definition: Contract.cs:397
void Serialize(StringBuilder Xml, bool IncludeNamespace, bool IncludeIdAttribute, bool IncludeClientSignatures, bool IncludeAttachments, bool IncludeStatus, bool IncludeServerSignature, bool IncludeAttachmentReferences)
Serializes the Contract, in normalized form.
Definition: Contract.cs:1542
Adds support for legal identities, smart contracts and signatures to an XMPP client.
static string LegalIdUriString(string LegalId)
Legal identity URI, as a string.
Abstract base class for contractual parameters
Definition: Parameter.cs:17
Contains information about a parsed contract.
bool Ok
If the response is an OK result response (true), or an error response (false).
Event arguments for HTTP File Upload callback methods.
async Task PUT(byte[] Content, string ContentType, int Timeout)
Uploads file content to the server.
Manages an XMPP client connection. Implements XMPP, as defined in https://tools.ietf....
Definition: XmppClient.cs:59
static string GetBareJID(string JID)
Gets the Bare JID from a JID, which may be a Full JID.
Definition: XmppClient.cs:6901
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:626
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
static Task< object > TryLoadObject(string CollectionName, object ObjectId)
Tries to load an object given its Object ID ObjectId and its collection name CollectionName .
Definition: Database.cs:1079
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
This filter selects objects that have a named field lesser than a given value.
Task DeleteEvents(NotificationEventType Type, CaseInsensitiveString Category)
Deletes events for a given button and category.
bool TryGetNotificationEvents(NotificationEventType Type, CaseInsensitiveString Category, [NotNullWhen(true)] out NotificationEvent[]? Events)
Tries to get available notification events.
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.
Interfaces for views displaying markdown
Definition: IChatView.cs:43
Interface for linkable views.
Definition: ILinkableView.cs:7
abstract class NotificationEvent()
Abstract base class of notification events.
NotificationEventType
Button on which event is to be displayed.
BackMethod
Navigation Back Method
Definition: BackMethod.cs:7
QoSLevel
Quality of Service Level for asynchronous messages. Support for QoS Levels must be supported by the r...
Definition: QoSLevel.cs:8
MessageType
Type of message received.
Definition: MessageType.cs:7
ContentType
DTLS Record content type.
Definition: Enumerations.cs:11
Definition: App.xaml.cs:4