1using CommunityToolkit.Mvvm.ComponentModel;
2using CommunityToolkit.Mvvm.Input;
6using Microsoft.Maui.Controls.Shapes;
41 private DateTime lastEDalerEvent;
42 private DateTime lastTokenEvent;
43 private bool hasMoreTokens;
44 private bool hasTotals;
45 private bool hasTokens;
48 public override async Task OnInitializeAsync()
50 await base.OnInitializeAsync();
52 this.EDalerFrontGlyph =
"https://" + ServiceRef.TagProfile.Domain +
"/Images/eDalerFront200.png";
53 this.EDalerBackGlyph =
"https://" + ServiceRef.TagProfile.Domain +
"/Images/eDalerBack200.png";
55 if (this.navigationArguments is not
null)
57 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
59 await this.AssignProperties(this.navigationArguments.Balance,
this.navigationArguments.PendingAmount,
60 this.navigationArguments.PendingCurrency,
this.navigationArguments.PendingPayments,
this.navigationArguments.Events,
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;
71 public override async Task OnAppearingAsync()
73 await base.OnAppearingAsync();
86 await this.LoadTokens(
true);
90 public override async Task OnDisposeAsync()
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;
97 await base.OnDisposeAsync();
100 private SortedDictionary<CaseInsensitiveString, NotificationEvent[]> GetNotificationEvents()
117 this.NrBalanceNotifications = NrBalance;
118 this.NrTokenNotifications = NrToken;
123 private async Task AssignProperties(
Balance?
Balance, decimal PendingAmount,
string? PendingCurrency,
136 this.lastEDalerEvent = LastEvent;
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;
144 Dictionary<string, string> FriendlyNames = [];
145 string? FriendlyName;
147 ObservableItemGroup<IUniqueItem> NewPaymentItems =
new(nameof(this.PaymentItems), []);
149 if (PendingPayments is not
null)
151 List<IUniqueItem> NewPendingPayments =
new(PendingPayments.Length);
155 if (!FriendlyNames.TryGetValue(Payment.To, out FriendlyName))
158 FriendlyNames[Payment.To] = FriendlyName;
161 NewPendingPayments.Add(
new PendingPaymentItem(Payment, FriendlyName));
164 if (NewPendingPayments.Count > 0)
165 NewPaymentItems.Add(
new ObservableItemGroup<IUniqueItem>(nameof(PendingPaymentItem), NewPendingPayments));
168 if (Events is not
null)
170 List<IUniqueItem> NewAccountEvents =
new(Events.Length);
174 if (!FriendlyNames.TryGetValue(Event.Remote, out FriendlyName))
177 FriendlyNames[Event.Remote] = FriendlyName;
180 if (!NotificationEvents.TryGetValue(Event.TransactionId.ToString(), out
NotificationEvent[]? CategoryEvents))
183 NewAccountEvents.Add(
new AccountEventItem(Event,
this, FriendlyName, CategoryEvents));
186 if (NewAccountEvents.Count > 0)
187 NewPaymentItems.Add(
new ObservableItemGroup<IUniqueItem>(nameof(AccountEventItem), NewAccountEvents));
190 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.PaymentItems, NewPaymentItems));
195 Task.Run(() => this.ReloadEDalerWallet(e.
Balance));
196 return Task.CompletedTask;
205 IUniqueItem? OldItems = this.PaymentItems.FirstOrDefault(el =>
string.Equals(el.UniqueName, nameof(AccountEventItem), StringComparison.Ordinal));
209 (OldItems is ObservableItemGroup<IUniqueItem> OldAccountEvents) &&
210 (OldAccountEvents.LastOrDefault() is AccountEventItem OldLastEvent) &&
212 (OldLastEvent.Timestamp < NewLastEvent.Timestamp))
227 for (
int i = 0; i < Events2.Length; i++)
230 AllEvents.Add(Event);
232 if (OldLastEvent.Timestamp.Equals(Event.
Timestamp))
242 AllEvents.AddRange(Events2);
246 Events = [.. AllEvents];
249 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
251 MainThread.BeginInvokeOnMainThread(async () => await this.AssignProperties(
Balance, PendingAmount, PendingCurrency,
272 private decimal amount;
278 private string? currency;
284 private bool hasPending;
290 private bool isFrontViewShowing;
296 private decimal pendingAmount;
302 private string? pendingCurrency;
308 private decimal reservedAmount;
314 private DateTime timestamp;
320 private string? eDalerFrontGlyph;
326 private string? eDalerBackGlyph;
332 private bool hasEvents;
338 private bool hasMoreEvents;
344 private int nrBalanceNotifications;
350 private int nrTokenNotifications;
355 public ObservableItemGroup<IUniqueItem> PaymentItems {
get; } =
new(nameof(PaymentItems), []);
360 public ObservableItemGroup<IUniqueItem> Tokens {
get; } =
new(nameof(Tokens), []);
365 public ObservableItemGroup<IUniqueItem> Totals {
get; } =
new(nameof(Totals), []);
375 return this.GoBack();
382 private static async Task ScanQrCode()
384 await Services.UI.QR.QrCode.ScanQrCodeAndHandleResult();
390 [RelayCommand(CanExecute = nameof(IsConnected))]
391 private async Task RequestPayment()
397 if (ServiceProviders.Length == 0)
405 List<IBuyEDalerServiceProvider> ServiceProviders2 = [];
407 ServiceProviders2.AddRange(ServiceProviders);
408 ServiceProviders2.Add(
new EmptyBuyEDalerServiceProvider());
410 ServiceProvidersNavigationArgs e =
new(ServiceProviders2.ToArray(),
426 else if (
string.IsNullOrEmpty(
ServiceProvider.BuyEDalerTemplateContractId))
428 TaskCompletionSource<decimal?> Result =
new();
433 decimal? Amount = await Result.Task;
435 if (Amount.HasValue && Amount.Value > 0)
438 Amount.Value,
this.Balance?.Currency);
446 Dictionary<CaseInsensitiveString, object> Parameters =
new()
448 {
"Visibility",
"CreatorAndParts" },
450 {
"Currency", this.Balance?.
Currency ?? e2.Currency },
451 {
"TrustProvider", e2.TrustProviderId }
458 IDictionary<CaseInsensitiveString, object>[] Options = await
OptionsTransaction.Wait();
461 MainThread.BeginInvokeOnMainThread(async () => await ContractOptionsPage.ShowContractOptions(Options));
489 [RelayCommand(CanExecute = nameof(IsConnected))]
490 private async Task MakePayment()
496 if (ServiceProviders.Length == 0)
500 CanScanQrCode =
true,
501 AllowAnonymous =
true,
509 List<ISellEDalerServiceProvider> ServiceProviders2 = [];
511 ServiceProviders2.AddRange(ServiceProviders);
512 ServiceProviders2.Add(
new EmptySellEDalerServiceProvider());
514 ServiceProvidersNavigationArgs e =
new(ServiceProviders2.ToArray(),
528 CanScanQrCode =
true,
529 AllowAnonymous =
true,
535 else if (
string.IsNullOrEmpty(
ServiceProvider.SellEDalerTemplateContractId))
537 TaskCompletionSource<decimal?> Result =
new();
542 decimal? Amount = await Result.Task;
544 if (Amount.HasValue && Amount.Value > 0)
547 Amount.Value,
this.Balance?.Currency);
555 Dictionary<CaseInsensitiveString, object> Parameters =
new()
557 {
"Visibility",
"CreatorAndParts" },
558 {
"Role",
"Seller" },
559 {
"Currency", this.Balance?.
Currency ?? e2.Currency },
560 {
"TrustProvider", e2.TrustProviderId }
567 IDictionary<CaseInsensitiveString, object>[] Options = await
OptionsTransaction.Wait();
570 MainThread.BeginInvokeOnMainThread(async () => await ContractOptionsPage.ShowContractOptions(Options));
586 private async Task ShowPaymentItem(
object Item)
588 if (Item is PendingPaymentItem PendingItem)
599 else if (Item is AccountEventItem EventItem)
607 private async Task LoadMoreAccountEvents()
609 if (this.HasMoreEvents)
611 this.HasMoreEvents =
false;
617 IUniqueItem? OldItems = this.PaymentItems.FirstOrDefault(el =>
string.Equals(el.UniqueName, nameof(AccountEventItem), StringComparison.Ordinal));
619 if (OldItems is
null)
623 ObservableItemGroup<IUniqueItem> OldAccountEvents = (ObservableItemGroup<IUniqueItem>)OldItems;
625 if (OldAccountEvents.LastOrDefault() is AccountEventItem LastEvent)
631 if (Events is not
null)
633 List<IUniqueItem> NewAccountEvents = [];
634 Dictionary<string, string> FriendlyNames = [];
635 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> NotificationEvents = this.GetNotificationEvents();
639 if (!FriendlyNames.TryGetValue(Event.Remote, out
string? FriendlyName))
642 FriendlyNames[Event.Remote] = FriendlyName;
645 if (!NotificationEvents.TryGetValue(Event.TransactionId.ToString(), out
NotificationEvent[]? CategoryEvents))
648 NewAccountEvents.Add(
new AccountEventItem(Event,
this, FriendlyName, CategoryEvents));
651 MainThread.BeginInvokeOnMainThread(() =>
654 if (OldItems is ObservableItemGroup<IUniqueItem> SubItems)
661 this.PaymentItems.Add(
new ObservableItemGroup<IUniqueItem>(nameof(AccountEventItem), NewAccountEvents));
662 this.HasMoreEvents = More;
677 public async
void BindTokens()
681 await this.LoadTokens(
false);
689 private async Task LoadTokens(
bool Reload)
693 if (!this.hasTotals || Reload)
695 this.hasTotals =
true;
703 ObservableItemGroup<IUniqueItem> NewTotals =
new(nameof(this.Totals), []);
705 if (tteArgs.
Totals is not
null)
709 NewTotals.Add(
new TokenTotalItem(Total));
713 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.Totals, NewTotals));
716 this.hasTotals = tteArgs.
Ok;
720 this.hasTotals =
false;
725 if (!this.hasTokens || Reload)
727 this.hasTokens =
true;
731 SortedDictionary<CaseInsensitiveString, TokenNotificationEvent[]> NotificationEvents =
735 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> EventsByCateogy = this.GetNotificationEvents();
737 ObservableItemGroup<IUniqueItem> NewTokens =
new(nameof(this.Tokens), []);
738 List<TokenNotificationEvent> ToDelete = [];
747 if (
Token is not
null)
751 if (
Token is not
null)
764 NewTokens.Add(
new EventModel(TokenEvent.Received, Icon, Description, TokenEvent));
768 ToDelete.Add(TokenEvent);
774 if (ToDelete.Count > 0)
779 if (teArgs.Tokens is not
null)
795 MainThread.BeginInvokeOnMainThread(() => ObservableItemGroup<IUniqueItem>.UpdateGroupsItems(this.Tokens, NewTokens));
798 this.hasTokens = teArgs?.Ok ??
false;
802 this.hasTokens =
false;
808 internal void ViewsFlipped(
bool IsFrontViewShowing)
810 this.IsFrontViewShowing = IsFrontViewShowing;
817 private async Task CreateToken()
821 TaskCompletionSource<Contract?> TemplateSelection =
new();
826 Contract? Template = await TemplateSelection.Task;
827 if (Template is
null)
830 Dictionary<CaseInsensitiveString, object> Parameters = [];
836 XmlDocument Doc =
new()
838 PreserveWhitespace =
true
842 XmlNamespaceManager NamespaceManager =
new(Doc.NameTable);
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;
851 if (Template.
Parts is
null)
853 List<Part> Parts = [];
855 if (!
string.IsNullOrEmpty(CreatorRole))
864 if (!
string.IsNullOrEmpty(TrustProviderRole))
869 Role = TrustProviderRole
873 Template.Parts = [.. Parts];
882 else if (
Part.
Role == TrustProviderRole)
887 if (!
string.IsNullOrEmpty(CurrencyParameter))
888 Parameters[CurrencyParameter] = e2.
Currency;
890 if (!
string.IsNullOrEmpty(CommissionParameter))
891 Parameters[CommissionParameter] = e2.
Commission;
894 NewContractNavigationArgs NewContractArgs =
new(Template,
true, Parameters);
908 private async Task LoadMoreTokens()
910 if (this.hasMoreTokens)
912 this.hasMoreTokens =
false;
917 SortedDictionary<CaseInsensitiveString, NotificationEvent[]> EventsByCateogy = this.GetNotificationEvents();
919 MainThread.BeginInvokeOnMainThread(() =>
923 if (e.Tokens is not null)
925 foreach (Token Token in e.Tokens)
927 if (!EventsByCateogy.TryGetValue(Token.TokenId, out NotificationEvent[]? Events))
930 this.Tokens.Add(new TokenItem(Token, Events));
933 this.hasMoreTokens = e.Tokens.Length == Constants.BatchSizes.TokenBatchSize;
950 MainThread.BeginInvokeOnMainThread(() =>
954 if (this.Tokens.Count == 0)
955 this.Tokens.Add(Item);
957 this.Tokens.Insert(0, Item);
960 return Task.CompletedTask;
965 MainThread.BeginInvokeOnMainThread(() =>
967 int i, c = this.Tokens.Count;
969 for (i = 0; i < c; i++)
973 this.Tokens.RemoveAt(i);
979 return Task.CompletedTask;
986 MainThread.BeginInvokeOnMainThread(() =>
989 this.NrBalanceNotifications++;
991 this.NrTokenNotifications++;
995 return Task.CompletedTask;
1000 public async Task ViewApps()
1006 catch (Exception Ex)
1013 public async Task ViewMainPage()
1019 catch (Exception Ex)
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.
Wallet balance event arguments.
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.
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 an incoming chat message.
Abstract base class for token notification events.
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.
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 INavigationService NavigationService
The navigation service for navigating between pages.
static INotificationService NotificationService
Service for managing notifications for the user.
static ITagProfile TagProfile
TAG Profile service.
static IContractOrchestratorService ContractOrchestratorService
Contract orchestrator service.
static IReportingStringLocalizer Localizer
Localization 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.
Main page for viewing apps.
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.
Event arguments for callback methods to token creation attributes queries.
string TrustProviderId
Legal ID used by the trust provider to sign contracts.
string Currency
Default currency used by the broker
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.
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.
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.
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.