Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
MyWalletViewModel.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using CommunityToolkit.Mvvm.Input;
3using EDaler;
4using EDaler.Uris;
5using Microsoft.Maui.Controls.Shapes;
23using NeuroFeatures;
24using System.Xml;
27
29{
35 {
36 private readonly WalletNavigationArgs? navigationArguments = Args;
37 private DateTime lastEDalerEvent;
38 private DateTime lastTokenEvent;
39 private bool hasMoreTokens;
40 private bool hasTotals;
41 private bool hasTokens;
42
44 protected override async Task OnInitialize()
45 {
46 await base.OnInitialize();
47
48 this.EDalerFrontGlyph = "https://" + ServiceRef.TagProfile.Domain + "/Images/eDalerFront200.png";
49 this.EDalerBackGlyph = "https://" + ServiceRef.TagProfile.Domain + "/Images/eDalerBack200.png";
50
51 if (this.navigationArguments is not null)
52 {
53 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
54
55 await this.AssignProperties(this.navigationArguments.Balance, this.navigationArguments.PendingAmount,
56 this.navigationArguments.PendingCurrency, this.navigationArguments.PendingPayments, this.navigationArguments.Events,
57 this.navigationArguments.More, ServiceRef.XmppService.LastEDalerEvent, NotificationEvents);
58 }
59
60 ServiceRef.XmppService.EDalerBalanceUpdated += this.Wallet_BalanceUpdated;
61 ServiceRef.XmppService.NeuroFeatureAdded += this.Wallet_TokenAdded;
62 ServiceRef.XmppService.NeuroFeatureRemoved += this.Wallet_TokenRemoved;
63 ServiceRef.NotificationService.OnNewNotification += this.NotificationService_OnNewNotification;
64 }
65
67 protected override async Task OnAppearing()
68 {
69 await base.OnAppearing();
70
71 if (((this.Balance is not null) && (ServiceRef.XmppService.LastEDalerBalance is not null) &&
72 (this.Balance.Amount != ServiceRef.XmppService.LastEDalerBalance.Amount ||
73 this.Balance.Currency != ServiceRef.XmppService.LastEDalerBalance.Currency ||
74 this.Balance.Timestamp != ServiceRef.XmppService.LastEDalerBalance.Timestamp)) ||
75 this.lastEDalerEvent != ServiceRef.XmppService.LastEDalerEvent)
76 {
77 await this.ReloadEDalerWallet(ServiceRef.XmppService.LastEDalerBalance ?? this.Balance);
78 }
79
80
81 if (this.hasTokens && this.lastTokenEvent != ServiceRef.XmppService.LastNeuroFeatureEvent)
82 await this.LoadTokens(true);
83 }
84
86 protected override async Task OnDispose()
87 {
88 ServiceRef.XmppService.EDalerBalanceUpdated -= this.Wallet_BalanceUpdated;
89 ServiceRef.XmppService.NeuroFeatureAdded -= this.Wallet_TokenAdded;
90 ServiceRef.XmppService.NeuroFeatureRemoved -= this.Wallet_TokenRemoved;
91 ServiceRef.NotificationService.OnNewNotification -= this.NotificationService_OnNewNotification;
92
93 await base.OnDispose();
94 }
95
96 private SortedDictionary<CaseInsensitiveString, NotificationEvent[]> GetNotificationEvents()
97 {
98 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> Result = ServiceRef.NotificationService.GetEventsByCategory(NotificationEventType.Wallet);
99 int NrBalance = 0;
100 int NrToken = 0;
101
102 foreach (NotificationEvent[] Events in Result.Values)
103 {
104 foreach (NotificationEvent Event in Events)
105 {
106 if (Event is BalanceNotificationEvent)
107 NrBalance++;
108 else if (Event is TokenNotificationEvent)
109 NrToken++;
110 }
111 }
112
113 this.NrBalanceNotifications = NrBalance;
114 this.NrTokenNotifications = NrToken;
115
116 return Result;
117 }
118
119 private async Task AssignProperties(Balance? Balance, decimal PendingAmount, string? PendingCurrency,
120 EDaler.PendingPayment[]? PendingPayments, EDaler.AccountEvent[]? Events, bool More, DateTime LastEvent,
121 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents)
122 {
123 if (Balance is not null)
124 {
125 this.Balance = Balance;
126 this.Amount = Balance.Amount;
127 this.ReservedAmount = Balance.Reserved;
128 this.Currency = Balance.Currency;
129 this.Timestamp = Balance.Timestamp;
130 }
131
132 this.lastEDalerEvent = LastEvent;
133
134 this.PendingAmount = PendingAmount;
135 this.PendingCurrency = PendingCurrency;
136 this.HasPending = (PendingPayments?.Length ?? 0) > 0;
137 this.HasEvents = (Events?.Length ?? 0) > 0;
138 this.HasMoreEvents = More;
139
140 Dictionary<string, string> FriendlyNames = [];
141 string? FriendlyName;
142
143 ObservableItemGroup<IUniqueItem> NewPaymentItems = new(nameof(this.PaymentItems), []);
144
145 if (PendingPayments is not null)
146 {
147 List<IUniqueItem> NewPendingPayments = new(PendingPayments.Length);
148
149 foreach (EDaler.PendingPayment Payment in PendingPayments)
150 {
151 if (!FriendlyNames.TryGetValue(Payment.To, out FriendlyName))
152 {
153 FriendlyName = await ContactInfo.GetFriendlyName(Payment.To);
154 FriendlyNames[Payment.To] = FriendlyName;
155 }
156
157 NewPendingPayments.Add(new PendingPaymentItem(Payment, FriendlyName));
158 }
159
160 if (NewPendingPayments.Count > 0)
161 NewPaymentItems.Add(new ObservableItemGroup<IUniqueItem>(nameof(PendingPaymentItem), NewPendingPayments));
162 }
163
164 if (Events is not null)
165 {
166 List<IUniqueItem> NewAccountEvents = new(Events.Length);
167
168 foreach (EDaler.AccountEvent Event in Events)
169 {
170 if (!FriendlyNames.TryGetValue(Event.Remote, out FriendlyName))
171 {
172 FriendlyName = await ContactInfo.GetFriendlyName(Event.Remote);
173 FriendlyNames[Event.Remote] = FriendlyName;
174 }
175
176 if (!NotificationEvents.TryGetValue(Event.TransactionId.ToString(), out NotificationEvent[]? CategoryEvents))
177 CategoryEvents = [];
178
179 NewAccountEvents.Add(new AccountEventItem(Event, this, FriendlyName, CategoryEvents));
180 }
181
182 if (NewAccountEvents.Count > 0)
183 NewPaymentItems.Add(new ObservableItemGroup<IUniqueItem>(nameof(AccountEventItem), NewAccountEvents));
184 }
185
186 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.PaymentItems, NewPaymentItems));
187 }
188
189 private Task Wallet_BalanceUpdated(object? Sender, BalanceEventArgs e)
190 {
191 Task.Run(() => this.ReloadEDalerWallet(e.Balance));
192 return Task.CompletedTask;
193 }
194
195 private async Task ReloadEDalerWallet(Balance? Balance)
196 {
197 try
198 {
199 (decimal PendingAmount, string PendingCurrency, EDaler.PendingPayment[] PendingPayments) = await ServiceRef.XmppService.GetPendingEDalerPayments();
200 (EDaler.AccountEvent[] Events, bool More) = await ServiceRef.XmppService.GetEDalerAccountEvents(Constants.BatchSizes.AccountEventBatchSize);
201 IUniqueItem? OldItems = this.PaymentItems.FirstOrDefault(el => string.Equals(el.UniqueName, nameof(AccountEventItem), StringComparison.Ordinal));
202
203 // Reload also items which were loaded earlier by the LoadMoreAccountEvents
204 if (More &&
205 (OldItems is ObservableItemGroup<IUniqueItem> OldAccountEvents) &&
206 (OldAccountEvents.LastOrDefault() is AccountEventItem OldLastEvent) &&
207 (Events.LastOrDefault() is EDaler.AccountEvent NewLastEvent) &&
208 (OldLastEvent.Timestamp < NewLastEvent.Timestamp))
209 {
210 List<EDaler.AccountEvent> AllEvents = new(Events);
211 EDaler.AccountEvent[] Events2;
212 bool More2 = true;
213
214 while (More2)
215 {
216 EDaler.AccountEvent LastEvent = AllEvents.Last();
217 (Events2, More2) = await ServiceRef.XmppService.GetEDalerAccountEvents(Constants.BatchSizes.AccountEventBatchSize, LastEvent.Timestamp);
218
219 if (More2)
220 {
221 More = true;
222
223 for (int i = 0; i < Events2.Length; i++)
224 {
225 EDaler.AccountEvent Event = Events2[i];
226 AllEvents.Add(Event);
227
228 if (OldLastEvent.Timestamp.Equals(Event.Timestamp))
229 {
230 More2 = false;
231 break;
232 }
233 }
234 }
235 else
236 {
237 More = false;
238 AllEvents.AddRange(Events2);
239 }
240 }
241
242 Events = [.. AllEvents];
243 }
244
245 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
246
247 MainThread.BeginInvokeOnMainThread(async () => await this.AssignProperties(Balance, PendingAmount, PendingCurrency,
248 PendingPayments, Events, More, ServiceRef.XmppService.LastEDalerEvent, NotificationEvents));
249 }
250 catch (Exception ex)
251 {
252 ServiceRef.LogService.LogException(ex);
253 }
254 }
255
256 #region Properties
257
261 [ObservableProperty]
262 private Balance? balance;
263
267 [ObservableProperty]
268 private decimal amount;
269
273 [ObservableProperty]
274 private string? currency;
275
279 [ObservableProperty]
280 private bool hasPending;
281
285 [ObservableProperty]
286 private bool isFrontViewShowing;
287
291 [ObservableProperty]
292 private decimal pendingAmount;
293
297 [ObservableProperty]
298 private string? pendingCurrency;
299
303 [ObservableProperty]
304 private decimal reservedAmount;
305
309 [ObservableProperty]
310 private DateTime timestamp;
311
315 [ObservableProperty]
316 private string? eDalerFrontGlyph;
317
321 [ObservableProperty]
322 private string? eDalerBackGlyph;
323
327 [ObservableProperty]
328 private bool hasEvents;
329
333 [ObservableProperty]
334 private bool hasMoreEvents;
335
339 [ObservableProperty]
340 private int nrBalanceNotifications;
341
345 [ObservableProperty]
346 private int nrTokenNotifications;
347
351 public ObservableItemGroup<IUniqueItem> PaymentItems { get; } = new(nameof(PaymentItems), []);
352
356 public ObservableItemGroup<IUniqueItem> Tokens { get; } = new(nameof(Tokens), []);
357
361 public ObservableItemGroup<IUniqueItem> Totals { get; } = new(nameof(Totals), []);
362
363 #endregion
364
368 [RelayCommand]
369 private Task Back()
370 {
371 return this.GoBack();
372 }
373
377 [RelayCommand]
378 private static async Task ScanQrCode()
379 {
380 await Services.UI.QR.QrCode.ScanQrCodeAndHandleResult();
381 }
382
386 [RelayCommand(CanExecute = nameof(IsConnected))]
387 private async Task RequestPayment()
388 {
389 try
390 {
391 IBuyEDalerServiceProvider[] ServiceProviders = await ServiceRef.XmppService.GetServiceProvidersForBuyingEDalerAsync();
392
393 if (ServiceProviders.Length == 0)
394 {
395 EDalerBalanceNavigationArgs Args = new(this.Balance);
396
397 await ServiceRef.UiService.GoToAsync(nameof(RequestPaymentPage), Args, BackMethod.CurrentPage);
398 }
399 else
400 {
401 List<IBuyEDalerServiceProvider> ServiceProviders2 = [];
402
403 ServiceProviders2.AddRange(ServiceProviders);
404 ServiceProviders2.Add(new EmptyBuyEDalerServiceProvider());
405
406 ServiceProvidersNavigationArgs e = new(ServiceProviders2.ToArray(),
407 ServiceRef.Localizer[nameof(AppResources.BuyEDaler)],
408 ServiceRef.Localizer[nameof(AppResources.SelectServiceProviderBuyEDaler)]);
409
411
412 IBuyEDalerServiceProvider? ServiceProvider = (IBuyEDalerServiceProvider?)(e.ServiceProvider is null ? null : await e.ServiceProvider.Task);
413
414 if (ServiceProvider is not null)
415 {
416 if (string.IsNullOrEmpty(ServiceProvider.Id))
417 {
418 EDalerBalanceNavigationArgs Args = new(this.Balance);
419
420 await ServiceRef.UiService.GoToAsync(nameof(RequestPaymentPage), Args, BackMethod.CurrentPage);
421 }
422 else if (string.IsNullOrEmpty(ServiceProvider.BuyEDalerTemplateContractId))
423 {
424 TaskCompletionSource<decimal?> Result = new();
425 BuyEDalerNavigationArgs Args = new(this.Balance?.Currency, Result);
426
427 await ServiceRef.UiService.GoToAsync(nameof(BuyEDalerPage), Args, BackMethod.CurrentPage);
428
429 decimal? Amount = await Result.Task;
430
431 if (Amount.HasValue && Amount.Value > 0)
432 {
434 Amount.Value, this.Balance?.Currency);
435
436 WaitForComletion(Transaction);
437 }
438 }
439 else
440 {
441 CreationAttributesEventArgs e2 = await ServiceRef.XmppService.GetNeuroFeatureCreationAttributes();
442 Dictionary<CaseInsensitiveString, object> Parameters = new()
443 {
444 { "Visibility", "CreatorAndParts" },
445 { "Role", "Buyer" },
446 { "Currency", this.Balance?.Currency ?? e2.Currency },
447 { "TrustProvider", e2.TrustProviderId }
448 };
449
450 await ServiceRef.ContractOrchestratorService.OpenContract(ServiceProvider.BuyEDalerTemplateContractId,
451 ServiceRef.Localizer[nameof(AppResources.BuyEDaler)], Parameters);
452
454 IDictionary<CaseInsensitiveString, object>[] Options = await OptionsTransaction.Wait();
455
456 if (ServiceRef.UiService.CurrentPage is IContractOptionsPage ContractOptionsPage)
457 MainThread.BeginInvokeOnMainThread(async () => await ContractOptionsPage.ShowContractOptions(Options));
458 }
459 }
460 }
461 }
462 catch (Exception ex)
463 {
464 ServiceRef.LogService.LogException(ex);
466 }
467 }
468
469 private static async void WaitForComletion(PaymentTransaction Transaction)
470 {
471 try
472 {
473 await Transaction.Wait();
474 }
475 catch (Exception ex)
476 {
477 ServiceRef.LogService.LogException(ex);
479 }
480 }
481
485 [RelayCommand(CanExecute = nameof(IsConnected))]
486 private async Task MakePayment()
487 {
488 try
489 {
490 ISellEDalerServiceProvider[] ServiceProviders = await ServiceRef.XmppService.GetServiceProvidersForSellingEDalerAsync();
491
492 if (ServiceProviders.Length == 0)
493 {
494 ContactListNavigationArgs Args = new(ServiceRef.Localizer[nameof(AppResources.SelectContactToPay)], SelectContactAction.MakePayment)
495 {
496 CanScanQrCode = true,
497 AllowAnonymous = true,
498 AnonymousText = ServiceRef.Localizer[nameof(AppResources.Open)]
499 };
500
501 await ServiceRef.UiService.GoToAsync(nameof(MyContactsPage), Args, BackMethod.CurrentPage);
502 }
503 else
504 {
505 List<ISellEDalerServiceProvider> ServiceProviders2 = [];
506
507 ServiceProviders2.AddRange(ServiceProviders);
508 ServiceProviders2.Add(new EmptySellEDalerServiceProvider());
509
510 ServiceProvidersNavigationArgs e = new(ServiceProviders2.ToArray(),
511 ServiceRef.Localizer[nameof(AppResources.SellEDaler)],
512 ServiceRef.Localizer[nameof(AppResources.SelectServiceProviderSellEDaler)]);
513
515
516 ISellEDalerServiceProvider? ServiceProvider = (ISellEDalerServiceProvider?)(e.ServiceProvider is null ? null : await e.ServiceProvider.Task);
517
518 if (ServiceProvider is not null)
519 {
520 if (string.IsNullOrEmpty(ServiceProvider.Id))
521 {
522 ContactListNavigationArgs Args = new(ServiceRef.Localizer[nameof(AppResources.SelectContactToPay)], SelectContactAction.MakePayment)
523 {
524 CanScanQrCode = true,
525 AllowAnonymous = true,
526 AnonymousText = ServiceRef.Localizer[nameof(AppResources.Open)],
527 };
528
529 await ServiceRef.UiService.GoToAsync(nameof(MyContactsPage), Args, BackMethod.CurrentPage);
530 }
531 else if (string.IsNullOrEmpty(ServiceProvider.SellEDalerTemplateContractId))
532 {
533 TaskCompletionSource<decimal?> Result = new();
534 SellEDalerNavigationArgs Args = new(this.Balance?.Currency, Result);
535
536 await ServiceRef.UiService.GoToAsync(nameof(SellEDalerPage), Args, BackMethod.CurrentPage);
537
538 decimal? Amount = await Result.Task;
539
540 if (Amount.HasValue && Amount.Value > 0)
541 {
543 Amount.Value, this.Balance?.Currency);
544
545 WaitForComletion(Transaction);
546 }
547 }
548 else
549 {
550 CreationAttributesEventArgs e2 = await ServiceRef.XmppService.GetNeuroFeatureCreationAttributes();
551 Dictionary<CaseInsensitiveString, object> Parameters = new()
552 {
553 { "Visibility", "CreatorAndParts" },
554 { "Role", "Seller" },
555 { "Currency", this.Balance?.Currency ?? e2.Currency },
556 { "TrustProvider", e2.TrustProviderId }
557 };
558
559 await ServiceRef.ContractOrchestratorService.OpenContract(ServiceProvider.SellEDalerTemplateContractId,
560 ServiceRef.Localizer[nameof(AppResources.SellEDaler)], Parameters);
561
563 IDictionary<CaseInsensitiveString, object>[] Options = await OptionsTransaction.Wait();
564
565 if (ServiceRef.UiService.CurrentPage is IContractOptionsPage ContractOptionsPage)
566 MainThread.BeginInvokeOnMainThread(async () => await ContractOptionsPage.ShowContractOptions(Options));
567 }
568 }
569 }
570 }
571 catch (Exception ex)
572 {
573 ServiceRef.LogService.LogException(ex);
575 }
576 }
577
581 [RelayCommand]
582 private async Task ShowPaymentItem(object Item)
583 {
584 if (Item is PendingPaymentItem PendingItem)
585 {
586 if (!ServiceRef.XmppService.TryParseEDalerUri(PendingItem.Uri, out EDalerUri Uri, out string Reason))
587 {
588 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
589 ServiceRef.Localizer[nameof(AppResources.InvalidEDalerUri), Reason]);
590 return;
591 }
592
593 await ServiceRef.UiService.GoToAsync(nameof(PendingPayment.PendingPaymentPage), new EDalerUriNavigationArgs(Uri, PendingItem.FriendlyName));
594 }
595 else if (Item is AccountEventItem EventItem)
596 await ServiceRef.UiService.GoToAsync(nameof(AccountEvent.AccountEventPage), new AccountEvent.AccountEventNavigationArgs(EventItem));
597 }
598
602 [RelayCommand]
603 private async Task LoadMoreAccountEvents()
604 {
605 if (this.HasMoreEvents)
606 {
607 this.HasMoreEvents = false; // So multiple requests are not made while scrolling.
608 bool More = true;
609
610 try
611 {
612 EDaler.AccountEvent[]? Events = null;
613 IUniqueItem? OldItems = this.PaymentItems.FirstOrDefault(el => string.Equals(el.UniqueName, nameof(AccountEventItem), StringComparison.Ordinal));
614
615 if (OldItems is null)
616 (Events, More) = await ServiceRef.XmppService.GetEDalerAccountEvents(Constants.BatchSizes.AccountEventBatchSize);
617 else
618 {
619 ObservableItemGroup<IUniqueItem> OldAccountEvents = (ObservableItemGroup<IUniqueItem>)OldItems;
620
621 if (OldAccountEvents.LastOrDefault() is AccountEventItem LastEvent)
622 {
623 (Events, More) = await ServiceRef.XmppService.GetEDalerAccountEvents(Constants.BatchSizes.AccountEventBatchSize, LastEvent.Timestamp);
624 }
625 }
626
627 if (Events is not null)
628 {
629 List<IUniqueItem> NewAccountEvents = [];
630 Dictionary<string, string> FriendlyNames = [];
631 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
632
633 foreach (EDaler.AccountEvent Event in Events)
634 {
635 if (!FriendlyNames.TryGetValue(Event.Remote, out string? FriendlyName))
636 {
637 FriendlyName = await ContactInfo.GetFriendlyName(Event.Remote);
638 FriendlyNames[Event.Remote] = FriendlyName;
639 }
640
641 if (!NotificationEvents.TryGetValue(Event.TransactionId.ToString(), out NotificationEvent[]? CategoryEvents))
642 CategoryEvents = [];
643
644 NewAccountEvents.Add(new AccountEventItem(Event, this, FriendlyName, CategoryEvents));
645 }
646
647 MainThread.BeginInvokeOnMainThread(() =>
648 {
649
650 if (OldItems is ObservableItemGroup<IUniqueItem> SubItems)
651 {
652 foreach (IUniqueItem Item in NewAccountEvents)
653 SubItems.Add(Item);
654 }
655 else
656 {
657 this.PaymentItems.Add(new ObservableItemGroup<IUniqueItem>(nameof(AccountEventItem), NewAccountEvents));
658 this.HasMoreEvents = More;
659 }
660 });
661 }
662 }
663 catch (Exception ex)
664 {
665 ServiceRef.LogService.LogException(ex);
666 }
667 }
668 }
669
673 public async void BindTokens()
674 {
675 try
676 {
677 await this.LoadTokens(false);
678 }
679 catch (Exception ex)
680 {
681 ServiceRef.LogService.LogException(ex);
682 }
683 }
684
685 private async Task LoadTokens(bool Reload)
686 {
687 this.lastTokenEvent = ServiceRef.XmppService.LastNeuroFeatureEvent;
688
689 if (!this.hasTotals || Reload)
690 {
691 this.hasTotals = true; // prevent fast reentering
692
693 try
694 {
695 TokenTotalsEventArgs tteArgs = await ServiceRef.XmppService.GetNeuroFeatureTotals();
696
697 if (tteArgs.Ok)
698 {
699 ObservableItemGroup<IUniqueItem> NewTotals = new(nameof(this.Totals), []);
700
701 if (tteArgs.Totals is not null)
702 {
703 foreach (TokenTotal Total in tteArgs.Totals)
704 {
705 NewTotals.Add(new TokenTotalItem(Total));
706 }
707 }
708
709 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.Totals, NewTotals));
710 }
711
712 this.hasTotals = tteArgs.Ok;
713 }
714 catch (Exception ex)
715 {
716 this.hasTotals = false;
717 ServiceRef.LogService.LogException(ex);
718 }
719 }
720
721 if (!this.hasTokens || Reload)
722 {
723 this.hasTokens = true; // prevent fast reentering
724
725 try
726 {
727 SortedDictionary<CaseInsensitiveString, TokenNotificationEvent[]> NotificationEvents =
729
730 TokensEventArgs teArgs = await ServiceRef.XmppService.GetNeuroFeatures(0, Constants.BatchSizes.TokenBatchSize);
731 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> EventsByCateogy = this.GetNotificationEvents();
732
733 ObservableItemGroup<IUniqueItem> NewTokens = new(nameof(this.Tokens), []);
734 List<TokenNotificationEvent> ToDelete = [];
735
736 foreach (KeyValuePair<CaseInsensitiveString, TokenNotificationEvent[]> P in NotificationEvents)
737 {
738 Token? Token = null;
739
740 foreach (TokenNotificationEvent TokenEvent in P.Value)
741 {
742 Token = TokenEvent.Token;
743 if (Token is not null)
744 break;
745 }
746
747 if (Token is not null)
748 {
749 NewTokens.Add(new TokenItem(Token, P.Value));
750 }
751 else
752 {
753 foreach (TokenNotificationEvent TokenEvent in P.Value)
754 {
755 if (TokenEvent is TokenRemovedNotificationEvent)
756 {
757 Geometry Icon = await TokenEvent.GetCategoryIcon();
758 string Description = await TokenEvent.GetDescription();
759
760 NewTokens.Add(new EventModel(TokenEvent.Received, Icon, Description, TokenEvent));
761 }
762 else
763 {
764 ToDelete.Add(TokenEvent);
765 }
766 }
767 }
768 }
769
770 if (ToDelete.Count > 0)
771 await ServiceRef.NotificationService.DeleteEvents([.. ToDelete]);
772
773 if (teArgs.Ok)
774 {
775 if (teArgs.Tokens is not null)
776 {
777 foreach (Token Token in teArgs.Tokens)
778 {
779 if (NotificationEvents.ContainsKey(Token.TokenId))
780 continue;
781
782 if (!EventsByCateogy.TryGetValue(Token.TokenId, out NotificationEvent[]? Events))
783 Events = [];
784
785 NewTokens.Add(new TokenItem(Token, Events));
786 }
787 }
788
789 this.hasMoreTokens = teArgs?.Tokens is not null && teArgs.Tokens.Length == Constants.BatchSizes.TokenBatchSize;
790
791 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.Tokens, NewTokens));
792 }
793
794 this.hasTokens = teArgs?.Ok ?? false;
795 }
796 catch (Exception ex)
797 {
798 this.hasTokens = false;
799 ServiceRef.LogService.LogException(ex);
800 }
801 }
802 }
803
804 internal void ViewsFlipped(bool IsFrontViewShowing)
805 {
806 this.IsFrontViewShowing = IsFrontViewShowing;
807 }
808
812 [RelayCommand]
813 private async Task CreateToken()
814 {
815 try
816 {
817 TaskCompletionSource<Contract?> TemplateSelection = new();
818 MyContractsNavigationArgs Args = new(ContractsListMode.TokenCreationTemplates, TemplateSelection);
819
820 await ServiceRef.UiService.GoToAsync(nameof(MyContractsPage), Args, BackMethod.Pop);
821
822 Contract? Template = await TemplateSelection.Task;
823 if (Template is null)
824 return;
825
826 Dictionary<CaseInsensitiveString, object> Parameters = [];
827 Template.Visibility = ContractVisibility.Public;
828
830 {
831 CreationAttributesEventArgs e2 = await ServiceRef.XmppService.GetNeuroFeatureCreationAttributes();
832 XmlDocument Doc = new()
833 {
834 PreserveWhitespace = true
835 };
836 Doc.LoadXml(Template.ForMachines.OuterXml);
837
838 XmlNamespaceManager NamespaceManager = new(Doc.NameTable);
839 NamespaceManager.AddNamespace("nft", NeuroFeaturesClient.NamespaceNeuroFeatures);
840
841 string? CreatorRole = Doc.SelectSingleNode("/nft:Create/nft:Creator/nft:RoleReference/@role", NamespaceManager)?.Value;
842 string? OwnerRole = Doc.SelectSingleNode("/nft:Create/nft:Owner/nft:RoleReference/@role", NamespaceManager)?.Value;
843 string? TrustProviderRole = Doc.SelectSingleNode("/nft:Create/nft:TrustProvider/nft:RoleReference/@role", NamespaceManager)?.Value;
844 string? CurrencyParameter = Doc.SelectSingleNode("/nft:Create/nft:Currency/nft:ParameterReference/@parameter", NamespaceManager)?.Value;
845 string? CommissionParameter = Doc.SelectSingleNode("/nft:Create/nft:CommissionPercent/nft:ParameterReference/@parameter", NamespaceManager)?.Value;
846
847 if (Template.Parts is null)
848 {
849 List<Part> Parts = [];
850
851 if (!string.IsNullOrEmpty(CreatorRole))
852 {
853 Parts.Add(new Part()
854 {
855 LegalId = ServiceRef.TagProfile.LegalIdentity?.Id,
856 Role = CreatorRole
857 });
858 }
859
860 if (!string.IsNullOrEmpty(TrustProviderRole))
861 {
862 Parts.Add(new Part()
863 {
864 LegalId = e2.TrustProviderId,
865 Role = TrustProviderRole
866 });
867 }
868
869 Template.Parts = [.. Parts];
870 Template.PartsMode = ContractParts.ExplicitlyDefined;
871 }
872 else
873 {
874 foreach (Part Part in Template.Parts)
875 {
876 if (Part.Role == CreatorRole || Part.Role == OwnerRole)
877 Part.LegalId = ServiceRef.TagProfile.LegalIdentity?.Id;
878 else if (Part.Role == TrustProviderRole)
879 Part.LegalId = e2.TrustProviderId;
880 }
881 }
882
883 if (!string.IsNullOrEmpty(CurrencyParameter))
884 Parameters[CurrencyParameter] = e2.Currency;
885
886 if (!string.IsNullOrEmpty(CommissionParameter))
887 Parameters[CommissionParameter] = e2.Commission;
888 }
889
890 NewContractNavigationArgs NewContractArgs = new(Template, true, Parameters);
891
892 await ServiceRef.UiService.GoToAsync(nameof(NewContractPage), NewContractArgs, BackMethod.CurrentPage);
893 }
894 catch (Exception ex)
895 {
897 }
898 }
899
903 [RelayCommand]
904 private async Task LoadMoreTokens()
905 {
906 if (this.hasMoreTokens)
907 {
908 this.hasMoreTokens = false; // So multiple requests are not made while scrolling.
909
910 try
911 {
912 TokensEventArgs e = await ServiceRef.XmppService.GetNeuroFeatures(this.Tokens.Count, Constants.BatchSizes.TokenBatchSize);
913 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> EventsByCateogy = this.GetNotificationEvents();
914
915 MainThread.BeginInvokeOnMainThread(() =>
916 {
917 if (e.Ok)
918 {
919 if (e.Tokens is not null)
920 {
921 foreach (Token Token in e.Tokens)
922 {
923 if (!EventsByCateogy.TryGetValue(Token.TokenId, out NotificationEvent[]? Events))
924 Events = [];
925
926 this.Tokens.Add(new TokenItem(Token, Events));
927 }
928
929 this.hasMoreTokens = e.Tokens.Length == Constants.BatchSizes.TokenBatchSize;
930 }
931 }
932 });
933 }
934 catch (Exception ex)
935 {
936 ServiceRef.LogService.LogException(ex);
937 }
938 }
939 }
940
941 private Task Wallet_TokenAdded(object _, TokenEventArgs e)
942 {
944 Events = [];
945
946 MainThread.BeginInvokeOnMainThread(() =>
947 {
948 TokenItem Item = new(e.Token, Events);
949
950 if (this.Tokens.Count == 0)
951 this.Tokens.Add(Item);
952 else
953 this.Tokens.Insert(0, Item);
954 });
955
956 return Task.CompletedTask;
957 }
958
959 private Task Wallet_TokenRemoved(object _, TokenEventArgs e)
960 {
961 MainThread.BeginInvokeOnMainThread(() =>
962 {
963 int i, c = this.Tokens.Count;
964
965 for (i = 0; i < c; i++)
966 {
967 if (this.Tokens[i] is TokenItem Item && Item.TokenId == e.Token.TokenId)
968 {
969 this.Tokens.RemoveAt(i);
970 break;
971 }
972 }
973 });
974
975 return Task.CompletedTask;
976 }
977
978 private void NotificationService_OnNewNotification(object? Sender, NotificationEventArgs e)
979 {
980 if (e.Event.Type == NotificationEventType.Wallet)
981 {
982 MainThread.BeginInvokeOnMainThread(() =>
983 {
984 if (e.Event is BalanceNotificationEvent)
985 this.NrBalanceNotifications++;
986 else if (e.Event is TokenNotificationEvent)
987 this.NrTokenNotifications++;
988 });
989 }
990 }
991
992 }
993}
Account event
Definition: AccountEvent.cs:16
DateTime Timestamp
Timestamp of transaction
Definition: AccountEvent.cs:47
Contains information about a balance.
Definition: Balance.cs:11
CaseInsensitiveString Currency
Currency of amount.
Definition: Balance.cs:54
decimal Amount
Amount at given point in time.
Definition: Balance.cs:44
decimal Reserved
Reserved amount, that the user cannot use directly.
Definition: Balance.cs:49
DateTime Timestamp
Timestamp of balance.
Definition: Balance.cs:39
Contains information about a pending payment.
Represents a transaction in the eDaler network.
Definition: Transaction.cs:36
Abstract base class for eDaler URIs
Definition: EDalerUri.cs:14
const int TokenBatchSize
Number of tokens to load in a single batch.
Definition: Constants.cs:774
const int AccountEventBatchSize
Number of account events to load in a single batch.
Definition: Constants.cs:779
A set of never changing property constants and helpful values.
Definition: Constants.cs:7
Contains information about a contact.
Definition: ContactInfo.cs:21
static async Task< string > GetFriendlyName(CaseInsensitiveString RemoteId)
Gets the friendly name of a remote identity (Legal ID or Bare JID).
Definition: ContactInfo.cs:257
override Task< Geometry > GetCategoryIcon()
Gets an icon for the category of event.
override Task< string > GetDescription()
Gets a descriptive text for the event.
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 IContractOrchestratorService ContractOrchestratorService
Contract orchestrator service.
Definition: ServiceRef.cs:115
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:67
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 that allows the user to create a new contract.
Holds navigation parameters specific to buying eDaler.
A page that allows the user to buy eDaler.
Holds navigation parameters specific to an eDaler balance event.
Holds navigation parameters specific to eDaler URIs.
Holds navigation parameters specific to the eDaler wallet.
A page that displays information about eDaler received.
Holds navigation parameters specific to selling eDaler.
A page that allows the user to sell eDaler.
A view model that holds the XMPP state.
const string NamespaceNeuroFeatures
Namespace for Neuro-Features.
Neuro-Feature Token
Definition: Token.cs:43
decimal Value
Latest value of token
Definition: Token.cs:300
string TokenId
Token ID
Definition: Token.cs:111
Contains one token total, i.e. sum of token values, for a given currency.
Definition: TokenTotal.cs:7
Contains the definition of a contract
Definition: Contract.cs:22
string ForMachinesLocalName
Local name used by the root node of the machine-readable contents of the contract (ForMachines).
Definition: Contract.cs:294
XmlElement ForMachines
Machine-readable contents of the contract.
Definition: Contract.cs:276
Part[] Parts
Defined parts for the smart contract.
Definition: Contract.cs:258
string ForMachinesNamespace
Namespace used by the root node of the machine-readable contents of the contract (ForMachines).
Definition: Contract.cs:289
Class defining a part in a contract
Definition: Part.cs:30
string Role
Role of the part in the contract
Definition: Part.cs:57
Class defining a role
Definition: Role.cs:7
Contains information about a service provider.
Represents a case-insensitive string.
Interface for information about a service provider that users can use to buy eDaler.
Interface for information about a service provider that users can use to sell eDaler.
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.
SortedDictionary< CaseInsensitiveString, NotificationEvent[]> GetEventsByCategory(NotificationEventType Type)
Gets available notification events for a button, sorted by category.
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.
Interface for pages that can receive contract options from an asynchronous process.
abstract class NotificationEvent()
Abstract base class of notification events.
class NotificationEventArgs(NotificationEvent Event)
Event argument for notification events.
NotificationEventType
Button on which event is to be displayed.
BackMethod
Navigation Back Method
Definition: BackMethod.cs:7
class OptionsTransaction(string TransactionId)
Maintains the status of an ongoing retrieval of payment options.
class PaymentTransaction(string TransactionId, string Currency)
Maintains the status of an ongoing payment transaction.
SelectContactAction
Actions to take when a contact has been selected.
partial class MyWalletViewModel(WalletNavigationArgs? Args)
The view model to bind to for when displaying the wallet.
ContractParts
How the parts of the contract are defined.
Definition: Part.cs:9
ContractVisibility
Visibility types for contracts.
Definition: Enumerations.cs:58