Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
TransactionHistoryViewModel.cs
1using System.Collections.ObjectModel;
2using CommunityToolkit.Mvvm.ComponentModel;
3using CommunityToolkit.Mvvm.Input;
4using EDaler;
5using EDaler.Events; // Added for BalanceEventArgs
9using Microsoft.Maui.Graphics; // Added for Colors
10
11// Alias to avoid conflict with NeuroAccessMaui.UI.Pages.Wallet.AccountEvent namespace
12using AccountEventModel = EDaler.AccountEvent;
14
16{
21 {
22 private DateTime? lastLoadedOldestTimestamp;
23 private string? currency; // Currency for events (taken from balance)
24
28 private readonly List<TransactionEventItem> allEvents = new();
29
34 {
35 this.Events = new ObservableCollection<TransactionEventItem>();
36 }
37
41 public ObservableCollection<TransactionEventItem> Events { get; }
42
43 [ObservableProperty]
44 private bool isLoading;
45
46 [ObservableProperty]
47 private bool hasMore;
48
49 [ObservableProperty]
50 private string? searchText;
51
52 partial void OnSearchTextChanged(string? value)
53 {
54 this.ApplyFilter();
55 }
56
58 public override async Task OnInitializeAsync()
59 {
60 await base.OnInitializeAsync();
61 ServiceRef.XmppService.EDalerBalanceUpdated += this.Wallet_BalanceUpdated;
62 await this.LoadInitialAsync();
63 }
64
66 public override async Task OnDisposeAsync()
67 {
68 ServiceRef.XmppService.EDalerBalanceUpdated -= this.Wallet_BalanceUpdated;
69 await base.OnDisposeAsync();
70 }
71
72 private async Task EnsureCurrencyAsync()
73 {
74 if (!string.IsNullOrEmpty(this.currency))
75 return;
76
77 try
78 {
79 Balance Balance = await ServiceRef.XmppService.GetEDalerBalance();
80 this.currency = Balance.Currency;
81 }
82 catch (Exception Ex)
83 {
84 ServiceRef.LogService.LogException(Ex);
85 this.currency = string.Empty;
86 }
87 }
88
89 private async Task LoadInitialAsync()
90 {
91 if (this.IsLoading)
92 return;
93
94 this.IsLoading = true;
95 try
96 {
97 await this.EnsureCurrencyAsync();
98 this.Events.Clear();
99 this.allEvents.Clear();
100 this.lastLoadedOldestTimestamp = null;
101
102 await ServiceRef.XmppService.WaitForConnectedState(Constants.Timeouts.XmppConnect);
103 (AccountEventModel[] Events, bool More) = await ServiceRef.XmppService.GetEDalerAccountEvents(Constants.BatchSizes.AccountEventBatchSize);
104 await this.AddEventsAsync(Events);
105 this.HasMore = More;
106 }
107 catch (Exception Ex)
108 {
109 ServiceRef.LogService.LogException(Ex);
110 }
111 finally
112 {
113 this.IsLoading = false;
114 }
115 }
116
117 private async Task AddEventsAsync(AccountEventModel[] eventsBatch)
118 {
119 if (eventsBatch is null || eventsBatch.Length == 0)
120 return;
121
122 Dictionary<string, string> FriendlyNames = new();
123 string CurrencyLocal = this.currency ?? string.Empty;
124
125 // Events are expected newest->oldest. Maintain order in UI newest first.
126 foreach (AccountEventModel Evt in eventsBatch)
127 {
128 string? Type = null;
129 string Remote = Evt.Remote;
130 if (!FriendlyNames.TryGetValue(Remote, out string? Friendly))
131 {
132 Friendly = await ContactInfo.GetFriendlyName(Remote);
133
134 string[] FriendlyParts = Friendly.Split("@");
135
136 Type = ServiceRef.Localizer[nameof(AppResources.Transfer)].Value;
137
138 Friendly = FriendlyParts[0];
139 FriendlyNames[Remote] = Friendly;
140 }
141
142 TransactionEventItem Item = new(Evt, Friendly, CurrencyLocal, Type);
143 // Insert at end since service provides descending blocks already preserving global order.
144 this.Events.Add(Item);
145 // Store in backing collection
146 this.allEvents.Add(Item);
147
148 if (!this.lastLoadedOldestTimestamp.HasValue || Evt.Timestamp < this.lastLoadedOldestTimestamp.Value)
149 this.lastLoadedOldestTimestamp = Evt.Timestamp;
150 }
151 }
152
153 private void ApplyFilter()
154 {
155 string? Filter = this.SearchText;
156 IEnumerable<TransactionEventItem> Query = this.allEvents;
157
158 if (!string.IsNullOrWhiteSpace(Filter))
159 {
160 string FilterLower = Filter.Trim().ToLowerInvariant();
161 Query = Query.Where(e => (e.FriendlyName ?? string.Empty).ToLowerInvariant().Contains(FilterLower));
162 }
163
164 // Update observable collection efficiently
165 List<TransactionEventItem> NewItems = Query.ToList();
166 // Simple refresh strategy (dataset relatively small per page)
167 this.Events.Clear();
168 foreach (TransactionEventItem Item in NewItems)
169 this.Events.Add(Item);
170 }
171
172 [RelayCommand]
173 private async Task Refresh()
174 {
175 await this.LoadInitialAsync();
176 }
177
178 [RelayCommand]
179 private async Task LoadMore()
180 {
181 if (this.IsLoading || !this.HasMore || !this.lastLoadedOldestTimestamp.HasValue)
182 return;
183
184 this.IsLoading = true;
185 try
186 {
187 (AccountEventModel[] Events, bool More) = await ServiceRef.XmppService.GetEDalerAccountEvents(Constants.BatchSizes.AccountEventBatchSize, this.lastLoadedOldestTimestamp.Value);
188 await this.AddEventsAsync(Events);
189 this.HasMore = More;
190 this.ApplyFilter();
191 }
192 catch (Exception Ex)
193 {
194 ServiceRef.LogService.LogException(Ex);
195 }
196 finally
197 {
198 this.IsLoading = false;
199 }
200 }
201
205 private async Task Wallet_BalanceUpdated(object? sender, BalanceEventArgs e)
206 {
207 try
208 {
209 await this.EnsureCurrencyAsync();
210 AccountEventModel? Evt = e.Balance?.Event;
211 if (Evt is null)
212 return;
213
214 string Remote = Evt.Remote;
215 string Friendly = await ContactInfo.GetFriendlyName(Remote);
216 string[] FriendlyParts = Friendly.Split('@');
217 Friendly = FriendlyParts[0];
218 string TransactionType = ServiceRef.Localizer[nameof(AppResources.Transfer)].Value;
219 string CurrencyLocal = this.currency ?? string.Empty;
220 TransactionEventItem Item = new(Evt, Friendly, CurrencyLocal, TransactionType);
221
222 // Insert at beginning of backing list (newest first)
223 this.allEvents.Insert(0, Item);
224
225 bool passesFilter = true;
226 if (!string.IsNullOrWhiteSpace(this.SearchText))
227 {
228 string filterLower = this.SearchText.Trim().ToLowerInvariant();
229 passesFilter = (Item.FriendlyName ?? string.Empty).ToLowerInvariant().Contains(filterLower);
230 }
231
232 MainThread.BeginInvokeOnMainThread(() =>
233 {
234 if (passesFilter)
235 {
236 this.Events.Insert(0, Item);
237 }
238 // Update oldest timestamp tracking
239 if (!this.lastLoadedOldestTimestamp.HasValue || Evt.Timestamp < this.lastLoadedOldestTimestamp.Value)
240 this.lastLoadedOldestTimestamp = Evt.Timestamp;
241 });
242 }
243 catch (Exception Ex)
244 {
245 ServiceRef.LogService.LogException(Ex);
246 }
247 }
248
252 [RelayCommand]
253 private Task Back()
254 {
255 return this.GoBack();
256 }
257 }
258}
Account event
Definition: AccountEvent.cs:16
CaseInsensitiveString Remote
Remote party
Definition: AccountEvent.cs:56
Contains information about a balance.
Definition: Balance.cs:11
CaseInsensitiveString Currency
Currency of amount.
Definition: Balance.cs:54
AccountEvent Event
Any account event associated to the balance message.
Definition: Balance.cs:59
Wallet balance event arguments.
const int AccountEventBatchSize
Number of account events to load in a single batch.
Definition: Constants.cs:926
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 Transfer
Looks up a localized string similar to Transfer.
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
Base class that references services in the app.
Definition: ServiceRef.cs:43
static ILogService LogService
Log service.
Definition: ServiceRef.cs:214
static IReportingStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:370
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:190
virtual async Task GoBack()
Method called when user wants to navigate to the previous screen.
View model for the Transaction History page. Loads and pages through eDaler account events.
TransactionHistoryViewModel()
Creates a new instance of the TransactionHistoryViewModel class.
ObservableCollection< TransactionEventItem > Events
Collection of transaction events (filtered).
override async Task OnInitializeAsync()
Method called when view is initialized for the first time. Use this method to implement registration ...
override async Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
A view model that holds the XMPP state.
partial class TransactionEventItem(AccountEventModel accountEvent, string friendlyName, string currency, string? transactionType)
Presentation model for a transaction event in history (no notification support).