Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
WalletViewModel.cs
1using System.Collections.ObjectModel;
2using System.ComponentModel;
3using System.Text;
4using System.Xml;
5using CommunityToolkit.Mvvm.ComponentModel;
6using CommunityToolkit.Mvvm.Input;
7using EDaler;
8using EDaler.Events;
9using EDaler.Uris;
10using Microsoft.Extensions.DependencyInjection;
11using Microsoft.Maui.Controls.Shapes;
35using NeuroFeatures;
42
44{
48 public partial class WalletViewModel : XmppViewModel
49 {
50 private readonly IAuthenticationService authenticationService = ServiceRef.Provider.GetRequiredService<IAuthenticationService>();
51
52 #region Observable Properties
53
57 [ObservableProperty]
58 [NotifyPropertyChangedFor(nameof(BalanceDecimal))]
59 [NotifyPropertyChangedFor(nameof(BalanceString))]
60 [NotifyPropertyChangedFor(nameof(ReservedDecimal))]
61 [NotifyPropertyChangedFor(nameof(ReservedString))]
62 [NotifyPropertyChangedFor(nameof(HasReserved))]
63 [NotifyPropertyChangedFor(nameof(Currency))]
64 Balance? fetchedBalance;
65
69 [ObservableProperty]
70 private bool isRefreshingBalance;
71
75 [ObservableProperty]
76 private DateTime? balanceUpdated = ServiceRef.TagProfile.LastEDalerBalanceUpdate;
77
81 [ObservableProperty]
82 private bool showBottomNavigation = true;
83
84 #endregion
85
86 #region Computed Properties
87
91 public string Currency => this.FetchedBalance?.Currency ?? "NC";
92
96 public decimal BalanceDecimal => this.FetchedBalance?.Amount ?? ServiceRef.TagProfile.LastEDalerBalanceDecimal;
97
101 public string BalanceString => this.BalanceDecimal + " " + this.Currency;
102
106 public decimal ReservedDecimal => this.FetchedBalance?.Reserved ?? -1;
107
111 public string ReservedString => this.ReservedDecimal == -1
113 : this.ReservedDecimal + " " + this.Currency;
114
118 public bool HasReserved => (this.ReservedDecimal > 0 || this.ReservedDecimal == -1) && this.GetBalanceTask.IsSucceeded;
119
120 #endregion
121
122 #region Tasks & Refresh Policy
123
127 public ObservableTask<bool> GetBalanceTask { get; } = new();
128
129 #endregion
130
131 #region Lifecycle
132
134 public override async Task OnInitializeAsync()
135 {
136 await base.OnInitializeAsync();
137
138 // Uncomment if event handlers are needed
139 ServiceRef.XmppService.EDalerBalanceUpdated += this.Wallet_BalanceUpdated;
140 // ServiceRef.XmppService.NeuroFeatureAdded += this.Wallet_TokenAdded;
141 // ServiceRef.XmppService.NeuroFeatureRemoved += this.Wallet_TokenRemoved;
142 // ServiceRef.NotificationService.OnNewNotification += this.NotificationService_OnNewNotification;
143 }
144
146 public override async Task OnAppearingAsync()
147 {
148 await base.OnAppearingAsync();
149 // Re-load on appearing.
150 this.GetBalanceTask.Load(this.LoadBalanceAsync);
151 }
152
154 public override async Task OnDisposeAsync()
155 {
156 // Uncomment if event handlers are subscribed in OnInitialize.
157 // ServiceRef.XmppService.EDalerBalanceUpdated -= this.Wallet_BalanceUpdated;
158 // ServiceRef.XmppService.NeuroFeatureAdded -= this.Wallet_TokenAdded;
159 // ServiceRef.XmppService.NeuroFeatureRemoved -= this.Wallet_TokenRemoved;
160 // ServiceRef.NotificationService.OnNewNotification -= this.NotificationService_OnNewNotification;
161
162 ServiceRef.XmppService.EDalerBalanceUpdated -= this.Wallet_BalanceUpdated;
163
164 await base.OnDisposeAsync();
165 }
166
167 #endregion
168
169 #region Property Change Logic
170
172 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
173 {
174 base.OnPropertyChanged(e);
175
176 if (e.PropertyName == nameof(this.IsConnected))
177 {
178 this.BuyEdalerCommand.NotifyCanExecuteChanged();
179 this.SendEdalerCommand.NotifyCanExecuteChanged();
180 }
181 }
182
183 #endregion
184
185 #region Commands
186
190 [RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(IsConnected))]
191 private async Task BuyEdaler()
192 {
193 try
194 {
195 IBuyEDalerServiceProvider[] ServiceProviders = await ServiceRef.XmppService.GetServiceProvidersForBuyingEDalerAsync();
196 Balance Balance = await ServiceRef.XmppService.GetEDalerBalance();
197
198 // 2. If no providers, navigate to RequestPaymentPage.
199 if (ServiceProviders.Length == 0)
200 {
202 await ServiceRef.NavigationService.GoToAsync(nameof(RequestPaymentPage), Args, BackMethod.CurrentPage);
203 return;
204 }
205
206 // 3. Compose list of providers for selection UI.
207 List<IBuyEDalerServiceProvider> ServiceProviders2 = [];
208 ServiceProviders2.AddRange(ServiceProviders);
209 ServiceProviders2.Add(new EmptyBuyEDalerServiceProvider());
210
211 // Todo: Change to ServiceProviders2 IF you want to include from user. We now have a button for "Request" on wallet page.
212 ServiceProvidersNavigationArgs SelectionArgs = new(
213 ServiceProviders.ToArray(),
216 );
217
218 await ServiceRef.NavigationService.GoToAsync(nameof(ServiceProvidersPage), SelectionArgs, BackMethod.Pop);
219 if (SelectionArgs.ServiceProvider is null)
220 return;
221
222 IBuyEDalerServiceProvider? ServiceProvider = (IBuyEDalerServiceProvider?)(await SelectionArgs.ServiceProvider.Task);
223 if (ServiceProvider is null)
224 return;
225
226 // 4. User authentication if required.
227 if (!await this.authenticationService.AuthenticateUserAsync(AuthenticationPurpose.ApplyForOrganizationalId))
228 return;
229
230 // 5. No ID: go to RequestPaymentPage.
231 if (string.IsNullOrEmpty(ServiceProvider.Id))
232 {
234 await ServiceRef.NavigationService.GoToAsync(nameof(RequestPaymentPage), Args, BackMethod.CurrentPage);
235 }
236 // 6. No contract template: manual amount input flow.
237 else if (string.IsNullOrEmpty(ServiceProvider.BuyEDalerTemplateContractId))
238 {
239 TaskCompletionSource<decimal?> Result = new();
240 BuyEDalerNavigationArgs Args = new(Balance?.Currency, Result);
241
242 await ServiceRef.NavigationService.GoToAsync(nameof(BuyEDalerPage), Args, BackMethod.CurrentPage);
243
244 decimal? Amount = await Result.Task;
245 if (!Amount.HasValue)
246 return;
247
248 if (Amount.Value > 0)
249 {
250 PaymentTransaction Transaction = await ServiceRef.XmppService.InitiateBuyEDaler(
252
253 Amount = await Transaction.Wait();
254
255 if (Amount.HasValue && Amount.Value > 0)
256 {
257 ServiceRef.TagProfile.HasWallet = true;
258 }
259 }
260 }
261 // 7. Contract-based buy flow.
262 else
263 {
264 CreationAttributesEventArgs e2 = await ServiceRef.XmppService.GetNeuroFeatureCreationAttributes();
265 Dictionary<CaseInsensitiveString, object> Parameters = new()
266 {
267 { "Visibility", "CreatorAndParts" },
268 { "Role", "Buyer" },
269 { "Currency", Balance?.Currency ?? e2.Currency },
270 { "TrustProvider", e2.TrustProviderId }
271 };
272
273 await ServiceRef.ContractOrchestratorService.OpenContract(
274 ServiceProvider.BuyEDalerTemplateContractId,
276 Parameters
277 );
278
280 await ServiceRef.XmppService.InitiateBuyEDalerGetOptions(ServiceProvider.Id, ServiceProvider.Type);
281
282 IDictionary<CaseInsensitiveString, object>[] Options = await OptionsTransaction.Wait();
283
284 // Show contract options if UI is in a contract page.
286 MainThread.BeginInvokeOnMainThread(async () => await ContractOptionsPage.ShowContractOptions(Options));
287 }
288 }
289 catch (Exception Ex)
290 {
291 ServiceRef.LogService.LogException(Ex);
293 }
294 }
295
299 [RelayCommand(AllowConcurrentExecutions = false, CanExecute = nameof(IsConnected))]
300 private async Task SendEdaler()
301 {
302 try
303 {
304 // Prepare a base eDaler URI with current currency. Recipient & amount will be set on SendPaymentPage.
305 Balance Balance = await ServiceRef.XmppService.GetEDalerBalance();
306 StringBuilder Sb = new();
307 Sb.Append("edaler:");
308 Sb.Append("cu=");
309 Sb.Append(Balance.Currency);
310
311 if (!EDalerUri.TryParse(Sb.ToString(), out EDalerUri Parsed))
312 return;
313
314 TaskCompletionSource<string?> UriToSend = new();
315 TaskCompletionSource<string?> MessageToSend = new();
316 EDalerUriNavigationArgs Args = new(Parsed, string.Empty, UriToSend, MessageToSend);
318
319 string? Uri = await UriToSend.Task; // User composed URI. Null if cancelled.
320 string? Message = await MessageToSend.Task; // Optional message.
321 if (string.IsNullOrEmpty(Uri))
322 return;
323
324 // Automatically claim/process the payment (execute transfer) directly.
325 // await ServiceRef.NeuroWalletOrchestratorService.OpenEDalerUri(Uri);
326 EDaler.Transaction Transaction = await ServiceRef.XmppService.SendEDalerUri(Uri);
327
328 PaymentSuccessPopup Popup = new(Transaction, Message);
329 await ServiceRef.PopupService.PushAsync(Popup);
330 }
331 catch (Exception Ex)
332 {
333 ServiceRef.LogService.LogException(Ex);
335 }
336 }
337
338 [RelayCommand]
339 public async Task ViewMainPage()
340 {
341 try
342 {
343 if (Application.Current?.MainPage?.Navigation != null)
344 await Application.Current.MainPage.Navigation.PopToRootAsync();
345 }
346 catch (Exception Ex)
347 {
348 ServiceRef.LogService.LogException(Ex);
349 }
350 }
351
352 [RelayCommand]
353 public async Task ViewApps()
354 {
355 try
356 {
358 }
359 catch (Exception Ex)
360 {
361 ServiceRef.LogService.LogException(Ex);
362 }
363 }
364
365 [RelayCommand]
366 public async Task ViewRequestPayment()
367 {
368 try
369 {
370 EDalerBalanceNavigationArgs Args = new(this.FetchedBalance);
372 }
373 catch (Exception Ex)
374 {
375 ServiceRef.LogService.LogException(Ex);
376 }
377 }
378
382 [RelayCommand]
383 private async Task OpenTransactionHistory()
384 {
385 try
386 {
388 }
389 catch (Exception Ex)
390 {
391 ServiceRef.LogService.LogException(Ex);
393 }
394 }
395
396 #endregion
397
398 #region Private Methods
399
404 private async Task LoadBalanceAsync(TaskContext<bool> Ctx)
405 {
406 ServiceRef.LogService.LogDebug("Refreshing Edaler...");
407
408 if (!await ServiceRef.XmppService.WaitForConnectedState(Constants.Timeouts.XmppConnect))
409 return;
410
411 Balance CurrentBalance = await ServiceRef.XmppService.GetEDalerBalance();
412
413 MainThread.BeginInvokeOnMainThread(() =>
414 {
415 this.FetchedBalance = CurrentBalance;
416 this.BalanceUpdated = DateTime.Now;
417 ServiceRef.TagProfile.LastEDalerBalanceDecimal = this.BalanceDecimal;
418 ServiceRef.TagProfile.LastEDalerBalanceUpdate = DateTime.Now;
419 });
420
421 ServiceRef.LogService.LogDebug("Refreshing Edaler Completed");
422 }
423
427 private async Task Wallet_BalanceUpdated(object? sender, BalanceEventArgs e)
428 {
429 await MainThread.InvokeOnMainThreadAsync(() =>
430 {
431 this.FetchedBalance = e.Balance;
432 this.BalanceUpdated = DateTime.Now;
433 ServiceRef.TagProfile.LastEDalerBalanceDecimal = this.BalanceDecimal;
434 ServiceRef.TagProfile.LastEDalerBalanceUpdate = DateTime.Now;
435 });
436 }
437
438 #endregion
439 }
440}
Contains information about a balance.
Definition: Balance.cs:11
CaseInsensitiveString Currency
Currency of amount.
Definition: Balance.cs:54
Wallet balance event arguments.
Represents a transaction in the eDaler network.
Definition: Transaction.cs:36
Abstract base class for eDaler URIs
Definition: EDalerUri.cs:14
static bool TryParse(string Uri, out EDalerUri Result)
Tries to parse an eDaler URI
Definition: EDalerUri.cs:192
static readonly TimeSpan XmppConnect
XMPP Connect timeout
Definition: Constants.cs:702
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 UnknownPleaseRefresh
Looks up a localized string similar to Unknown, please refresh.
static string SelectServiceProviderBuyEDaler
Looks up a localized string similar to Select the Service Provider you want to use to buy eDaler....
static string BuyEDaler
Looks up a localized string similar to Buy eDaler..
Base class that references services in the app.
Definition: ServiceRef.cs:43
static IServiceProvider Provider
The service provider for the app. This is set before the app is started, and will be used to resolve ...
Definition: ServiceRef.cs:48
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 IPopupService PopupService
Popup service for presenting application popups.
Definition: ServiceRef.cs:142
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
Provides a data-binding friendly mechanism to manage and report the status of asynchronous operations...
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.
ViewModel for the Wallet page, handling balance display, buy flow, and related navigation.
string BalanceString
Balance formatted with currency.
ObservableTask< bool > GetBalanceTask
Wraps the balance fetch operation for async UI binding.
override async Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
override void OnPropertyChanged(PropertyChangedEventArgs e)
override async Task OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
override async Task OnAppearingAsync()
Method called when view is appearing on the screen.
decimal BalanceDecimal
Exposes the current balance as a decimal.
bool HasReserved
If there is a reserved amount. Only show once balance fetch is complete.
A page that displays information about eDaler received.
A page that allows the user to realize payments.
A view model that holds the XMPP state.
Event arguments for callback methods to token creation attributes queries.
Contains information about a service provider.
Interface for information about a service provider that users can use to buy eDaler.
Task GoToAsync(string Route)
Navigates to the specified route and pushes the page onto 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.
Interface for pages that can receive contract options from an asynchronous process.
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.
AuthenticationPurpose
Purpose for requesting the user to authenticate itself.