Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
MyContractsViewModel.cs
11using System.Collections.ObjectModel;
12using CommunityToolkit.Mvvm.ComponentModel;
14using CommunityToolkit.Mvvm.Input;
16using Waher.Script;
18using CommunityToolkit.Maui.Core.Extensions;
20
22{
27 public partial class MyContractsViewModel : BaseViewModel
28 {
29 private readonly ContractsListMode contractsListMode;
30 private readonly TaskCompletionSource<Contract?>? selection;
31 private Contract? selectedContract = null;
32 private readonly Dictionary<string, SelectableTag> tagMap = new(StringComparer.OrdinalIgnoreCase);
33
37 public event Action<SelectableTag>? TagSelected;
38
39 private readonly string AllCategory = "All";
40 private readonly int contractBatchSize = 10;
41
42 private int loadedContracts;
43 private string currentCategory;
44
48 public ObservableCollection<SelectableTag> FilterTags { get; set; } = new();
49
53 [ObservableProperty]
54 private int hasMore = 0; // 0 means collectionView will load more when scrolles. -1 means event wont be fired.
55
59 [ObservableProperty]
60 private bool canShareTemplate;
61
67 {
68 this.IsBusy = true;
69 this.Action = SelectContractAction.ViewContract;
70
71 this.loadedContracts = 0;
72 this.hasMore = 0;
73 this.currentCategory = this.AllCategory;
74
75 if (Args is not null)
76 {
77 this.contractsListMode = Args.Mode;
78 this.Action = Args.Action;
79 this.selection = Args.Selection;
80
81 switch (this.contractsListMode)
82 {
83 case ContractsListMode.Contracts:
84 this.Title = ServiceRef.Localizer[nameof(AppResources.Contracts)];
85 this.Description = ServiceRef.Localizer[nameof(AppResources.ContractsInfoText)];
86 this.CanShareTemplate = false;
87 break;
88
89 case ContractsListMode.ContractTemplates:
92 this.CanShareTemplate = true;
93 break;
94
95 case ContractsListMode.TokenCreationTemplates:
98 this.CanShareTemplate = true;
99 break;
100 }
101 }
102 }
103
105 public override async Task OnInitializeAsync()
106 {
107 await base.OnInitializeAsync();
108
109 this.IsBusy = true;
110 this.ShowContractsMissing = false;
111
112 await this.LoadCategories();
113
114 // Ensure an "All" tag exists showing total count, selected by default
115 MainThread.BeginInvokeOnMainThread(() =>
116 {
117 if (this.tagMap.TryGetValue(this.AllCategory, out SelectableTag? allTag))
118 {
119 allTag.IsSelected = true;
120 }
121 else
122 {
123 SelectableTag all = new(this.AllCategory, true);
124 this.tagMap[this.AllCategory] = all;
125 this.FilterTags.Insert(0, all);
126 }
127 });
128
129 this.currentCategory = this.AllCategory;
130
131 await this.LoadContracts();
132
133 this.ShowContractsMissing = this.Contracts.Count < 1;
134 }
135
137 public override async Task OnAppearingAsync()
138 {
139 await base.OnAppearingAsync();
140
141 if (this.selection is not null && this.selection.Task.IsCompleted)
142 {
143 await this.GoBack();
144 return;
145 }
146 }
147
149 public override async Task OnDisposeAsync()
150 {
151 if (this.Action != SelectContractAction.Select)
152 {
153 this.ShowContractsMissing = false;
154 }
155
156 this.selection?.TrySetResult(this.selectedContract);
157
158 await base.OnDisposeAsync();
159 }
160
164 [ObservableProperty]
165 private string? title;
166
170 [ObservableProperty]
171 private string? description;
172
176 [ObservableProperty]
177 private SelectContractAction action;
178
182 [ObservableProperty]
183 private bool showContractsMissing;
184
188 public ObservableCollection<ContractModel> Contracts { get; } = [];
189
193 [RelayCommand(AllowConcurrentExecutions = false)]
194 private async Task ContractSelected(object? parameter)
195 {
196 if (parameter is not ContractModel model)
197 return;
198
199 try
200 {
201 this.IsBusy = true;
202 await Task.Yield();
203 await this.ContractSelectedAsync(model);
204 }
205 catch (Exception Ex)
206 {
207 ServiceRef.LogService.LogException(Ex);
208 }
209 finally
210 {
211 this.IsBusy = false;
212 }
213 }
214
218 [RelayCommand]
219 private async Task OpenFilterPopup()
220 {
223 if (selectedTag is not null && !string.Equals(selectedTag.Category, this.currentCategory, StringComparison.OrdinalIgnoreCase))
224 await this.FilterChanged(selectedTag);
225 }
226
230 [RelayCommand(AllowConcurrentExecutions = false)]
231 private async Task FilterChanged(object? parameter)
232 {
233 if (parameter is SelectableTag Tag)
234 {
235 bool wasSelected = Tag.IsSelected;
236 if (wasSelected)
237 {
238 foreach (SelectableTag t in this.FilterTags)
239 {
240 t.IsSelected = t.Category == this.AllCategory;
241 if (string.Equals(t.Category, this.AllCategory, StringComparison.OrdinalIgnoreCase))
242 this.TagSelected?.Invoke(t);
243 }
244
245 this.currentCategory = this.AllCategory;
246 }
247 else
248 {
249 foreach (SelectableTag t in this.FilterTags)
250 t.IsSelected = (t == Tag);
251
252 this.currentCategory = Tag.Category;
253 this.TagSelected?.Invoke(Tag);
254 }
255
256 await this.ApplySearchFilter();
257 }
258 }
259
263 public void UpdateSearch(string? text)
264 {
265 string? previousCategory = this.currentCategory;
266
267 SelectableTag? selectedTag = null;
268
269 if (string.IsNullOrEmpty(text))
270 {
271 foreach (SelectableTag Tag in this.FilterTags)
272 {
273 Tag.IsSelected = false;
274 }
275 this.FilterTags[0].IsSelected = true; // Select "All"
276 selectedTag = this.FilterTags[0];
277 this.currentCategory = this.AllCategory;
278 }
279 else
280 {
281 bool found = false; // To ensure only one tag is selected
282 foreach (SelectableTag Tag in this.FilterTags)
283 {
284 if (Tag.Category.Contains(text ?? string.Empty, StringComparison.OrdinalIgnoreCase) && !found)
285 {
286 found = true;
287 Tag.IsSelected = true;
288 selectedTag = Tag;
289 this.currentCategory = Tag.Category;
290 }
291 else
292 {
293 Tag.IsSelected = false;
294 }
295 }
296 }
297
298 if (!string.Equals(previousCategory, this.currentCategory, StringComparison.OrdinalIgnoreCase))
299 {
300 if (selectedTag is not null)
301 this.TagSelected?.Invoke(selectedTag);
302
303 this.ApplySearchFilter().ConfigureAwait(false);
304 }
305 }
306
310 private async Task ApplySearchFilter()
311 {
312 await MainThread.InvokeOnMainThreadAsync(async () =>
313 {
314 this.Contracts.Clear();
315 this.loadedContracts = 0;
316 this.HasMore = 0;
317 });
318
319 await this.LoadContracts();
320 }
321
325 public async Task ContractSelectedAsync(ContractModel Model)
326 {
327 try
328 {
329 ContractReference Ref = Model.ContractRef;
330
331 if (Ref.ContractId is null)
332 {
333 bool Delete = await MainThread.InvokeOnMainThreadAsync(async () =>
339
340 if (Delete)
341 {
342 await Database.FindDelete<ContractReference>(new FilterFieldEqualTo("ContractId", Ref.ContractId)).ConfigureAwait(false);
343 await this.LoadContracts();
344 }
345 }
346
347 switch (this.Action)
348 {
349 case SelectContractAction.ViewContract:
350 if (this.contractsListMode == ContractsListMode.Contracts)
351 {
352 ViewContractNavigationArgs Args = new(Ref, false);
353 await MainThread.InvokeOnMainThreadAsync(async () =>
355 }
356 else
357 {
359 }
360 break;
361
362 case SelectContractAction.Select:
363 Contract? Contract = await Ref.GetContract().ConfigureAwait(false);
364 this.selectedContract = Contract;
365 await MainThread.InvokeOnMainThreadAsync(async () =>
366 {
367 await this.GoBack();
368 });
369 this.selection?.TrySetResult(Contract);
370 break;
371 }
372 }
373 catch (Exception Ex)
374 {
375 ServiceRef.LogService.LogException(Ex);
376 return;
377 }
378 }
379
384 {
385 _ = this.ContractSelectedAsync(Model);
386 }
387
391 [RelayCommand]
392 private async Task ShareTemplateQR(object? parameter)
393 {
394 try
395 {
396 ContractModel? Model = parameter as ContractModel;
397 if (Model is null)
398 return;
399
400 string ContractUri = Model.ContractIdUriString;
401 string ContractName = Model.Category;
402
403 if (string.IsNullOrEmpty(ContractUri))
404 return;
405
408 byte[] QrBytes = Services.UI.QR.QrCode.GeneratePng(ContractUri, Width, Height);
409
410 ShowQRPopup QrPopup = new ShowQRPopup(QrBytes, ContractUri, ContractName);
411 await ServiceRef.PopupService.PushAsync(QrPopup);
412 }
413 catch (Exception Ex)
414 {
415 ServiceRef.LogService.LogException(Ex);
416 }
417 }
418
422 private async Task LoadCategories()
423 {
424 try
425 {
426 object Categories = await Expression.EvalAsync($"select distinct Category from NeuroAccessMaui.Services.Contracts.ContractReference where IsTemplate={this.contractsListMode != ContractsListMode.Contracts}");
427
428 if (Categories is not object[] Items)
429 return;
430
431 foreach (object Item in Items)
432 {
433 if (Item is string category && !string.IsNullOrWhiteSpace(category))
434 {
435 MainThread.BeginInvokeOnMainThread(() =>
436 {
437 if (!this.tagMap.ContainsKey(category))
438 {
439 SelectableTag NewTag = new(category, false);
440 this.tagMap[category] = NewTag;
441 this.FilterTags.Add(NewTag);
442 }
443 });
444 }
445 }
446 }
447 catch (Exception Ex)
448 {
449 ServiceRef.LogService.LogException(Ex);
450 }
451 }
452
456 [RelayCommand(AllowConcurrentExecutions = false)]
457 private async Task LoadMoreContracts()
458 {
459 await this.LoadContracts();
460 }
461
465 private async Task LoadContracts()
466 {
467 try
468 {
469 IEnumerable<ContractReference>? ContractReferences = await this.LoadFromDatabase();
470
471 if (ContractReferences is null)
472 {
473 this.HasMore = -1;
474 return;
475 }
476
477 foreach (ContractReference Ref in ContractReferences)
478 {
479 ContractModel Item = new(Ref, []);
480
481 MainThread.BeginInvokeOnMainThread(() =>
482 {
483 this.Contracts.Add(Item);
484 });
485 }
486 }
487 finally
488 {
489 this.loadedContracts += this.contractBatchSize;
490 this.IsBusy = false;
491
492 if (this.HasMore == -1 && this.contractsListMode == ContractsListMode.TokenCreationTemplates)
493 {
494 foreach (string TokenTemplateId in Constants.ContractTemplates.TokenCreationTemplates)
495 {
496 ContractReference? Existing = await Database.FindFirstDeleteRest<ContractReference>(new FilterAnd(
497 new FilterFieldEqualTo("IsTemplate", true),
498 new FilterFieldEqualTo("ContractLoaded", true),
499 new FilterFieldEqualTo("ContractId", TokenTemplateId)
500 ));
501
502 if (Existing is null)
503 {
504 Contract? Contract = await ServiceRef.XmppService.GetContract(TokenTemplateId);
505 if (Contract is not null)
506 {
507 ContractReference Ref = new()
508 {
509 ContractId = Contract.ContractId
510 };
511
512 await Ref.SetContract(Contract);
513 await Database.Insert(Ref);
514
515 ContractModel Item = new ContractModel(Ref, []);
516
517 MainThread.BeginInvokeOnMainThread(() =>
518 {
519 this.Contracts.Add(Item);
520 this.OnPropertyChanged(nameof(this.ShowContractsMissing));
521 });
522 }
523 }
524 }
525 }
526 }
527 }
528
533 private async Task<IEnumerable<ContractReference>?> LoadFromDatabase()
534 {
535 IEnumerable<ContractReference> ContractReferences;
536
537 if (this.currentCategory == this.AllCategory)
538 {
539 switch (this.contractsListMode)
540 {
541 case ContractsListMode.Contracts:
542 ContractReferences = await Database.Find<ContractReference>(this.loadedContracts, this.contractBatchSize, new FilterAnd(
543 new FilterFieldEqualTo("IsTemplate", false),
544 new FilterFieldEqualTo("ContractLoaded", true)));
545
546 // If fetched amount is less than batch size, tell collectionview to not fire load more event.
547 this.HasMore = (ContractReferences.Count() < this.contractBatchSize) ? -1 : 0;
548 break;
549
550 case ContractsListMode.ContractTemplates:
551 ContractReferences = await Database.Find<ContractReference>(this.loadedContracts, this.contractBatchSize, new FilterAnd(
552 new FilterFieldEqualTo("IsTemplate", true),
553 new FilterFieldEqualTo("ContractLoaded", true)));
554
555 // If fetched amount is less than batch size, tell collectionview to not fire load more event.
556 this.HasMore = (ContractReferences.Count() < this.contractBatchSize) ? -1 : 0;
557 break;
558
559 case ContractsListMode.TokenCreationTemplates:
560 ContractReferences = await Database.Find<ContractReference>(this.loadedContracts, this.contractBatchSize, new FilterAnd(
561 new FilterFieldEqualTo("IsTemplate", true),
562 new FilterFieldEqualTo("ContractLoaded", true)));
563
564 // If fetched amount is less than batch size, tell collectionview to not fire load more event.
565 this.HasMore = (ContractReferences.Count() < this.contractBatchSize) ? -1 : 0;
566 break;
567
568 default:
569 return null;
570 }
571 }
572 else
573 {
574 switch (this.contractsListMode)
575 {
576 case ContractsListMode.Contracts:
577 ContractReferences = await Database.Find<ContractReference>(this.loadedContracts, this.contractBatchSize, new FilterAnd(
578 new FilterFieldEqualTo("IsTemplate", false),
579 new FilterFieldEqualTo("ContractLoaded", true),
580 new FilterFieldEqualTo("Category", this.currentCategory)));
581
582 // If fetched amount is less than batch size, tell collectionview to not fire load more event.
583 this.HasMore = (ContractReferences.Count() < this.contractBatchSize) ? -1 : 0;
584 break;
585
586 case ContractsListMode.ContractTemplates:
587 ContractReferences = await Database.Find<ContractReference>(this.loadedContracts, this.contractBatchSize, new FilterAnd(
588 new FilterFieldEqualTo("IsTemplate", true),
589 new FilterFieldEqualTo("ContractLoaded", true),
590 new FilterFieldEqualTo("Category", this.currentCategory)));
591
592 // If fetched amount is less than batch size, tell collectionview to not fire load more event.
593 this.HasMore = (ContractReferences.Count() < this.contractBatchSize) ? -1 : 0;
594 break;
595
596 case ContractsListMode.TokenCreationTemplates:
597 ContractReferences = await Database.Find<ContractReference>(this.loadedContracts, this.contractBatchSize, new FilterAnd(
598 new FilterFieldEqualTo("IsTemplate", true),
599 new FilterFieldEqualTo("ContractLoaded", true),
600 new FilterFieldEqualTo("Category", this.currentCategory)));
601
602 // If fetched amount is less than batch size, tell collectionview to not fire load more event.
603 this.HasMore = (ContractReferences.Count() < this.contractBatchSize) ? -1 : 0;
604 break;
605
606 default:
607 return null;
608 }
609 }
610
611 return ContractReferences;
612 }
613
617 public partial class SelectableTag : ObservableObject
618 {
622 [ObservableProperty]
623 private string category;
627 [ObservableProperty]
628 private bool isSelected;
629
635 public SelectableTag(string Category, bool IsSelected)
636 {
637 this.category = Category;
638 this.isSelected = IsSelected;
639 }
640
644 public void ToggleSelection() => this.IsSelected = !this.IsSelected;
645 }
646 }
647}
static readonly string[] TokenCreationTemplates
Array of contract templates for creating tokens.
Definition: Constants.cs:973
const int DefaultImageHeight
The default height to use when generating QR Code images.
Definition: Constants.cs:1013
const int DefaultImageWidth
The default width to use when generating QR Code images.
Definition: Constants.cs:1009
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 Yes
Looks up a localized string similar to Yes.
static string ReferencedID
Looks up a localized string similar to Referenced ID.
static string ContractCouldNotBeFound
Looks up a localized string similar to Contract could not be found, it could have been deleted....
static string Contracts
Looks up a localized string similar to Contracts.
static string ContractTemplates
Looks up a localized string similar to Contract Templates.
static string ContractTemplatesInfoText
Looks up a localized string similar to Below is a list of contract templates that you have used,...
static string ContractsInfoText
Looks up a localized string similar to Below is a list of contracts that you have created or signed,...
static string TokenCreationTemplates
Looks up a localized string similar to Token Templates.
static string No
Looks up a localized string similar to No.
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
static string TokenCreationTemplatesInfoText
Looks up a localized string similar to Below is a list of token templates that you have used,...
Contains a local reference to a contract that the user has created or signed.
async Task< Contract?> GetContract()
Gets a parsed contract.
async Task SetContract(Contract Contract)
Sets a parsed contract.
CaseInsensitiveString? ContractId
Contract reference
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 IPopupService PopupService
Popup service for presenting application popups.
Definition: ServiceRef.cs:142
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
A base class for all view models, inheriting from the BindableObject. NOTE: using this class requir...
virtual async Task GoBack()
Method called when user wants to navigate to the previous screen.
Holds navigation parameters specific to views displaying a list of contacts.
SelectContractAction Action
Action to take when a contact has been selected.
TaskCompletionSource< Contract?>? Selection
Selection source, if selecting identity.
SelectableTag(string Category, bool IsSelected)
Creates a new SelectableTag.
View model responsible for presenting and filtering a list of contracts and templates....
override async Task OnDisposeAsync()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
ObservableCollection< SelectableTag > FilterTags
Collection of available filter tags for categories. One tag can be selected at a time.
void UpdateSearch(string? text)
Should be called by page on text change.
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.
MyContractsViewModel(MyContractsNavigationArgs? Args)
Creates an instance of the MyContractsViewModel class.
ObservableCollection< ContractModel > Contracts
Holds the flat list of contracts to display.
async Task ContractSelectedAsync(ContractModel Model)
Handle contract selection.
Action< SelectableTag >? TagSelected
Event raised when a filter tag becomes selected. Consumers can react (e.g., scroll into view).
void ContractSelected(ContractModel Model)
Legacy helper maintained for compatibility with existing callers.
string ContractIdUriString
URI string for the contract ID, used for sharing and QR codes.
View model for a popup that allows users to select a contract filter tag. Uses buttons bound to comma...
Contains the definition of a contract
Definition: Contract.cs:22
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:21
static Task< IEnumerable< object > > FindDelete(string Collection, params string[] SortOrder)
Finds objects in a given collection and deletes them in the same atomic operation.
Definition: Database.cs:1442
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:238
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:97
This filter selects objects that conform to all child-filters provided.
Definition: FilterAnd.cs:10
This filter selects objects that have a named field equal to a given value.
Class managing a script expression.
Definition: Expression.cs:41
static Task< object > EvalAsync(string Script)
Evaluates script, in string format.
Definition: Expression.cs:5946
Task OpenContract(string ContractId, string Purpose, Dictionary< CaseInsensitiveString, object >? ParameterValues)
Downloads the specified Contract and opens the corresponding page in the app to show it.
Task GoToAsync(string Route)
Navigates to the specified route and pushes the page onto the navigation stack.
Task< bool > DisplayAlert(string Title, string Message, string? Accept=null, string? Cancel=null)
Displays an alert/message box to the user.
Definition: ImplTypes.g.cs:58
BackMethod
Navigation Back Method
Definition: BackMethod.cs:7
SelectContractAction
Actions to take when a contact has been selected.