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.Events;
5using EDaler.Uris;
6using Microsoft.Maui.Controls.Shapes;
26using NeuroFeatures;
28using System.Xml;
31
33{
39 {
40 private readonly WalletNavigationArgs? navigationArguments = Args;
41 private DateTime lastEDalerEvent;
42 private DateTime lastTokenEvent;
43 private bool hasMoreTokens;
44 private bool hasTotals;
45 private bool hasTokens;
46
48 public override async Task OnInitializeAsync()
49 {
50 await base.OnInitializeAsync();
51
52 this.EDalerFrontGlyph = "https://" + ServiceRef.TagProfile.Domain + "/Images/eDalerFront200.png";
53 this.EDalerBackGlyph = "https://" + ServiceRef.TagProfile.Domain + "/Images/eDalerBack200.png";
54
55 if (this.navigationArguments is not null)
56 {
57 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
58
59 await this.AssignProperties(this.navigationArguments.Balance, this.navigationArguments.PendingAmount,
60 this.navigationArguments.PendingCurrency, this.navigationArguments.PendingPayments, this.navigationArguments.Events,
61 this.navigationArguments.More, ServiceRef.XmppService.LastEDalerEvent, NotificationEvents);
62 }
63
64 ServiceRef.XmppService.EDalerBalanceUpdated += this.Wallet_BalanceUpdated;
65 ServiceRef.XmppService.NeuroFeatureAdded += this.Wallet_TokenAdded;
66 ServiceRef.XmppService.NeuroFeatureRemoved += this.Wallet_TokenRemoved;
67 ServiceRef.NotificationService.OnNewNotification += this.NotificationService_OnNewNotification;
68 }
69
71 public override async Task OnAppearingAsync()
72 {
73 await base.OnAppearingAsync();
74
75 if (((this.Balance is not null) && (ServiceRef.XmppService.LastEDalerBalance is not null) &&
76 (this.Balance.Amount != ServiceRef.XmppService.LastEDalerBalance.Amount ||
77 this.Balance.Currency != ServiceRef.XmppService.LastEDalerBalance.Currency ||
78 this.Balance.Timestamp != ServiceRef.XmppService.LastEDalerBalance.Timestamp)) ||
79 this.lastEDalerEvent != ServiceRef.XmppService.LastEDalerEvent)
80 {
81 await this.ReloadEDalerWallet(ServiceRef.XmppService.LastEDalerBalance ?? this.Balance);
82 }
83
84
85 if (this.hasTokens && this.lastTokenEvent != ServiceRef.XmppService.LastNeuroFeatureEvent)
86 await this.LoadTokens(true);
87 }
88
90 public override async Task OnDisposeAsync()
91 {
92 ServiceRef.XmppService.EDalerBalanceUpdated -= this.Wallet_BalanceUpdated;
93 ServiceRef.XmppService.NeuroFeatureAdded -= this.Wallet_TokenAdded;
94 ServiceRef.XmppService.NeuroFeatureRemoved -= this.Wallet_TokenRemoved;
95 ServiceRef.NotificationService.OnNewNotification -= this.NotificationService_OnNewNotification;
96
97 await base.OnDisposeAsync();
98 }
99
100 private SortedDictionary<CaseInsensitiveString, NotificationEvent[]> GetNotificationEvents()
101 {
102 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> Result = ServiceRef.NotificationService.GetEventsByCategory(NotificationEventType.Wallet);
103 int NrBalance = 0;
104 int NrToken = 0;
105
106 foreach (NotificationEvent[] Events in Result.Values)
107 {
108 foreach (NotificationEvent Event in Events)
109 {
110 if (Event is BalanceNotificationEvent)
111 NrBalance++;
112 else if (Event is TokenNotificationEvent)
113 NrToken++;
114 }
115 }
116
117 this.NrBalanceNotifications = NrBalance;
118 this.NrTokenNotifications = NrToken;
119
120 return Result;
121 }
122
123 private async Task AssignProperties(Balance? Balance, decimal PendingAmount, string? PendingCurrency,
124 EDaler.PendingPayment[]? PendingPayments, EDaler.AccountEvent[]? Events, bool More, DateTime LastEvent,
125 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents)
126 {
127 if (Balance is not null)
128 {
129 this.Balance = Balance;
130 this.Amount = Balance.Amount;
131 this.ReservedAmount = Balance.Reserved;
132 this.Currency = Balance.Currency;
133 this.Timestamp = Balance.Timestamp;
134 }
135
136 this.lastEDalerEvent = LastEvent;
137
138 this.PendingAmount = PendingAmount;
139 this.PendingCurrency = PendingCurrency;
140 this.HasPending = (PendingPayments?.Length ?? 0) > 0;
141 this.HasEvents = (Events?.Length ?? 0) > 0;
142 this.HasMoreEvents = More;
143
144 Dictionary<string, string> FriendlyNames = [];
145 string? FriendlyName;
146
147 ObservableItemGroup<IUniqueItem> NewPaymentItems = new(nameof(this.PaymentItems), []);
148
149 if (PendingPayments is not null)
150 {
151 List<IUniqueItem> NewPendingPayments = new(PendingPayments.Length);
152
153 foreach (EDaler.PendingPayment Payment in PendingPayments)
154 {
155 if (!FriendlyNames.TryGetValue(Payment.To, out FriendlyName))
156 {
157 FriendlyName = await ContactInfo.GetFriendlyName(Payment.To);
158 FriendlyNames[Payment.To] = FriendlyName;
159 }
160
161 NewPendingPayments.Add(new PendingPaymentItem(Payment, FriendlyName));
162 }
163
164 if (NewPendingPayments.Count > 0)
165 NewPaymentItems.Add(new ObservableItemGroup<IUniqueItem>(nameof(PendingPaymentItem), NewPendingPayments));
166 }
167
168 if (Events is not null)
169 {
170 List<IUniqueItem> NewAccountEvents = new(Events.Length);
171
172 foreach (EDaler.AccountEvent Event in Events)
173 {
174 if (!FriendlyNames.TryGetValue(Event.Remote, out FriendlyName))
175 {
176 FriendlyName = await ContactInfo.GetFriendlyName(Event.Remote);
177 FriendlyNames[Event.Remote] = FriendlyName;
178 }
179
180 if (!NotificationEvents.TryGetValue(Event.TransactionId.ToString(), out NotificationEvent[]? CategoryEvents))
181 CategoryEvents = [];
182
183 NewAccountEvents.Add(new AccountEventItem(Event, this, FriendlyName, CategoryEvents));
184 }
185
186 if (NewAccountEvents.Count > 0)
187 NewPaymentItems.Add(new ObservableItemGroup<IUniqueItem>(nameof(AccountEventItem), NewAccountEvents));
188 }
189
190 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.PaymentItems, NewPaymentItems));
191 }
192
193 private Task Wallet_BalanceUpdated(object? Sender, BalanceEventArgs e)
194 {
195 Task.Run(() => this.ReloadEDalerWallet(e.Balance));
196 return Task.CompletedTask;
197 }
198
199 private async Task ReloadEDalerWallet(Balance? Balance)
200 {
201 try
202 {
203 (decimal PendingAmount, string PendingCurrency, EDaler.PendingPayment[] PendingPayments) = await ServiceRef.XmppService.GetPendingEDalerPayments();
204 (EDaler.AccountEvent[] Events, bool More) = await ServiceRef.XmppService.GetEDalerAccountEvents(Constants.BatchSizes.AccountEventBatchSize);
205 IUniqueItem? OldItems = this.PaymentItems.FirstOrDefault(el => string.Equals(el.UniqueName, nameof(AccountEventItem), StringComparison.Ordinal));
206
207 // Reload also items which were loaded earlier by the LoadMoreAccountEvents
208 if (More &&
209 (OldItems is ObservableItemGroup<IUniqueItem> OldAccountEvents) &&
210 (OldAccountEvents.LastOrDefault() is AccountEventItem OldLastEvent) &&
211 (Events.LastOrDefault() is EDaler.AccountEvent NewLastEvent) &&
212 (OldLastEvent.Timestamp < NewLastEvent.Timestamp))
213 {
214 List<EDaler.AccountEvent> AllEvents = new(Events);
215 EDaler.AccountEvent[] Events2;
216 bool More2 = true;
217
218 while (More2)
219 {
220 EDaler.AccountEvent LastEvent = AllEvents.Last();
221 (Events2, More2) = await ServiceRef.XmppService.GetEDalerAccountEvents(Constants.BatchSizes.AccountEventBatchSize, LastEvent.Timestamp);
222
223 if (More2)
224 {
225 More = true;
226
227 for (int i = 0; i < Events2.Length; i++)
228 {
229 EDaler.AccountEvent Event = Events2[i];
230 AllEvents.Add(Event);
231
232 if (OldLastEvent.Timestamp.Equals(Event.Timestamp))
233 {
234 More2 = false;
235 break;
236 }
237 }
238 }
239 else
240 {
241 More = false;
242 AllEvents.AddRange(Events2);
243 }
244 }
245
246 Events = [.. AllEvents];
247 }
248
249 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
250
251 MainThread.BeginInvokeOnMainThread(async () => await this.AssignProperties(Balance, PendingAmount, PendingCurrency,
252 PendingPayments, Events, More, ServiceRef.XmppService.LastEDalerEvent, NotificationEvents));
253 }
254 catch (Exception ex)
255 {
256 ServiceRef.LogService.LogException(ex);
257 }
258 }
259
260 #region Properties
261
265 [ObservableProperty]
266 private Balance? balance;
267
271 [ObservableProperty]
272 private decimal amount;
273
277 [ObservableProperty]
278 private string? currency;
279
283 [ObservableProperty]
284 private bool hasPending;
285
289 [ObservableProperty]
290 private bool isFrontViewShowing;
291
295 [ObservableProperty]
296 private decimal pendingAmount;
297
301 [ObservableProperty]
302 private string? pendingCurrency;
303
307 [ObservableProperty]
308 private decimal reservedAmount;
309
313 [ObservableProperty]
314 private DateTime timestamp;
315
319 [ObservableProperty]
320 private string? eDalerFrontGlyph;
321
325 [ObservableProperty]
326 private string? eDalerBackGlyph;
327
331 [ObservableProperty]
332 private bool hasEvents;
333
337 [ObservableProperty]
338 private bool hasMoreEvents;
339
343 [ObservableProperty]
344 private int nrBalanceNotifications;
345
349 [ObservableProperty]
350 private int nrTokenNotifications;
351
355 public ObservableItemGroup<IUniqueItem> PaymentItems { get; } = new(nameof(PaymentItems), []);
356
360 public ObservableItemGroup<IUniqueItem> Tokens { get; } = new(nameof(Tokens), []);
361
365 public ObservableItemGroup<IUniqueItem> Totals { get; } = new(nameof(Totals), []);
366
367 #endregion
368
372 [RelayCommand]
373 private Task Back()
374 {
375 return this.GoBack();
376 }
377
381 [RelayCommand]
382 private static async Task ScanQrCode()
383 {
384 await Services.UI.QR.QrCode.ScanQrCodeAndHandleResult();
385 }
386
390 [RelayCommand(CanExecute = nameof(IsConnected))]
391 private async Task RequestPayment()
392 {
393 try
394 {
395 IBuyEDalerServiceProvider[] ServiceProviders = await ServiceRef.XmppService.GetServiceProvidersForBuyingEDalerAsync();
396
397 if (ServiceProviders.Length == 0)
398 {
399 EDalerBalanceNavigationArgs Args = new(this.Balance);
400
401 await ServiceRef.NavigationService.GoToAsync(nameof(RequestPaymentPage), Args, BackMethod.CurrentPage);
402 }
403 else
404 {
405 List<IBuyEDalerServiceProvider> ServiceProviders2 = [];
406
407 ServiceProviders2.AddRange(ServiceProviders);
408 ServiceProviders2.Add(new EmptyBuyEDalerServiceProvider());
409
410 ServiceProvidersNavigationArgs e = new(ServiceProviders2.ToArray(),
413
415
416 IBuyEDalerServiceProvider? ServiceProvider = (IBuyEDalerServiceProvider?)(e.ServiceProvider is null ? null : await e.ServiceProvider.Task);
417
418 if (ServiceProvider is not null)
419 {
420 if (string.IsNullOrEmpty(ServiceProvider.Id))
421 {
422 EDalerBalanceNavigationArgs Args = new(this.Balance);
423
424 await ServiceRef.NavigationService.GoToAsync(nameof(RequestPaymentPage), Args, BackMethod.CurrentPage);
425 }
426 else if (string.IsNullOrEmpty(ServiceProvider.BuyEDalerTemplateContractId))
427 {
428 TaskCompletionSource<decimal?> Result = new();
429 BuyEDalerNavigationArgs Args = new(this.Balance?.Currency, Result);
430
431 await ServiceRef.NavigationService.GoToAsync(nameof(BuyEDalerPage), Args, BackMethod.CurrentPage);
432
433 decimal? Amount = await Result.Task;
434
435 if (Amount.HasValue && Amount.Value > 0)
436 {
438 Amount.Value, this.Balance?.Currency);
439
440 WaitForComletion(Transaction);
441 }
442 }
443 else
444 {
445 CreationAttributesEventArgs e2 = await ServiceRef.XmppService.GetNeuroFeatureCreationAttributes();
446 Dictionary<CaseInsensitiveString, object> Parameters = new()
447 {
448 { "Visibility", "CreatorAndParts" },
449 { "Role", "Buyer" },
450 { "Currency", this.Balance?.Currency ?? e2.Currency },
451 { "TrustProvider", e2.TrustProviderId }
452 };
453
454 await ServiceRef.ContractOrchestratorService.OpenContract(ServiceProvider.BuyEDalerTemplateContractId,
455 ServiceRef.Localizer[nameof(AppResources.BuyEDaler)], Parameters);
456
458 IDictionary<CaseInsensitiveString, object>[] Options = await OptionsTransaction.Wait();
459
461 MainThread.BeginInvokeOnMainThread(async () => await ContractOptionsPage.ShowContractOptions(Options));
462 }
463 }
464 }
465 }
466 catch (Exception ex)
467 {
468 ServiceRef.LogService.LogException(ex);
470 }
471 }
472
473 private static async void WaitForComletion(PaymentTransaction Transaction)
474 {
475 try
476 {
477 await Transaction.Wait();
478 }
479 catch (Exception ex)
480 {
481 ServiceRef.LogService.LogException(ex);
483 }
484 }
485
489 [RelayCommand(CanExecute = nameof(IsConnected))]
490 private async Task MakePayment()
491 {
492 try
493 {
494 ISellEDalerServiceProvider[] ServiceProviders = await ServiceRef.XmppService.GetServiceProvidersForSellingEDalerAsync();
495
496 if (ServiceProviders.Length == 0)
497 {
499 {
500 CanScanQrCode = true,
501 AllowAnonymous = true,
502 AnonymousText = ServiceRef.Localizer[nameof(AppResources.Open)]
503 };
504
505 await ServiceRef.NavigationService.GoToAsync(nameof(MyContactsPage), Args, BackMethod.CurrentPage);
506 }
507 else
508 {
509 List<ISellEDalerServiceProvider> ServiceProviders2 = [];
510
511 ServiceProviders2.AddRange(ServiceProviders);
512 ServiceProviders2.Add(new EmptySellEDalerServiceProvider());
513
514 ServiceProvidersNavigationArgs e = new(ServiceProviders2.ToArray(),
517
519
520 ISellEDalerServiceProvider? ServiceProvider = (ISellEDalerServiceProvider?)(e.ServiceProvider is null ? null : await e.ServiceProvider.Task);
521
522 if (ServiceProvider is not null)
523 {
524 if (string.IsNullOrEmpty(ServiceProvider.Id))
525 {
527 {
528 CanScanQrCode = true,
529 AllowAnonymous = true,
530 AnonymousText = ServiceRef.Localizer[nameof(AppResources.Open)],
531 };
532
533 await ServiceRef.NavigationService.GoToAsync(nameof(MyContactsPage), Args, BackMethod.CurrentPage);
534 }
535 else if (string.IsNullOrEmpty(ServiceProvider.SellEDalerTemplateContractId))
536 {
537 TaskCompletionSource<decimal?> Result = new();
538 SellEDalerNavigationArgs Args = new(this.Balance?.Currency, Result);
539
540 await ServiceRef.NavigationService.GoToAsync(nameof(SellEDalerPage), Args, BackMethod.CurrentPage);
541
542 decimal? Amount = await Result.Task;
543
544 if (Amount.HasValue && Amount.Value > 0)
545 {
547 Amount.Value, this.Balance?.Currency);
548
549 WaitForComletion(Transaction);
550 }
551 }
552 else
553 {
554 CreationAttributesEventArgs e2 = await ServiceRef.XmppService.GetNeuroFeatureCreationAttributes();
555 Dictionary<CaseInsensitiveString, object> Parameters = new()
556 {
557 { "Visibility", "CreatorAndParts" },
558 { "Role", "Seller" },
559 { "Currency", this.Balance?.Currency ?? e2.Currency },
560 { "TrustProvider", e2.TrustProviderId }
561 };
562
563 await ServiceRef.ContractOrchestratorService.OpenContract(ServiceProvider.SellEDalerTemplateContractId,
564 ServiceRef.Localizer[nameof(AppResources.SellEDaler)], Parameters);
565
567 IDictionary<CaseInsensitiveString, object>[] Options = await OptionsTransaction.Wait();
568
570 MainThread.BeginInvokeOnMainThread(async () => await ContractOptionsPage.ShowContractOptions(Options));
571 }
572 }
573 }
574 }
575 catch (Exception ex)
576 {
577 ServiceRef.LogService.LogException(ex);
579 }
580 }
581
585 [RelayCommand]
586 private async Task ShowPaymentItem(object Item)
587 {
588 if (Item is PendingPaymentItem PendingItem)
589 {
590 if (!ServiceRef.XmppService.TryParseEDalerUri(PendingItem.Uri, out EDalerUri Uri, out string Reason))
591 {
594 return;
595 }
596
597 await ServiceRef.NavigationService.GoToAsync(nameof(PendingPayment.PendingPaymentPage), new EDalerUriNavigationArgs(Uri, PendingItem.FriendlyName));
598 }
599 else if (Item is AccountEventItem EventItem)
600 await ServiceRef.NavigationService.GoToAsync(nameof(AccountEvent.AccountEventPage), new AccountEvent.AccountEventNavigationArgs(EventItem));
601 }
602
606 [RelayCommand]
607 private async Task LoadMoreAccountEvents()
608 {
609 if (this.HasMoreEvents)
610 {
611 this.HasMoreEvents = false; // So multiple requests are not made while scrolling.
612 bool More = true;
613
614 try
615 {
616 EDaler.AccountEvent[]? Events = null;
617 IUniqueItem? OldItems = this.PaymentItems.FirstOrDefault(el => string.Equals(el.UniqueName, nameof(AccountEventItem), StringComparison.Ordinal));
618
619 if (OldItems is null)
620 (Events, More) = await ServiceRef.XmppService.GetEDalerAccountEvents(Constants.BatchSizes.AccountEventBatchSize);
621 else
622 {
623 ObservableItemGroup<IUniqueItem> OldAccountEvents = (ObservableItemGroup<IUniqueItem>)OldItems;
624
625 if (OldAccountEvents.LastOrDefault() is AccountEventItem LastEvent)
626 {
627 (Events, More) = await ServiceRef.XmppService.GetEDalerAccountEvents(Constants.BatchSizes.AccountEventBatchSize, LastEvent.Timestamp);
628 }
629 }
630
631 if (Events is not null)
632 {
633 List<IUniqueItem> NewAccountEvents = [];
634 Dictionary<string, string> FriendlyNames = [];
635 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
636
637 foreach (EDaler.AccountEvent Event in Events)
638 {
639 if (!FriendlyNames.TryGetValue(Event.Remote, out string? FriendlyName))
640 {
641 FriendlyName = await ContactInfo.GetFriendlyName(Event.Remote);
642 FriendlyNames[Event.Remote] = FriendlyName;
643 }
644
645 if (!NotificationEvents.TryGetValue(Event.TransactionId.ToString(), out NotificationEvent[]? CategoryEvents))
646 CategoryEvents = [];
647
648 NewAccountEvents.Add(new AccountEventItem(Event, this, FriendlyName, CategoryEvents));
649 }
650
651 MainThread.BeginInvokeOnMainThread(() =>
652 {
653
654 if (OldItems is ObservableItemGroup<IUniqueItem> SubItems)
655 {
656 foreach (IUniqueItem Item in NewAccountEvents)
657 SubItems.Add(Item);
658 }
659 else
660 {
661 this.PaymentItems.Add(new ObservableItemGroup<IUniqueItem>(nameof(AccountEventItem), NewAccountEvents));
662 this.HasMoreEvents = More;
663 }
664 });
665 }
666 }
667 catch (Exception ex)
668 {
669 ServiceRef.LogService.LogException(ex);
670 }
671 }
672 }
673
677 public async void BindTokens()
678 {
679 try
680 {
681 await this.LoadTokens(false);
682 }
683 catch (Exception ex)
684 {
685 ServiceRef.LogService.LogException(ex);
686 }
687 }
688
689 private async Task LoadTokens(bool Reload)
690 {
691 this.lastTokenEvent = ServiceRef.XmppService.LastNeuroFeatureEvent;
692
693 if (!this.hasTotals || Reload)
694 {
695 this.hasTotals = true; // prevent fast reentering
696
697 try
698 {
699 TokenTotalsEventArgs tteArgs = await ServiceRef.XmppService.GetNeuroFeatureTotals();
700
701 if (tteArgs.Ok)
702 {
703 ObservableItemGroup<IUniqueItem> NewTotals = new(nameof(this.Totals), []);
704
705 if (tteArgs.Totals is not null)
706 {
707 foreach (TokenTotal Total in tteArgs.Totals)
708 {
709 NewTotals.Add(new TokenTotalItem(Total));
710 }
711 }
712
713 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.Totals, NewTotals));
714 }
715
716 this.hasTotals = tteArgs.Ok;
717 }
718 catch (Exception ex)
719 {
720 this.hasTotals = false;
721 ServiceRef.LogService.LogException(ex);
722 }
723 }
724
725 if (!this.hasTokens || Reload)
726 {
727 this.hasTokens = true; // prevent fast reentering
728
729 try
730 {
731 SortedDictionary<CaseInsensitiveString, TokenNotificationEvent[]> NotificationEvents =
733
734 TokensEventArgs teArgs = await ServiceRef.XmppService.GetNeuroFeatures(0, Constants.BatchSizes.TokenBatchSize);
735 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> EventsByCateogy = this.GetNotificationEvents();
736
737 ObservableItemGroup<IUniqueItem> NewTokens = new(nameof(this.Tokens), []);
738 List<TokenNotificationEvent> ToDelete = [];
739
740 foreach (KeyValuePair<CaseInsensitiveString, TokenNotificationEvent[]> P in NotificationEvents)
741 {
742 Token? Token = null;
743
744 foreach (TokenNotificationEvent TokenEvent in P.Value)
745 {
746 Token = await TokenEvent.GetTokenAsync();
747 if (Token is not null)
748 break;
749 }
750
751 if (Token is not null)
752 {
753 NewTokens.Add(new TokenItem(Token, P.Value));
754 }
755 else
756 {
757 foreach (TokenNotificationEvent TokenEvent in P.Value)
758 {
759 if (TokenEvent is TokenRemovedNotificationEvent)
760 {
761 Geometry Icon = await TokenEvent.GetCategoryIcon();
762 string Description = await TokenEvent.GetDescription();
763
764 NewTokens.Add(new EventModel(TokenEvent.Received, Icon, Description, TokenEvent));
765 }
766 else
767 {
768 ToDelete.Add(TokenEvent);
769 }
770 }
771 }
772 }
773
774 if (ToDelete.Count > 0)
775 await ServiceRef.NotificationService.DeleteEvents([.. ToDelete]);
776
777 if (teArgs.Ok)
778 {
779 if (teArgs.Tokens is not null)
780 {
781 foreach (Token Token in teArgs.Tokens)
782 {
783 if (NotificationEvents.ContainsKey(Token.TokenId))
784 continue;
785
786 if (!EventsByCateogy.TryGetValue(Token.TokenId, out NotificationEvent[]? Events))
787 Events = [];
788
789 NewTokens.Add(new TokenItem(Token, Events));
790 }
791 }
792
793 this.hasMoreTokens = teArgs?.Tokens is not null && teArgs.Tokens.Length == Constants.BatchSizes.TokenBatchSize;
794
795 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.Tokens, NewTokens));
796 }
797
798 this.hasTokens = teArgs?.Ok ?? false;
799 }
800 catch (Exception ex)
801 {
802 this.hasTokens = false;
803 ServiceRef.LogService.LogException(ex);
804 }
805 }
806 }
807
808 internal void ViewsFlipped(bool IsFrontViewShowing)
809 {
810 this.IsFrontViewShowing = IsFrontViewShowing;
811 }
812
816 [RelayCommand]
817 private async Task CreateToken()
818 {
819 try
820 {
821 TaskCompletionSource<Contract?> TemplateSelection = new();
822 MyContractsNavigationArgs Args = new(ContractsListMode.TokenCreationTemplates, TemplateSelection);
823
825
826 Contract? Template = await TemplateSelection.Task;
827 if (Template is null)
828 return;
829
830 Dictionary<CaseInsensitiveString, object> Parameters = [];
831 Template.Visibility = ContractVisibility.Public;
832
834 {
835 CreationAttributesEventArgs e2 = await ServiceRef.XmppService.GetNeuroFeatureCreationAttributes();
836 XmlDocument Doc = new()
837 {
838 PreserveWhitespace = true
839 };
840 Doc.LoadXml(Template.ForMachines.OuterXml);
841
842 XmlNamespaceManager NamespaceManager = new(Doc.NameTable);
843 NamespaceManager.AddNamespace("nft", NeuroFeaturesClient.NamespaceNeuroFeatures);
844
845 string? CreatorRole = Doc.SelectSingleNode("/nft:Create/nft:Creator/nft:RoleReference/@role", NamespaceManager)?.Value;
846 string? OwnerRole = Doc.SelectSingleNode("/nft:Create/nft:Owner/nft:RoleReference/@role", NamespaceManager)?.Value;
847 string? TrustProviderRole = Doc.SelectSingleNode("/nft:Create/nft:TrustProvider/nft:RoleReference/@role", NamespaceManager)?.Value;
848 string? CurrencyParameter = Doc.SelectSingleNode("/nft:Create/nft:Currency/nft:ParameterReference/@parameter", NamespaceManager)?.Value;
849 string? CommissionParameter = Doc.SelectSingleNode("/nft:Create/nft:CommissionPercent/nft:ParameterReference/@parameter", NamespaceManager)?.Value;
850
851 if (Template.Parts is null)
852 {
853 List<Part> Parts = [];
854
855 if (!string.IsNullOrEmpty(CreatorRole))
856 {
857 Parts.Add(new Part()
858 {
859 LegalId = ServiceRef.TagProfile.LegalIdentity?.Id,
860 Role = CreatorRole
861 });
862 }
863
864 if (!string.IsNullOrEmpty(TrustProviderRole))
865 {
866 Parts.Add(new Part()
867 {
868 LegalId = e2.TrustProviderId,
869 Role = TrustProviderRole
870 });
871 }
872
873 Template.Parts = [.. Parts];
874 Template.PartsMode = ContractParts.ExplicitlyDefined;
875 }
876 else
877 {
878 foreach (Part Part in Template.Parts)
879 {
880 if (Part.Role == CreatorRole || Part.Role == OwnerRole)
881 Part.LegalId = ServiceRef.TagProfile.LegalIdentity?.Id;
882 else if (Part.Role == TrustProviderRole)
883 Part.LegalId = e2.TrustProviderId;
884 }
885 }
886
887 if (!string.IsNullOrEmpty(CurrencyParameter))
888 Parameters[CurrencyParameter] = e2.Currency;
889
890 if (!string.IsNullOrEmpty(CommissionParameter))
891 Parameters[CommissionParameter] = e2.Commission;
892 }
893
894 NewContractNavigationArgs NewContractArgs = new(Template, true, Parameters);
895
896 await ServiceRef.NavigationService.GoToAsync(nameof(NewContractPage), NewContractArgs, BackMethod.CurrentPage);
897 }
898 catch (Exception ex)
899 {
901 }
902 }
903
907 [RelayCommand]
908 private async Task LoadMoreTokens()
909 {
910 if (this.hasMoreTokens)
911 {
912 this.hasMoreTokens = false; // So multiple requests are not made while scrolling.
913
914 try
915 {
916 TokensEventArgs e = await ServiceRef.XmppService.GetNeuroFeatures(this.Tokens.Count, Constants.BatchSizes.TokenBatchSize);
917 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> EventsByCateogy = this.GetNotificationEvents();
918
919 MainThread.BeginInvokeOnMainThread(() =>
920 {
921 if (e.Ok)
922 {
923 if (e.Tokens is not null)
924 {
925 foreach (Token Token in e.Tokens)
926 {
927 if (!EventsByCateogy.TryGetValue(Token.TokenId, out NotificationEvent[]? Events))
928 Events = [];
929
930 this.Tokens.Add(new TokenItem(Token, Events));
931 }
932
933 this.hasMoreTokens = e.Tokens.Length == Constants.BatchSizes.TokenBatchSize;
934 }
935 }
936 });
937 }
938 catch (Exception ex)
939 {
940 ServiceRef.LogService.LogException(ex);
941 }
942 }
943 }
944
945 private Task Wallet_TokenAdded(object _, TokenEventArgs e)
946 {
948 Events = [];
949
950 MainThread.BeginInvokeOnMainThread(() =>
951 {
952 TokenItem Item = new(e.Token, Events);
953
954 if (this.Tokens.Count == 0)
955 this.Tokens.Add(Item);
956 else
957 this.Tokens.Insert(0, Item);
958 });
959
960 return Task.CompletedTask;
961 }
962
963 private Task Wallet_TokenRemoved(object _, TokenEventArgs e)
964 {
965 MainThread.BeginInvokeOnMainThread(() =>
966 {
967 int i, c = this.Tokens.Count;
968
969 for (i = 0; i < c; i++)
970 {
971 if (this.Tokens[i] is TokenItem Item && Item.TokenId == e.Token.TokenId)
972 {
973 this.Tokens.RemoveAt(i);
974 break;
975 }
976 }
977 });
978
979 return Task.CompletedTask;
980 }
981
982 private Task NotificationService_OnNewNotification(object? Sender, NotificationEventArgs e)
983 {
984 if (e.Event.Type == NotificationEventType.Wallet)
985 {
986 MainThread.BeginInvokeOnMainThread(() =>
987 {
988 if (e.Event is BalanceNotificationEvent)
989 this.NrBalanceNotifications++;
990 else if (e.Event is TokenNotificationEvent)
991 this.NrTokenNotifications++;
992 });
993 }
994
995 return Task.CompletedTask;
996 }
997
998 // Go to Apps page
999 [RelayCommand]
1000 public async Task ViewApps()
1001 {
1002 try
1003 {
1005 }
1006 catch (Exception Ex)
1007 {
1008 ServiceRef.LogService.LogException(Ex);
1009 }
1010 }
1011
1012 [RelayCommand]
1013 public async Task ViewMainPage()
1014 {
1015 try
1016 {
1018 }
1019 catch (Exception Ex)
1020 {
1021 ServiceRef.LogService.LogException(Ex);
1022 }
1023 }
1024 }
1025}
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
Wallet balance event arguments.
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:921
const int AccountEventBatchSize
Number of account events to load in a single batch.
Definition: Constants.cs:926
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 Open
Looks up a localized string similar to Open.
static string SelectServiceProviderSellEDaler
Looks up a localized string similar to Select the Service Provider you want to use to sell eDaler....
static string SelectServiceProviderBuyEDaler
Looks up a localized string similar to Select the Service Provider you want to use to buy eDaler....
static string InvalidEDalerUri
Looks up a localized string similar to Invalid eDaler URI: {0}.
static string SelectContactToPay
Looks up a localized string similar to Below are all contacts in your contact book....
static string BuyEDaler
Looks up a localized string similar to Buy eDaler..
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
static string SellEDaler
Looks up a localized string similar to Sell eDaler..
Contains information about a contact.
Definition: ContactInfo.cs:22
static async Task< string > GetFriendlyName(CaseInsensitiveString RemoteId)
Gets the friendly name of a remote identity (Legal ID or Bare JID).
Definition: ContactInfo.cs:258
override Task< Geometry > GetCategoryIcon()
Gets an icon for the category of event.
async Task< Token?> GetTokenAsync()
Gets the parsed token asynchronously, using cached XML if available.
override Task< string > GetDescription()
Gets a descriptive text for the event.
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 ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:202
static IContractOrchestratorService ContractOrchestratorService
Contract orchestrator service.
Definition: ServiceRef.cs:238
static IReportingStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:370
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:190
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.
Event arguments for callback methods to token creation attributes queries.
string TrustProviderId
Legal ID used by the trust provider to sign contracts.
decimal Commission
Minimum commission (in %) expected by the trust provider, in order to sign contract.
Event arguments for token events.
Event arguments to totals response callback methods.
TokenTotal[] Totals
Token totals, per currency.
Event arguments for Tokens responses
const string NamespaceNeuroFeatures
Namespace for Neuro-Features.
Neuro-Feature Token
Definition: Token.cs:46
decimal Value
Latest value of token
Definition: Token.cs:333
string TokenId
Token ID
Definition: Token.cs:116
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.
bool Ok
If the response is an OK result response (true), or an error response (false).
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 GoToAsync(string Route)
Navigates to the specified route and pushes the page onto the navigation stack.
Task PopToRootAsync()
Pops all pages until only the root page remains on the navigation stack.
BaseContentPage? CurrentPage
Gets the current visible view.
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.
Interface for pages that can receive contract options from an asynchronous process.
Definition: ImplTypes.g.cs:58
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:56