1using CommunityToolkit.Mvvm.ComponentModel;
2using CommunityToolkit.Mvvm.Input;
5using Microsoft.Maui.Controls.Shapes;
37 private DateTime lastEDalerEvent;
38 private DateTime lastTokenEvent;
39 private bool hasMoreTokens;
40 private bool hasTotals;
41 private bool hasTokens;
44 protected override async Task OnInitialize()
46 await base.OnInitialize();
48 this.EDalerFrontGlyph =
"https://" + ServiceRef.TagProfile.Domain +
"/Images/eDalerFront200.png";
49 this.EDalerBackGlyph =
"https://" + ServiceRef.TagProfile.Domain +
"/Images/eDalerBack200.png";
51 if (this.navigationArguments is not
null)
53 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
55 await this.AssignProperties(this.navigationArguments.Balance,
this.navigationArguments.PendingAmount,
56 this.navigationArguments.PendingCurrency,
this.navigationArguments.PendingPayments,
this.navigationArguments.Events,
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;
67 protected override async Task OnAppearing()
69 await base.OnAppearing();
82 await this.LoadTokens(
true);
86 protected override async Task OnDispose()
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;
93 await base.OnDispose();
96 private SortedDictionary<CaseInsensitiveString, NotificationEvent[]> GetNotificationEvents()
113 this.NrBalanceNotifications = NrBalance;
114 this.NrTokenNotifications = NrToken;
119 private async Task AssignProperties(
Balance?
Balance, decimal PendingAmount,
string? PendingCurrency,
132 this.lastEDalerEvent = LastEvent;
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;
140 Dictionary<string, string> FriendlyNames = [];
141 string? FriendlyName;
143 ObservableItemGroup<IUniqueItem> NewPaymentItems =
new(nameof(this.PaymentItems), []);
145 if (PendingPayments is not
null)
147 List<IUniqueItem> NewPendingPayments =
new(PendingPayments.Length);
151 if (!FriendlyNames.TryGetValue(Payment.To, out FriendlyName))
154 FriendlyNames[Payment.To] = FriendlyName;
157 NewPendingPayments.Add(
new PendingPaymentItem(Payment, FriendlyName));
160 if (NewPendingPayments.Count > 0)
161 NewPaymentItems.Add(
new ObservableItemGroup<IUniqueItem>(nameof(PendingPaymentItem), NewPendingPayments));
164 if (Events is not
null)
166 List<IUniqueItem> NewAccountEvents =
new(Events.Length);
170 if (!FriendlyNames.TryGetValue(Event.Remote, out FriendlyName))
173 FriendlyNames[Event.Remote] = FriendlyName;
176 if (!NotificationEvents.TryGetValue(Event.TransactionId.ToString(), out
NotificationEvent[]? CategoryEvents))
179 NewAccountEvents.Add(
new AccountEventItem(Event,
this, FriendlyName, CategoryEvents));
182 if (NewAccountEvents.Count > 0)
183 NewPaymentItems.Add(
new ObservableItemGroup<IUniqueItem>(nameof(AccountEventItem), NewAccountEvents));
186 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.PaymentItems, NewPaymentItems));
189 private Task Wallet_BalanceUpdated(
object? Sender, BalanceEventArgs e)
191 Task.Run(() => this.ReloadEDalerWallet(e.Balance));
192 return Task.CompletedTask;
201 IUniqueItem? OldItems = this.PaymentItems.FirstOrDefault(el =>
string.Equals(el.UniqueName, nameof(AccountEventItem), StringComparison.Ordinal));
205 (OldItems is ObservableItemGroup<IUniqueItem> OldAccountEvents) &&
206 (OldAccountEvents.LastOrDefault() is AccountEventItem OldLastEvent) &&
208 (OldLastEvent.Timestamp < NewLastEvent.Timestamp))
223 for (
int i = 0; i < Events2.Length; i++)
226 AllEvents.Add(Event);
228 if (OldLastEvent.Timestamp.Equals(Event.
Timestamp))
238 AllEvents.AddRange(Events2);
242 Events = [.. AllEvents];
245 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
247 MainThread.BeginInvokeOnMainThread(async () => await this.AssignProperties(
Balance, PendingAmount, PendingCurrency,
268 private decimal amount;
274 private string? currency;
280 private bool hasPending;
286 private bool isFrontViewShowing;
292 private decimal pendingAmount;
298 private string? pendingCurrency;
304 private decimal reservedAmount;
310 private DateTime timestamp;
316 private string? eDalerFrontGlyph;
322 private string? eDalerBackGlyph;
328 private bool hasEvents;
334 private bool hasMoreEvents;
340 private int nrBalanceNotifications;
346 private int nrTokenNotifications;
351 public ObservableItemGroup<IUniqueItem> PaymentItems {
get; } =
new(nameof(PaymentItems), []);
356 public ObservableItemGroup<IUniqueItem> Tokens {
get; } =
new(nameof(Tokens), []);
361 public ObservableItemGroup<IUniqueItem> Totals {
get; } =
new(nameof(Totals), []);
371 return this.GoBack();
378 private static async Task ScanQrCode()
380 await Services.UI.QR.QrCode.ScanQrCodeAndHandleResult();
386 [RelayCommand(CanExecute = nameof(IsConnected))]
387 private async Task RequestPayment()
393 if (ServiceProviders.Length == 0)
401 List<IBuyEDalerServiceProvider> ServiceProviders2 = [];
403 ServiceProviders2.AddRange(ServiceProviders);
404 ServiceProviders2.Add(
new EmptyBuyEDalerServiceProvider());
406 ServiceProvidersNavigationArgs e =
new(ServiceProviders2.ToArray(),
422 else if (
string.IsNullOrEmpty(
ServiceProvider.BuyEDalerTemplateContractId))
424 TaskCompletionSource<decimal?> Result =
new();
429 decimal? Amount = await Result.Task;
431 if (Amount.HasValue && Amount.Value > 0)
434 Amount.Value,
this.Balance?.Currency);
442 Dictionary<CaseInsensitiveString, object> Parameters =
new()
444 {
"Visibility",
"CreatorAndParts" },
446 {
"Currency", this.Balance?.
Currency ?? e2.Currency },
447 {
"TrustProvider", e2.TrustProviderId }
454 IDictionary<CaseInsensitiveString, object>[] Options = await
OptionsTransaction.Wait();
457 MainThread.BeginInvokeOnMainThread(async () => await ContractOptionsPage.ShowContractOptions(Options));
485 [RelayCommand(CanExecute = nameof(IsConnected))]
486 private async Task MakePayment()
492 if (ServiceProviders.Length == 0)
496 CanScanQrCode =
true,
497 AllowAnonymous =
true,
505 List<ISellEDalerServiceProvider> ServiceProviders2 = [];
507 ServiceProviders2.AddRange(ServiceProviders);
508 ServiceProviders2.Add(
new EmptySellEDalerServiceProvider());
510 ServiceProvidersNavigationArgs e =
new(ServiceProviders2.ToArray(),
524 CanScanQrCode =
true,
525 AllowAnonymous =
true,
531 else if (
string.IsNullOrEmpty(
ServiceProvider.SellEDalerTemplateContractId))
533 TaskCompletionSource<decimal?> Result =
new();
538 decimal? Amount = await Result.Task;
540 if (Amount.HasValue && Amount.Value > 0)
543 Amount.Value,
this.Balance?.Currency);
551 Dictionary<CaseInsensitiveString, object> Parameters =
new()
553 {
"Visibility",
"CreatorAndParts" },
554 {
"Role",
"Seller" },
555 {
"Currency", this.Balance?.
Currency ?? e2.Currency },
556 {
"TrustProvider", e2.TrustProviderId }
563 IDictionary<CaseInsensitiveString, object>[] Options = await
OptionsTransaction.Wait();
566 MainThread.BeginInvokeOnMainThread(async () => await ContractOptionsPage.ShowContractOptions(Options));
582 private async Task ShowPaymentItem(
object Item)
584 if (Item is PendingPaymentItem PendingItem)
595 else if (Item is AccountEventItem EventItem)
603 private async Task LoadMoreAccountEvents()
605 if (this.HasMoreEvents)
607 this.HasMoreEvents =
false;
613 IUniqueItem? OldItems = this.PaymentItems.FirstOrDefault(el =>
string.Equals(el.UniqueName, nameof(AccountEventItem), StringComparison.Ordinal));
615 if (OldItems is
null)
619 ObservableItemGroup<IUniqueItem> OldAccountEvents = (ObservableItemGroup<IUniqueItem>)OldItems;
621 if (OldAccountEvents.LastOrDefault() is AccountEventItem LastEvent)
627 if (Events is not
null)
629 List<IUniqueItem> NewAccountEvents = [];
630 Dictionary<string, string> FriendlyNames = [];
631 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
635 if (!FriendlyNames.TryGetValue(Event.Remote, out
string? FriendlyName))
638 FriendlyNames[Event.Remote] = FriendlyName;
641 if (!NotificationEvents.TryGetValue(Event.TransactionId.ToString(), out
NotificationEvent[]? CategoryEvents))
644 NewAccountEvents.Add(
new AccountEventItem(Event,
this, FriendlyName, CategoryEvents));
647 MainThread.BeginInvokeOnMainThread(() =>
650 if (OldItems is ObservableItemGroup<IUniqueItem> SubItems)
657 this.PaymentItems.Add(
new ObservableItemGroup<IUniqueItem>(nameof(AccountEventItem), NewAccountEvents));
658 this.HasMoreEvents = More;
673 public async
void BindTokens()
677 await this.LoadTokens(
false);
685 private async Task LoadTokens(
bool Reload)
689 if (!this.hasTotals || Reload)
691 this.hasTotals =
true;
699 ObservableItemGroup<IUniqueItem> NewTotals =
new(nameof(this.Totals), []);
701 if (tteArgs.Totals is not
null)
705 NewTotals.Add(
new TokenTotalItem(Total));
709 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.Totals, NewTotals));
712 this.hasTotals = tteArgs.Ok;
716 this.hasTotals =
false;
721 if (!this.hasTokens || Reload)
723 this.hasTokens =
true;
727 SortedDictionary<CaseInsensitiveString, TokenNotificationEvent[]> NotificationEvents =
731 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> EventsByCateogy = this.GetNotificationEvents();
733 ObservableItemGroup<IUniqueItem> NewTokens =
new(nameof(this.Tokens), []);
734 List<TokenNotificationEvent> ToDelete = [];
743 if (
Token is not
null)
747 if (
Token is not
null)
760 NewTokens.Add(
new EventModel(TokenEvent.Received, Icon, Description, TokenEvent));
764 ToDelete.Add(TokenEvent);
770 if (ToDelete.Count > 0)
775 if (teArgs.Tokens is not
null)
791 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.Tokens, NewTokens));
794 this.hasTokens = teArgs?.Ok ??
false;
798 this.hasTokens =
false;
804 internal void ViewsFlipped(
bool IsFrontViewShowing)
806 this.IsFrontViewShowing = IsFrontViewShowing;
813 private async Task CreateToken()
817 TaskCompletionSource<Contract?> TemplateSelection =
new();
822 Contract? Template = await TemplateSelection.Task;
823 if (Template is
null)
826 Dictionary<CaseInsensitiveString, object> Parameters = [];
832 XmlDocument Doc =
new()
834 PreserveWhitespace =
true
838 XmlNamespaceManager NamespaceManager =
new(Doc.NameTable);
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;
847 if (Template.
Parts is
null)
849 List<Part> Parts = [];
851 if (!
string.IsNullOrEmpty(CreatorRole))
860 if (!
string.IsNullOrEmpty(TrustProviderRole))
864 LegalId = e2.TrustProviderId,
865 Role = TrustProviderRole
869 Template.Parts = [.. Parts];
878 else if (
Part.
Role == TrustProviderRole)
879 Part.LegalId = e2.TrustProviderId;
883 if (!
string.IsNullOrEmpty(CurrencyParameter))
884 Parameters[CurrencyParameter] = e2.Currency;
886 if (!
string.IsNullOrEmpty(CommissionParameter))
887 Parameters[CommissionParameter] = e2.Commission;
890 NewContractNavigationArgs NewContractArgs =
new(Template,
true, Parameters);
904 private async Task LoadMoreTokens()
906 if (this.hasMoreTokens)
908 this.hasMoreTokens =
false;
913 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> EventsByCateogy = this.GetNotificationEvents();
915 MainThread.BeginInvokeOnMainThread(() =>
919 if (e.Tokens is not null)
921 foreach (Token Token in e.Tokens)
923 if (!EventsByCateogy.TryGetValue(Token.TokenId, out NotificationEvent[]? Events))
926 this.Tokens.Add(new TokenItem(Token, Events));
929 this.hasMoreTokens = e.Tokens.Length == Constants.BatchSizes.TokenBatchSize;
941 private Task Wallet_TokenAdded(
object _, TokenEventArgs e)
946 MainThread.BeginInvokeOnMainThread(() =>
950 if (this.Tokens.Count == 0)
951 this.Tokens.Add(Item);
953 this.Tokens.Insert(0, Item);
956 return Task.CompletedTask;
959 private Task Wallet_TokenRemoved(
object _, TokenEventArgs e)
961 MainThread.BeginInvokeOnMainThread(() =>
963 int i, c = this.Tokens.Count;
965 for (i = 0; i < c; i++)
969 this.Tokens.RemoveAt(i);
975 return Task.CompletedTask;
982 MainThread.BeginInvokeOnMainThread(() =>
985 this.NrBalanceNotifications++;
987 this.NrTokenNotifications++;
DateTime Timestamp
Timestamp of transaction
Contains information about a balance.
CaseInsensitiveString Currency
Currency of amount.
decimal Amount
Amount at given point in time.
decimal Reserved
Reserved amount, that the user cannot use directly.
DateTime Timestamp
Timestamp of balance.
Contains information about a pending payment.
Represents a transaction in the eDaler network.
Abstract base class for eDaler URIs
const int TokenBatchSize
Number of tokens to load in a single batch.
const int AccountEventBatchSize
Number of account events to load in a single batch.
A set of never changing property constants and helpful values.
Contains information about an incoming chat message.
Abstract base class for token notification events.
override Task< Geometry > GetCategoryIcon()
Gets an icon for the category of event.
override Task< string > GetDescription()
Gets a descriptive text for the event.
Contains information about a token that has been removed.
Base class that references services in the app.
static ILogService LogService
Log service.
static IUiService UiService
Service serializing and managing UI-related tasks.
static INotificationService NotificationService
Service for managing notifications for the user.
static ITagProfile TagProfile
TAG Profile service.
static IStringLocalizer Localizer
Localization service
static IContractOrchestratorService ContractOrchestratorService
Contract orchestrator service.
static IXmppService XmppService
The XMPP service for XMPP communication.
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.
Encapsulates a Token object.
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 page that allows the user to view its tokens.
A view model that holds the XMPP state.
const string NamespaceNeuroFeatures
Namespace for Neuro-Features.
decimal Value
Latest value of token
Contains one token total, i.e. sum of token values, for a given currency.
Contains the definition of a contract
string ForMachinesLocalName
Local name used by the root node of the machine-readable contents of the contract (ForMachines).
XmlElement ForMachines
Machine-readable contents of the contract.
Part[] Parts
Defined parts for the smart contract.
string ForMachinesNamespace
Namespace used by the root node of the machine-readable contents of the contract (ForMachines).
Class defining a part in a contract
string Role
Role of the part in the contract
Contains information about a service provider.
string Type
Type of service provider.
string Id
ID of 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.
Page CurrentPage
Current page
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
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.
ContractsListMode
What list of contracts to display
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.
ContractVisibility
Visibility types for contracts.