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;
23using NeuroFeatures;
24using SkiaSharp;
25using System.ComponentModel;
26using System.Globalization;
28using System.Text;
29using System.Timers;
30using System.Xml;
31using Waher.Content;
40
42{
47 {
48 private readonly SortedDictionary<string, LinkedListNode<MessageRecord>> messagesByObjectId = [];
49 private readonly LinkedList<MessageRecord> messages = [];
50 private readonly LinkedList<MessageFrame> frames = [];
51 private readonly ChatPage page;
52 private readonly object synchObject = new();
53 private TaskCompletionSource<bool> waitUntilBound = new();
54 private IView? scrollTo;
55
56 private class MessageRecord(ChatMessage Message, LinkedListNode<MessageFrame> FrameNode)
57 {
58 public ChatMessage Message = Message;
59 public LinkedListNode<MessageFrame> FrameNode = FrameNode;
60
61 public DateTime Created => this.Message.Created;
62 public MessageType MessageType => this.Message.MessageType;
63 }
64
71 : base()
72 {
73 this.page = Page;
74 this.LegalId = Args?.LegalId ?? string.Empty;
75 this.BareJid = Args?.BareJid ?? string.Empty;
76 this.FriendlyName = Args?.FriendlyName ?? string.Empty;
77 this.UniqueId = Args?.UniqueId;
78
79 this.XmppUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.Xmpp));
80 this.IotIdUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.IotId));
81 this.IotScUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.IotSc));
82 this.NeuroFeatureUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.NeuroFeature));
83 this.IotDiscoUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.IotDisco));
84 this.EDalerUriClicked = new Command(async Parameter => await this.ExecuteUriClicked(Parameter as string ?? "", UriScheme.EDaler));
85 this.HyperlinkClicked = new Command(async Parameter => await ExecuteHyperlinkClicked(Parameter));
86 }
87
91 public Command XmppUriClicked { get; }
92
96 public Command IotIdUriClicked { get; }
97
101 public Command IotScUriClicked { get; }
102
106 public Command NeuroFeatureUriClicked { get; }
107
111 public Command IotDiscoUriClicked { get; }
112
116 public Command EDalerUriClicked { get; }
117
121 public Command HyperlinkClicked { get; }
122
123 private static async Task ExecuteHyperlinkClicked(object Parameter)
124 {
125 if (Parameter is not string Url)
126 return;
127
128 await App.OpenUrlAsync(Url);
129 }
130
131
133 public override async Task OnInitializeAsync()
134 {
135 await base.OnInitializeAsync();
136
137 this.scrollTo = await this.LoadMessagesAsync(false);
138
139 this.waitUntilBound.TrySetResult(true);
140
142
143 }
144
146 public override async Task OnDisposeAsync()
147 {
148 await base.OnDisposeAsync();
149
150 this.waitUntilBound = new TaskCompletionSource<bool>();
151 }
152
153 private Task Page_OnAfterAppearing(object Sender, EventArgs e)
154 {
155 if (this.scrollTo is Element)
156 {
157 // await Task.Delay(100); // TODO: Why is this necessary? ScrollToAsync does not scroll to end element without it...
158
159 // await this.page.ScrollView.ScrollToAsync(this.page.Bottom, ScrollToPosition.End, false);
160 this.scrollTo = null;
161 }
162
163 return Task.CompletedTask;
164 }
165
169 [ObservableProperty]
170 private string? uniqueId;
171
175 [ObservableProperty]
176 private string? bareJid;
177
181 [ObservableProperty]
182 private string? legalId;
183
187 [ObservableProperty]
188 private string? friendlyName;
189
193 [ObservableProperty]
194 [NotifyCanExecuteChangedFor(nameof(SendCommand))]
195 [NotifyCanExecuteChangedFor(nameof(CancelCommand))]
196 private string markdownInput = string.Empty;
197
199 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
200 {
201 base.OnPropertyChanged(e);
202
203 switch (e.PropertyName)
204 {
205 case nameof(this.MarkdownInput):
206 this.IsWriting = !string.IsNullOrEmpty(this.MarkdownInput);
207 break;
208
209 case nameof(this.MessageId):
210 this.IsWriting = !string.IsNullOrEmpty(this.MessageId);
211 break;
212
213 case nameof(this.IsWriting):
214 this.IsButtonExpanded = false;
215 break;
216
217 case nameof(this.IsRecordingAudio):
218 // TODO: Audio
219 //
220 //this.IsRecordingPaused = audioRecorder.Value.IsPaused;
221 this.IsWriting = this.IsRecordingAudio;
222
223 //if (audioRecorderTimer is null)
224 //{
225 // audioRecorderTimer = new System.Timers.Timer(100);
226 // audioRecorderTimer.Elapsed += this.OnAudioRecorderTimer;
227 // audioRecorderTimer.AutoReset = true;
228 //}
229 //
230 //audioRecorderTimer.Enabled = this.IsRecordingAudio;
231
232 this.OnPropertyChanged(nameof(RecordingTime));
233 this.CancelCommand.NotifyCanExecuteChanged();
234 break;
235
236 case nameof(this.IsConnected):
237 this.SendCommand.NotifyCanExecuteChanged();
238 this.CancelCommand.NotifyCanExecuteChanged();
239 this.RecordAudioCommand.NotifyCanExecuteChanged();
240 this.TakePhotoCommand.NotifyCanExecuteChanged();
241 this.EmbedFileCommand.NotifyCanExecuteChanged();
242 this.EmbedIdCommand.NotifyCanExecuteChanged();
243 this.EmbedContractCommand.NotifyCanExecuteChanged();
244 this.EmbedMoneyCommand.NotifyCanExecuteChanged();
245 this.EmbedTokenCommand.NotifyCanExecuteChanged();
246 this.EmbedThingCommand.NotifyCanExecuteChanged();
247 break;
248 }
249 }
250
254 [ObservableProperty]
255 private string? messageId;
256
260 [ObservableProperty]
261 [NotifyCanExecuteChangedFor(nameof(LoadMoreMessagesCommand))]
262 private bool existsMoreMessages;
263
267 [ObservableProperty]
268 [NotifyCanExecuteChangedFor(nameof(RecordAudioCommand))]
269 [NotifyCanExecuteChangedFor(nameof(TakePhotoCommand))]
270 [NotifyCanExecuteChangedFor(nameof(EmbedFileCommand))]
271 [NotifyCanExecuteChangedFor(nameof(EmbedIdCommand))]
272 [NotifyCanExecuteChangedFor(nameof(EmbedContractCommand))]
273 [NotifyCanExecuteChangedFor(nameof(EmbedMoneyCommand))]
274 [NotifyCanExecuteChangedFor(nameof(EmbedTokenCommand))]
275 [NotifyCanExecuteChangedFor(nameof(EmbedThingCommand))]
276 private bool isWriting;
277
281 [ObservableProperty]
282 [NotifyCanExecuteChangedFor(nameof(SendCommand))]
283 private bool isRecordingAudio;
284
288 [ObservableProperty]
289 private bool isRecordingPaused;
290
294 public static string RecordingTime
295 {
296 get
297 {
298 // TODO: Audio
299 //
300 //double Milliseconds = audioRecorder.Value.TotalAudioTimeout.TotalMilliseconds - audioRecorder.Value.RecordingTime.TotalMilliseconds;
301 //return (Milliseconds > 0) ? string.Format(CultureInfo.CurrentCulture, "{0:F0}s left", Math.Ceiling(Milliseconds / 1000.0)) : "TIMEOUT";
302
303 return string.Empty;
304 }
305 }
306
311 public async Task MessageAddedAsync(ChatMessage Message)
312 {
313 if(Message.ParsedXaml is null)
314 await Message.GenerateXaml(this);
315
316 MainThread.BeginInvokeOnMainThread(async () =>
317 {
318 IView? View = await this.MessageAddedMainThread(Message, true);
319
320 if (View is Element)
321 {
322 await Task.Delay(25);
323 double Width = this.page.ScrollView.ScrollX;
324 double Height = this.page.ScrollView.ContentSize.Height;
325 await this.page.ScrollView.ScrollToAsync(Width, Height, true);
326 }
327 });
328 }
329
330 private async Task<IView?> MessageAddedMainThread(ChatMessage Message, bool Historic)
331 {
332 this.HasMessages = true;
333
334 TaskCompletionSource<IView?> Result = new();
335
336 try
337 {
338 if(Message.ParsedXaml is null)
339 await Message.GenerateXaml(this); // Makes sure XAML is generated
340
341 lock (this.synchObject)
342 {
343 LinkedListNode<MessageRecord>? MessageNode = Historic ? this.messages.Last : this.messages.First;
344 LinkedListNode<MessageFrame>? FrameNode;
345 MessageFrame? Frame;
346 MessageRecord Rec;
347 IView? View;
348 // int i;
349 if (MessageNode is null)
350 {
351
352 Frame = MessageFrame.Create(Message);
353 View = Frame.AddLast(Message);
354 this.page.Messages.Add(Frame);
355
356 FrameNode = this.frames.AddLast(Frame);
357
358 Rec = new(Message, FrameNode);
359 MessageNode = this.messages.AddLast(Rec);
360 }
361 else if (Historic)
362 {
363 while (MessageNode is not null && Message.Created > MessageNode.Value.Created)
364 MessageNode = MessageNode.Next;
365 if (MessageNode is null)
366 {
367 FrameNode = this.frames.Last!;
368
369 if (FrameNode.Value.MessageType != Message.MessageType)
370 {
371 FrameNode = this.frames.AddLast(MessageFrame.Create(Message));
372 this.page.Messages.Add(FrameNode.Value);
373 }
374
375 View = FrameNode.Value.AddLast(Message);
376
377 Rec = new(Message, FrameNode);
378 MessageNode = this.messages.AddLast(Rec);
379 }
380 else
381 {
382 View = null;
383 // TODO
384 }
385 }
386 else
387 {
388 while (MessageNode is not null && Message.Created < MessageNode.Value.Created)
389 MessageNode = MessageNode.Previous;
390
391 if (MessageNode is null)
392 {
393 FrameNode = this.frames.First!;
394
395 if (FrameNode.Value.MessageType != Message.MessageType)
396 {
397 FrameNode = this.frames.AddFirst(MessageFrame.Create(Message));
398 this.page.Messages.Insert(0, FrameNode.Value);
399 }
400
401 View = FrameNode.Value.AddFirst(Message);
402
403 Rec = new(Message, FrameNode);
404 MessageNode = this.messages.AddFirst(Rec);
405 }
406 else
407 {
408 View = null;
409 // TODO
410 }
411 }
412
413 Result.TrySetResult(View);
414
415 {
416
417
418 //else if (MessageNode.Value.ParsedXaml is IView PrevMessageXaml &&
419 // (i = this.page.Messages.IndexOf(PrevMessageXaml)) >= 0)
420 //{
421 // if (MessageNode.Value.ObjectId == Message.ObjectId)
422 // {
423 // this.page.Messages.RemoveAt(i);
424 // this.page.Messages.Insert(i, MessageXaml);
425 //
426 // MessageNode.Value = Message;
427 // }
428 // else
429 // {
430 // this.page.Messages.Insert(i + 1, MessageXaml);
431 // MessageNode = this.messages.AddAfter(MessageNode, Message);
432 // }
433 //}
434 //else
435 //{
436 // MessageNode = this.messages.AddLast(Message);
437 // this.page.Messages.Children.Add(MessageXaml);
438 //}
439 }
440
441 this.messagesByObjectId[Message.ObjectId ?? string.Empty] = MessageNode;
442 }
443 }
444 catch (Exception Ex)
445 {
446 Result.TrySetException(Ex);
447 }
448
449 return await Result.Task;
450 }
451
456 public async Task MessageUpdatedAsync(ChatMessage Message)
457 {
458 try
459 {
460 await Message.GenerateXaml(this);
461 }
462 catch (Exception Ex)
463 {
464 ServiceRef.LogService.LogException(Ex);
465 return;
466 }
467
468 if (Message.ParsedXaml is not IView MessageXaml || string.IsNullOrEmpty(Message.ObjectId))
469 return;
470
471 MainThread.BeginInvokeOnMainThread(() =>
472 {
473 try
474 {
475 lock (this.synchObject)
476 {
477 int Index;
478
479 if (this.messagesByObjectId.TryGetValue(Message.ObjectId, out LinkedListNode<MessageRecord>? Node) &&
480 Node.Value.Message.ParsedXaml is IView PrevMessageXaml &&
481 PrevMessageXaml.Parent is VerticalStackLayout Parent &&
482 (Index = Parent.IndexOf(PrevMessageXaml)) >= 0)
483 {
484 Parent.RemoveAt(Index);
485 Parent.Insert(Index, MessageXaml);
486
487 Node.Value.Message = Message;
488
489 // TODO: Update XAML
490 }
491 }
492 }
493 catch (Exception Ex)
494 {
495 ServiceRef.LogService.LogException(Ex);
496 }
497 });
498 }
499
500 private async Task<IView?> LoadMessagesAsync(bool LoadMore = true)
501 {
502 IEnumerable<ChatMessage>? Messages = null;
504 DateTime LastTime;
505 ChatMessage[] A;
506
507 try
508 {
509 this.ExistsMoreMessages = false;
510
511 lock (this.synchObject)
512 {
513 LastTime = LoadMore && this.messages.First is not null ? this.messages.First.Value.Created : DateTime.MaxValue;
514 }
515
517 new FilterFieldEqualTo("RemoteBareJid", this.BareJid),
518 new FilterFieldLesserThan("Created", LastTime)), "-Created");
519
520 A = [.. Messages];
521 C -= A.Length;
522
523 if (!LoadMore)
524 Array.Reverse(A);
525 }
526 catch (Exception Ex)
527 {
528 ServiceRef.LogService.LogException(Ex);
529 this.ExistsMoreMessages = false;
530 return null;
531 }
532
533 TaskCompletionSource<IView?> Result = new();
534
535 MainThread.BeginInvokeOnMainThread(async () =>
536 {
537 try
538 {
539 IView? Last = null;
540
541 foreach (ChatMessage Message in A)
542 Last = await this.MessageAddedMainThread(Message, true);
543
544 this.ExistsMoreMessages = C <= 0;
545
546 Result.TrySetResult(Last);
547 }
548 catch (Exception Ex)
549 {
550 Result.TrySetException(Ex);
551 }
552 });
553
554 return await Result.Task;
555 }
556
557 [ObservableProperty]
558 private bool hasMessages = false;
559
563 [ObservableProperty]
564 private bool isButtonExpanded;
565
569 [RelayCommand]
570 private void ExpandButtons()
571 {
572 this.IsButtonExpanded = !this.IsButtonExpanded;
573 }
574
578 [RelayCommand(CanExecute = nameof(CanExecuteLoadMoreMessages))]
579 private Task<IView?> LoadMoreMessages()
580 {
581 return this.LoadMessagesAsync(true);
582 }
583
584 private bool CanExecuteLoadMoreMessages()
585 {
586 return this.ExistsMoreMessages && this.page.Messages.Count > 0;
587 }
588
589 private bool CanExecuteSendMessage()
590 {
591 return this.IsConnected && (!string.IsNullOrEmpty(this.MarkdownInput) || this.IsRecordingAudio);
592 }
593
597 [RelayCommand(CanExecute = nameof(CanExecuteSendMessage))]
598 private async Task Send()
599 {
600 if (this.IsRecordingAudio)
601 {
602 // TODO: Audio
603 //
604 // try
605 // {
606 // await audioRecorder.Value.StopRecording();
607 // string audioPath = await this.audioRecorderTask!;
608 //
609 // if (audioPath is not null)
610 // await this.EmbedMedia(audioPath, true);
611 // }
612 // catch (Exception ex)
613 // {
614 // ServiceRef.LogService.LogException(ex);
615 // }
616 // finally
617 // {
618 // this.IsRecordingAudio = false;
619 // }
620 }
621 else
622 {
623 await this.ExecuteSendMessage(this.MessageId, this.MarkdownInput);
624 await this.Cancel();
625 }
626 }
627
628 private Task ExecuteSendMessage(string? ReplaceObjectId, string MarkdownInput)
629 {
630 return ExecuteSendMessage(ReplaceObjectId, MarkdownInput, this.BareJid!, this);
631 }
632
639 public static Task ExecuteSendMessage(string? ReplaceObjectId, string MarkdownInput, string BareJid)
640 {
641 return ExecuteSendMessage(ReplaceObjectId, MarkdownInput, BareJid, null);
642 }
643
651 public static async Task ExecuteSendMessage(string? ReplaceObjectId, string MarkdownInput, string BareJid, ChatViewModel? ChatViewModel)
652 {
653 try
654 {
655 if (string.IsNullOrEmpty(MarkdownInput))
656 return;
657
658 MarkdownSettings Settings = new()
659 {
660 AllowScriptTag = false,
661 EmbedEmojis = false, // TODO: Emojis
662 AudioAutoplay = false,
663 AudioControls = false,
664 ParseMetaData = false,
665 VideoAutoplay = false,
666 VideoControls = false
667 };
668
669 MarkdownDocument Doc = await MarkdownDocument.CreateAsync(MarkdownInput, Settings);
670
671 ChatMessage Message = new()
672 {
673 Created = DateTime.UtcNow,
674 RemoteBareJid = BareJid,
675 RemoteObjectId = string.Empty,
676
678 Html = HtmlDocument.GetBody(await Doc.GenerateHTML()),
679 PlainText = (await Doc.GeneratePlainText()).Trim(),
680 Markdown = MarkdownInput
681 };
682
683 StringBuilder Xml = new();
684
685 Xml.Append("<content xmlns=\"urn:xmpp:content\" type=\"text/markdown\">");
686 Xml.Append(XML.Encode(MarkdownInput));
687 Xml.Append("</content><html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>");
688
689 HtmlDocument HtmlDoc = new("<root>" + Message.Html + "</root>");
690
691 foreach (HtmlNode N in (HtmlDoc.Body ?? HtmlDoc.Root).Children)
692 N.Export(Xml);
693
694 Xml.Append("</body></html>");
695
696 if (!string.IsNullOrEmpty(ReplaceObjectId))
697 {
698 Xml.Append("<replace id='");
699 Xml.Append(ReplaceObjectId);
700 Xml.Append("' xmlns='urn:xmpp:message-correct:0'/>");
701 }
702
703 if (string.IsNullOrEmpty(ReplaceObjectId))
704 {
705 await Database.Insert(Message);
706
707 if (ChatViewModel is not null)
708 await ChatViewModel.MessageAddedAsync(Message);
709 }
710 else
711 {
712 ChatMessage Old = await Database.TryLoadObject<ChatMessage>(ReplaceObjectId);
713
714 if (Old is null)
715 {
716 ReplaceObjectId = null;
717 await Database.Insert(Message);
718
719 if (ChatViewModel is not null)
720 await ChatViewModel.MessageAddedAsync(Message);
721 }
722 else
723 {
724 Old.Updated = Message.Created;
725 Old.Html = Message.Html;
726 Old.PlainText = Message.PlainText;
727 Old.Markdown = Message.Markdown;
728
729 await Database.Update(Old);
730
731 Message = Old;
732
733 if (ChatViewModel is not null)
734 await ChatViewModel.MessageUpdatedAsync(Message);
735 }
736 }
737 ServiceRef.XmppService.SendMessage(QoSLevel.Unacknowledged, Waher.Networking.XMPP.MessageType.Chat, Message.ObjectId ?? string.Empty,
738 BareJid, Xml.ToString(), Message.PlainText, string.Empty, string.Empty, string.Empty, string.Empty, null, null);
739 }
740 catch (Exception Ex)
741 {
743 }
744 }
745
749 [RelayCommand(CanExecute = nameof(CanExecutePauseResume))]
750 private Task PauseResume()
751 {
752 // TODO: Audio
753 //
754 // if (audioRecorder.Value.IsPaused)
755 // await audioRecorder.Value.Resume();
756 // else
757 // await audioRecorder.Value.Pause();
758 //
759 // this.IsRecordingPaused = audioRecorder.Value.IsPaused;
760
761 return Task.CompletedTask;
762 }
763
764 private static bool CanExecutePauseResume()
765 {
766 // TODO: Audio
767 //
768 // return this.IsRecordingAudio && audioRecorder.Value.IsRecording;
769 return false;
770 }
771
772 private bool CanExecuteCancelMessage()
773 {
774 return this.IsConnected && (!string.IsNullOrEmpty(this.MarkdownInput) || this.IsRecordingAudio);
775 }
776
780 [RelayCommand(CanExecute = nameof(CanExecuteCancelMessage))]
781 private Task Cancel()
782 {
783 if (this.IsRecordingAudio)
784 {
785 // TODO: Audio
786 //
787 // try
788 // {
789 // return audioRecorder.Value.StopRecording();
790 // }
791 // catch (Exception ex)
792 // {
793 // ServiceRef.LogService.LogException(ex);
794 // }
795 // finally
796 // {
797 // this.IsRecordingAudio = false;
798 // }
799 }
800 else
801 {
802 this.MarkdownInput = string.Empty;
803 this.MessageId = string.Empty;
804 }
805
806 return Task.CompletedTask;
807 }
808
809 // TODO: Audio
810 //
811 // private static System.Timers.Timer audioRecorderTimer;
812 //
813 // private static readonly Lazy<AudioRecorderService> audioRecorder = new(() =>
814 // {
815 // return new AudioRecorderService()
816 // {
817 // StopRecordingOnSilence = false,
818 // StopRecordingAfterTimeout = true,
819 // TotalAudioTimeout = TimeSpan.FromSeconds(60)
820 // };
821 // }, LazyThreadSafetyMode.PublicationOnly);
822 //
823 //private readonly Task<string>? audioRecorderTask = null;
824
825 private bool CanExecuteRecordAudio()
826 {
827 return this.IsConnected && !this.IsWriting && ServiceRef.XmppService.FileUploadIsSupported;
828 }
829
830 private void OnAudioRecorderTimer(object? source, ElapsedEventArgs e)
831 {
832 this.OnPropertyChanged(nameof(RecordingTime));
833
834 // TODO: Audio
835 //
836 // this.IsRecordingPaused = audioRecorder.Value.IsPaused;
837 }
838
839 [RelayCommand(CanExecute = nameof(CanExecuteRecordAudio))]
840 private async Task RecordAudio()
841 {
842 if (!ServiceRef.XmppService.FileUploadIsSupported)
843 {
846 return;
847 }
848
849 try
850 {
851 PermissionStatus Status = await Permissions.RequestAsync<Permissions.Microphone>();
852
853 if (Status == PermissionStatus.Granted)
854 {
855 // TODO: Audio
856 //
857 // this.audioRecorderTask = await audioRecorder.Value.StartRecording();
858 // this.IsRecordingAudio = true;
859 }
860 }
861 catch (Exception Ex)
862 {
863 ServiceRef.LogService.LogException(Ex);
864 }
865 }
866
867 private bool CanExecuteTakePhoto()
868 {
869 return this.IsConnected && !this.IsWriting && ServiceRef.XmppService.FileUploadIsSupported;
870 }
871
875 [RelayCommand(CanExecute = nameof(CanExecuteTakePhoto))]
876 private async Task TakePhoto()
877 {
878 if (!ServiceRef.XmppService.FileUploadIsSupported)
879 {
882 return;
883 }
884
885 bool Permitted = await ServiceRef.PermissionService.CheckCameraPermissionAsync();
886 if (!Permitted)
887 return;
888
889 if (DeviceInfo.Platform == DevicePlatform.iOS)
890 {
891 FileResult? CapturedPhoto;
892
893 try
894 {
895 CapturedPhoto = await MediaPicker.Default.CapturePhotoAsync(new MediaPickerOptions()
896 {
898 });
899 }
900 catch (Exception Ex)
901 {
903 ServiceRef.Localizer[nameof(AppResources.TakingAPhotoIsNotSupported)] + ": " + Ex.Message);
904 return;
905 }
906
907 if (CapturedPhoto is not null)
908 {
909 try
910 {
911 await this.EmbedMedia(CapturedPhoto.FullPath, true);
912 }
913 catch (Exception Ex)
914 {
916 }
917 }
918 }
919 else
920 {
921 FileResult? CapturedPhoto;
922
923 try
924 {
925 CapturedPhoto = await MediaPicker.CapturePhotoAsync();
926 if (CapturedPhoto is null)
927 return;
928 }
929 catch (Exception Ex)
930 {
932 ServiceRef.Localizer[nameof(AppResources.TakingAPhotoIsNotSupported)] + ": " + Ex.Message);
933 return;
934 }
935
936 if (CapturedPhoto is not null)
937 {
938 try
939 {
940 await this.EmbedMedia(CapturedPhoto.FullPath, true);
941 }
942 catch (Exception Ex)
943 {
945 }
946 }
947 }
948 }
949
950 private async Task EmbedMedia(string FilePath, bool DeleteFile)
951 {
952 try
953 {
954 byte[] Bin = File.ReadAllBytes(FilePath);
955 if (!InternetContent.TryGetContentType(Path.GetExtension(FilePath), out string ContentType))
956 ContentType = "application/octet-stream";
957
958 if (Bin.Length > ServiceRef.TagProfile.HttpFileUploadMaxSize)
959 {
962 return;
963 }
964
965 // Taking or picking photos switches to another app, so ID app has to reconnect again after.
966 if (!await ServiceRef.XmppService.WaitForConnectedState(Constants.Timeouts.XmppConnect))
967 {
970 return;
971 }
972
973 string FileName = Path.GetFileName(FilePath);
974
975 // Encrypting image
976
977 using RandomNumberGenerator Rnd = RandomNumberGenerator.Create();
978 byte[] Key = new byte[16];
979 byte[] IV = new byte[16];
980
981 Rnd.GetBytes(Key);
982 Rnd.GetBytes(IV);
983
984 Aes Aes = Aes.Create();
985 Aes.BlockSize = 128;
986 Aes.KeySize = 256;
987 Aes.Mode = CipherMode.CBC;
988 Aes.Padding = PaddingMode.PKCS7;
989
990 using ICryptoTransform Transform = Aes.CreateEncryptor(Key, IV);
991 Bin = Transform.TransformFinalBlock(Bin, 0, Bin.Length);
992
993 // Preparing File upload service that content uploaded next is encrypted, and can be stored in encrypted storage.
994
995 StringBuilder Xml = new();
996
997 Xml.Append("<prepare xmlns='http://waher.se/Schema/EncryptedStorage.xsd' filename='");
998 Xml.Append(XML.Encode(FileName));
999 Xml.Append("' size='");
1000 Xml.Append(Bin.Length.ToString(CultureInfo.InvariantCulture));
1001 Xml.Append("' content-type='application/octet-stream'/>");
1002
1003 await ServiceRef.XmppService.IqSetAsync(ServiceRef.TagProfile.HttpFileUploadJid!, Xml.ToString());
1004 // Empty response expected. Errors cause an exception to be raised.
1005
1006 // Requesting upload slot
1007
1008 HttpFileUploadEventArgs Slot = await ServiceRef.XmppService.RequestUploadSlotAsync(
1009 FileName, "application/octet-stream", Bin.Length);
1010
1011 if (!Slot.Ok)
1012 throw Slot.StanzaError ?? new Exception(Slot.ErrorText);
1013
1014 // Uploading encrypted image
1015
1016 await Slot.PUT(Bin, "application/octet-stream", (int)Constants.Timeouts.UploadFile.TotalMilliseconds);
1017
1018 // Generating Markdown message to send to recipient
1019
1020 StringBuilder Markdown = new();
1021
1022 Markdown.Append("![");
1023 Markdown.Append(MarkdownDocument.Encode(FileName));
1024 Markdown.Append("](");
1025 Markdown.Append(Constants.UriSchemes.Aes256);
1026 Markdown.Append(':');
1027 Markdown.Append(Convert.ToBase64String(Key));
1028 Markdown.Append(':');
1029 Markdown.Append(Convert.ToBase64String(IV));
1030 Markdown.Append(':');
1031 Markdown.Append(ContentType);
1032 Markdown.Append(':');
1033 Markdown.Append(Slot.GetUrl);
1034
1035 SKImageInfo ImageInfo = SKBitmap.DecodeBounds(Bin);
1036 if (!ImageInfo.IsEmpty)
1037 {
1038 Markdown.Append(' ');
1039 Markdown.Append(ImageInfo.Width.ToString(CultureInfo.InvariantCulture));
1040 Markdown.Append(' ');
1041 Markdown.Append(ImageInfo.Height.ToString(CultureInfo.InvariantCulture));
1042 }
1043
1044 Markdown.Append(')');
1045
1046 await this.ExecuteSendMessage(string.Empty, Markdown.ToString());
1047
1048 // TODO: End-to-End encryption, or using Elliptic Curves of recipient together with sender to deduce shared secret.
1049
1050 if (DeleteFile)
1051 File.Delete(FilePath);
1052 }
1053 catch (Exception Ex)
1054 {
1056 ServiceRef.LogService.LogException(Ex);
1057 return;
1058 }
1059 }
1060
1061 private bool CanExecuteEmbedFile()
1062 {
1063 return this.IsConnected && !this.IsWriting && ServiceRef.XmppService.FileUploadIsSupported;
1064 }
1065
1069 [RelayCommand(CanExecute = nameof(CanExecuteEmbedFile))]
1070 private async Task EmbedFile()
1071 {
1072 if (!ServiceRef.XmppService.FileUploadIsSupported)
1073 {
1075 return;
1076 }
1077
1078 FileResult? PickedPhoto = await MediaPicker.PickPhotoAsync();
1079
1080 if (PickedPhoto is not null)
1081 await this.EmbedMedia(PickedPhoto.FullPath, false);
1082 }
1083
1084 private bool CanExecuteEmbedId()
1085 {
1086 return this.IsConnected && !this.IsWriting;
1087 }
1088
1092 [RelayCommand(CanExecute = nameof(CanExecuteEmbedId))]
1093 private async Task EmbedId()
1094 {
1095 TaskCompletionSource<ContactInfoModel?> SelectedContact = new();
1097 {
1098 CanScanQrCode = true
1099 };
1100
1102
1103 ContactInfoModel? Contact = await SelectedContact.Task;
1104 if (Contact is null)
1105 return;
1106
1107 await this.waitUntilBound.Task; // Wait until view is bound again.
1108
1109 if (Contact.LegalIdentity is not null)
1110 {
1111 StringBuilder Markdown = new();
1112
1113 Markdown.Append("```");
1114 Markdown.AppendLine(Constants.UriSchemes.IotId);
1115
1116 Contact.LegalIdentity.Serialize(Markdown, true, true, true, true, true, true, true);
1117
1118 Markdown.AppendLine();
1119 Markdown.AppendLine("```");
1120
1121 await this.ExecuteSendMessage(string.Empty, Markdown.ToString());
1122 return;
1123 }
1124
1125 if (!string.IsNullOrEmpty(Contact.LegalId))
1126 {
1127 await this.ExecuteSendMessage(string.Empty, "![" + MarkdownDocument.Encode(Contact.FriendlyName) + "](" + ContractsClient.LegalIdUriString(Contact.LegalId) + ")");
1128 return;
1129 }
1130
1131 if (!string.IsNullOrEmpty(Contact.BareJid))
1132 {
1133 await this.ExecuteSendMessage(string.Empty, "![" + MarkdownDocument.Encode(Contact.FriendlyName) + "](xmpp:" + Contact.BareJid + "?subscribe)");
1134 return;
1135 }
1136 }
1137
1138 private bool CanExecuteEmbedContract()
1139 {
1140 return this.IsConnected && !this.IsWriting;
1141 }
1142
1146 [RelayCommand(CanExecute = nameof(CanExecuteEmbedContract))]
1147 private async Task EmbedContract()
1148 {
1149 TaskCompletionSource<Contract?> SelectedContract = new();
1150 MyContractsNavigationArgs Args = new(ContractsListMode.Contracts, SelectedContract);
1151
1153
1154 Contract? Contract = await SelectedContract.Task;
1155 if (Contract is null)
1156 return;
1157
1158 await this.waitUntilBound.Task; // Wait until view is bound again.
1159
1160 StringBuilder Markdown = new();
1161
1162 Markdown.Append("```");
1163 Markdown.AppendLine(Constants.UriSchemes.IotSc);
1164
1165 Contract.Serialize(Markdown, true, true, true, true, true, true, true);
1166
1167 Markdown.AppendLine();
1168 Markdown.AppendLine("```");
1169
1170 await this.ExecuteSendMessage(string.Empty, Markdown.ToString());
1171 }
1172
1173 private bool CanExecuteEmbedMoney()
1174 {
1175 return this.IsConnected && !this.IsWriting;
1176 }
1177
1181 [RelayCommand(CanExecute = nameof(CanExecuteEmbedMoney))]
1182 private async Task EmbedMoney()
1183 {
1184 StringBuilder Sb = new();
1185
1186 Sb.Append("edaler:");
1187
1188 if (!string.IsNullOrEmpty(this.LegalId))
1189 {
1190 Sb.Append("ti=");
1191 Sb.Append(this.LegalId);
1192 }
1193 else if (!string.IsNullOrEmpty(this.BareJid))
1194 {
1195 Sb.Append("t=");
1196 Sb.Append(this.BareJid);
1197 }
1198 else
1199 return;
1200
1201 Balance CurrentBalance = await ServiceRef.XmppService.GetEDalerBalance();
1202
1203 Sb.Append(";cu=");
1204 Sb.Append(CurrentBalance.Currency);
1205
1206 if (!EDalerUri.TryParse(Sb.ToString(), out EDalerUri Parsed))
1207 return;
1208
1209 TaskCompletionSource<string?> UriToSend = new();
1210 EDalerUriNavigationArgs Args = new(Parsed, this.FriendlyName ?? string.Empty, UriToSend);
1211
1213
1214 string? Uri = await UriToSend.Task;
1215 if (string.IsNullOrEmpty(Uri) || !EDalerUri.TryParse(Uri, out Parsed))
1216 return;
1217
1218 await this.waitUntilBound.Task; // Wait until view is bound again.
1219
1220 Sb.Clear();
1221 Sb.Append(MoneyToString.ToString(Parsed.Amount));
1222
1223 if (Parsed.AmountExtra.HasValue)
1224 {
1225 Sb.Append(" (+");
1226 Sb.Append(MoneyToString.ToString(Parsed.AmountExtra.Value));
1227 Sb.Append(')');
1228 }
1229
1230 Sb.Append(' ');
1231 Sb.Append(Parsed.Currency);
1232
1233 await this.ExecuteSendMessage(string.Empty, "![" + Sb.ToString() + "](" + Uri + ")");
1234 }
1235
1236 private bool CanExecuteEmbedToken()
1237 {
1238 return this.IsConnected && !this.IsWriting;
1239 }
1240
1244 [RelayCommand(CanExecute = nameof(CanExecuteEmbedToken))]
1245 private async Task EmbedToken()
1246 {
1247 MyTokensNavigationArgs Args = new();
1248
1250
1251 TokenItem? Selected = await Args.TokenItemProvider.Task;
1252
1253 if (Selected is null)
1254 return;
1255
1256 StringBuilder Markdown = new();
1257
1258 Markdown.AppendLine("```nfeat");
1259
1260 Selected.Token.Serialize(Markdown);
1261
1262 Markdown.AppendLine();
1263 Markdown.AppendLine("```");
1264
1265 await this.ExecuteSendMessage(string.Empty, Markdown.ToString());
1266 }
1267
1268 private bool CanExecuteEmbedThing()
1269 {
1270 return this.IsConnected && !this.IsWriting;
1271 }
1272
1276 [RelayCommand(CanExecute = nameof(CanExecuteEmbedThing))]
1277 private async Task EmbedThing()
1278 {
1279 TaskCompletionSource<ContactInfoModel?> ThingToShare = new();
1280 MyThingsNavigationArgs Args = new(ThingToShare);
1281
1283
1284 ContactInfoModel? Thing = await ThingToShare.Task;
1285 if (Thing is null)
1286 return;
1287
1288 await this.waitUntilBound.Task; // Wait until view is bound again.
1289
1290 StringBuilder Sb = new();
1291
1292 Sb.Append("![");
1293 Sb.Append(MarkdownDocument.Encode(Thing.FriendlyName));
1294 Sb.Append("](iotdisco:JID=");
1295 Sb.Append(Thing.BareJid);
1296
1297 if (!string.IsNullOrEmpty(Thing.SourceId))
1298 {
1299 Sb.Append(";SID=");
1300 Sb.Append(Thing.SourceId);
1301 }
1302
1303 if (!string.IsNullOrEmpty(Thing.Partition))
1304 {
1305 Sb.Append(";PT=");
1306 Sb.Append(Thing.Partition);
1307 }
1308
1309 if (!string.IsNullOrEmpty(Thing.NodeId))
1310 {
1311 Sb.Append(";NID=");
1312 Sb.Append(Thing.NodeId);
1313 }
1314
1315 Sb.Append(')');
1316
1317 await this.ExecuteSendMessage(string.Empty, Sb.ToString());
1318 }
1319
1323 [RelayCommand]
1324 private Task MessageSelected(object Parameter)
1325 {
1326 if (Parameter is ChatMessage Message)
1327 {
1328 // TODO: Audio
1329 //
1330 // if (Message.ParsedXaml is View View)
1331 // {
1332 // AudioPlayerControl AudioPlayer = View.Descendants().OfType<AudioPlayerControl>().FirstOrDefault();
1333 // if (AudioPlayer is not null)
1334 // {
1335 // return Task.CompletedTask;
1336 // }
1337 // }
1338
1339 switch (Message.MessageType)
1340 {
1341
1342 case MessageType.Sent:
1343 this.MessageId = Message.ObjectId;
1344 this.MarkdownInput = Message.Markdown;
1345 break;
1346
1347
1348 case MessageType.Received:
1349 string S = Message.Markdown;
1350 if (string.IsNullOrEmpty(S))
1351 S = MarkdownDocument.Encode(Message.PlainText);
1352
1353 string[] Rows = S.Replace("\r\n", "\n").Replace("\r", "\n").Split('\n');
1354
1355 StringBuilder Quote = new();
1356
1357 foreach (string Row in Rows)
1358 {
1359 Quote.Append("> ");
1360 Quote.AppendLine(Row);
1361 }
1362
1363 Quote.AppendLine();
1364
1365 this.MessageId = string.Empty;
1366 this.MarkdownInput = Quote.ToString();
1367 break;
1368 }
1369 }
1370
1371 return Task.CompletedTask;
1372 }
1373
1379 public async Task ExecuteUriClicked(string Uri, UriScheme Scheme)
1380 {
1381 try
1382 {
1383 if (Scheme == UriScheme.Xmpp)
1384 await ProcessXmppUri(Uri);
1385 else
1386 {
1387 int I = Uri.IndexOf(':');
1388 if (I < 0)
1389 return;
1390
1391 string S = Uri[(I + 1)..].Trim();
1392 if (S.StartsWith('<') && S.EndsWith('>')) // XML
1393 {
1394 XmlDocument Doc = new()
1395 {
1396 PreserveWhitespace = true
1397 };
1398 Doc.LoadXml(S);
1399
1400 switch (Scheme)
1401 {
1402 case UriScheme.IotId:
1403 LegalIdentity Id = LegalIdentity.Parse(Doc.DocumentElement);
1404 ViewIdentityNavigationArgs ViewIdentityArgs = new(Id);
1405
1406 await ServiceRef.NavigationService.GoToAsync(nameof(ViewIdentityPage), ViewIdentityArgs, BackMethod.Pop);
1407 break;
1408
1409 case UriScheme.IotSc:
1410 ParsedContract ParsedContract = await Contract.Parse(Doc.DocumentElement, ServiceRef.XmppService.ContractsClient, true);
1411 ViewContractNavigationArgs ViewContractArgs = new(ParsedContract.Contract, false);
1412
1413 await ServiceRef.NavigationService.GoToAsync(nameof(ViewContractPage), ViewContractArgs, BackMethod.Pop);
1414 break;
1415
1416 case UriScheme.NeuroFeature:
1417 Token? ParsedToken = await Token.TryParse(Doc.DocumentElement);
1418 if (ParsedToken is null)
1419 throw new Exception(ServiceRef.Localizer[nameof(AppResources.InvalidNeuroFeatureToken)]);
1420
1422 Events = [];
1423
1424 TokenDetailsNavigationArgs Args = new(new TokenItem(ParsedToken, Events));
1425
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 ServiceRef.PopupService.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
Represents an instance of the Neuro-Access app.
Definition: App.xaml.cs:125
const int MessageBatchSize
Number of messages to load in a single batch.
Definition: Constants.cs:916
static readonly TimeSpan XmppConnect
XMPP Connect timeout
Definition: Constants.cs:702
static readonly TimeSpan UploadFile
Upload file timeout
Definition: Constants.cs:712
const string IotSc
The IoT Smart Contract URI Scheme (iotsc)
Definition: Constants.cs:163
const string Aes256
AES-256-encrypted data.
Definition: Constants.cs:193
const string IotId
The IoT ID URI Scheme (iotid)
Definition: Constants.cs:153
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 PickPhoto
Looks up a localized string similar to Pick photo.
static string TakePhoto
Looks up a localized string similar to Take photo.
static string SelectingAPhotoIsNotSupported
Looks up a localized string similar to Selecting a photo is not supported on this device.
static string InvalidNeuroFeatureToken
Looks up a localized string similar to Invalid Neuro-Feature token..
static string ServerDoesNotSupportFileUpload
Looks up a localized string similar to The server does not seem to support File Uploading....
static string UnableToConnectTo
Looks up a localized string similar to Unable to connect to {0}.
static string PhotoIsTooLarge
Looks up a localized string similar to Photo is too large.
static string TakingAPhotoIsNotSupported
Looks up a localized string similar to Taking a photo is not supported on this device.
static string SelectContactToPay
Looks up a localized string similar to Below are all contacts in your contact book....
static string TakePhotoToShare
Looks up a localized string similar to Take a photo to share.
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
Base class that references services in the app.
Definition: ServiceRef.cs:43
static ILogService LogService
Log service.
Definition: ServiceRef.cs:214
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 INotificationService NotificationService
Service for managing notifications for the user.
Definition: ServiceRef.cs:334
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 IPermissionService PermissionService
Permission Service
Definition: ServiceRef.cs:358
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:20
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:83
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.
override async Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
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
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 async Task OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
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:46
static async Task< Token > TryParse(XmlElement Xml)
Tries to parse a Token.
Definition: Token.cs:555
string TokenId
Token ID
Definition: Token.cs:116
void Serialize(StringBuilder Xml)
Serializes the Token, in normalized form.
Definition: Token.cs:997
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, Dictionary< string, string > Namespaces)
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:21
static string Encode(string s)
Encodes a string for use in XML.
Definition: XML.cs:29
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:441
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:1621
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.
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:58
static string GetBareJID(string JID)
Gets the Bare JID from a JID, which may be a Full JID.
Definition: XmppClient.cs:6958
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static async Task Update(object Object)
Updates an object in the database.
Definition: Database.cs:1211
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
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:1838
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 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.
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