Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
ContactListViewModel.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using EDaler;
3using EDaler.Uris;
9using System.Collections.ObjectModel;
10using System.Globalization;
11using System.Text;
12using System.ComponentModel;
19using CommunityToolkit.Mvvm.Input;
21
23{
27 public partial class ContactListViewModel : BaseViewModel
28 {
29 private readonly Dictionary<CaseInsensitiveString, List<ContactInfoModel>> byBareJid;
30 private readonly TaskCompletionSource<ContactInfoModel?>? selection;
31 private readonly ContactListNavigationArgs? navigationArguments;
32
38 {
39 this.navigationArguments = Args;
40 this.Contacts = [];
41 this.byBareJid = [];
42
43 if (Args is not null)
44 {
45 this.Description = Args.Description;
46 this.Action = Args.Action;
47 this.selection = Args.Selection;
48 this.CanScanQrCode = Args.CanScanQrCode;
49 this.AllowAnonymous = Args.AllowAnonymous;
50 this.AnonymousText = string.IsNullOrEmpty(Args.AnonymousText) ?
51 ServiceRef.Localizer[nameof(AppResources.Anonymous)] : Args.AnonymousText;
52 }
53 else
54 {
55 this.Description = ServiceRef.Localizer[nameof(AppResources.ContactsDescription)];
56 this.Action = SelectContactAction.ViewIdentity;
57 this.selection = null;
58 }
59 }
60
62 protected override async Task OnInitialize()
63 {
64 await base.OnInitialize();
65
66 await this.UpdateContactList(this.navigationArguments?.Contacts);
67
68 ServiceRef.XmppService.OnPresence += this.Xmpp_OnPresence;
69 ServiceRef.NotificationService.OnNewNotification += this.NotificationService_OnNewNotification;
70 ServiceRef.NotificationService.OnNotificationsDeleted += this.NotificationService_OnNotificationsDeleted;
71 }
72
74 protected override async Task OnAppearing()
75 {
76 await base.OnAppearing();
77
78 if (this.selection is not null && this.selection.Task.IsCompleted)
79 {
80 await this.GoBack();
81 return;
82 }
83
84 this.SelectedContact = null;
85 }
86
87 private async Task UpdateContactList(IEnumerable<ContactInfo>? Contacts)
88 {
89 SortedDictionary<CaseInsensitiveString, ContactInfo> Sorted = [];
90 Dictionary<CaseInsensitiveString, bool> Jids = [];
91
92 Contacts ??= await Database.Find<ContactInfo>();
93
94 foreach (ContactInfo Info in Contacts)
95 {
96 Jids[Info.BareJid] = true;
97
98 if (Info.IsThing.HasValue && Info.IsThing.Value) // Include those with IsThing=null
99 continue;
100
101 if (Info.AllowSubscriptionFrom.HasValue && !Info.AllowSubscriptionFrom.Value)
102 continue;
103
104 Add(Sorted, Info.FriendlyName, Info);
105 }
106
107 foreach (RosterItem Item in ServiceRef.XmppService.Roster)
108 {
109 if (Jids.ContainsKey(Item.BareJid))
110 continue;
111
112 ContactInfo Info = new()
113 {
114 BareJid = Item.BareJid,
115 FriendlyName = Item.NameOrBareJid,
116 IsThing = null
117 };
118
119 await Database.Insert(Info);
120
121 Add(Sorted, Info.FriendlyName, Info);
122 }
123
124 NotificationEvent[]? Events;
125
126 this.Contacts.Clear();
127 this.ShowContactsMissing = Sorted.Count == 0;
128
130 {
132 {
133 if (Sorted.TryGetValue(Category, out ContactInfo? Info))
134 Sorted.Remove(Category);
135 else
136 {
137 Info = await ContactInfo.FindByBareJid(Category);
138
139 if (Info is not null)
140 Remove(Sorted, Info.FriendlyName, Info);
141 else
142 {
143 Info = new()
144 {
145 BareJid = Category,
146 FriendlyName = Category,
147 IsThing = null
148 };
149 }
150 }
151
152 this.Contacts.Add(new ContactInfoModel(Info, Events));
153 }
154 }
155
156 foreach (ContactInfo Info in Sorted.Values)
157 {
159 Events = [];
160
161 this.Contacts.Add(new ContactInfoModel(Info, Events));
162 }
163
164 this.byBareJid.Clear();
165
166 foreach (ContactInfoModel? Contact in this.Contacts)
167 {
168 if (string.IsNullOrEmpty(Contact?.BareJid))
169 continue;
170
171 if (!this.byBareJid.TryGetValue(Contact.BareJid, out List<ContactInfoModel>? Contacts2))
172 {
173 Contacts2 = [];
174 this.byBareJid[Contact.BareJid] = Contacts2;
175 }
176
177 Contacts2.Add(Contact);
178 }
179 }
180
181 private static void Add(SortedDictionary<CaseInsensitiveString, ContactInfo> Sorted, CaseInsensitiveString Name, ContactInfo Info)
182 {
183 if (Sorted.ContainsKey(Name))
184 {
185 int i = 1;
186 string Suffix;
187
188 do
189 {
190 Suffix = " " + (++i).ToString(CultureInfo.InvariantCulture);
191 }
192 while (Sorted.ContainsKey(Name + Suffix));
193
194 Sorted[Name + Suffix] = Info;
195 }
196 else
197 Sorted[Name] = Info;
198 }
199
200 private static void Remove(SortedDictionary<CaseInsensitiveString, ContactInfo> Sorted, CaseInsensitiveString Name, ContactInfo Info)
201 {
202 int i = 1;
203 string Suffix = string.Empty;
204
205 while (Sorted.TryGetValue(Name + Suffix, out ContactInfo? Info2))
206 {
207 if (Info2.BareJid == Info.BareJid &&
208 Info2.SourceId == Info.SourceId &&
209 Info2.Partition == Info.Partition &&
210 Info2.NodeId == Info.NodeId &&
211 Info2.LegalId == Info.LegalId)
212 {
213 Sorted.Remove(Name + Suffix);
214
215 i++;
216 string Suffix2 = " " + i.ToString(CultureInfo.InvariantCulture);
217
218 while (Sorted.TryGetValue(Name + Suffix2, out Info2))
219 {
220 Sorted[Name + Suffix] = Info2;
221 Sorted.Remove(Name + Suffix2);
222
223 i++;
224 Suffix2 = " " + i.ToString(CultureInfo.InvariantCulture);
225 }
226
227 return;
228 }
229
230 i++;
231 Suffix = " " + i.ToString(CultureInfo.InvariantCulture);
232 }
233 }
234
236 protected override Task OnDispose()
237 {
238 ServiceRef.XmppService.OnPresence -= this.Xmpp_OnPresence;
239 ServiceRef.NotificationService.OnNewNotification -= this.NotificationService_OnNewNotification;
240 ServiceRef.NotificationService.OnNotificationsDeleted -= this.NotificationService_OnNotificationsDeleted;
241
242 if (this.Action != SelectContactAction.Select)
243 {
244 this.ShowContactsMissing = false;
245 this.Contacts.Clear();
246 }
247
248 this.selection?.TrySetResult(this.SelectedContact);
249
250 return base.OnDispose();
251 }
252
256 [ObservableProperty]
257 private bool showContactsMissing;
258
262 [ObservableProperty]
263 private string? description;
264
268 [ObservableProperty]
269 private bool canScanQrCode;
270
274 [ObservableProperty]
275 private bool allowAnonymous;
276
280 [ObservableProperty]
281 private string? anonymousText;
282
286 [ObservableProperty]
287 private SelectContactAction? action;
288
292 public ObservableCollection<ContactInfoModel?> Contacts { get; }
293
297 [ObservableProperty]
298 private ContactInfoModel? selectedContact;
299
301 protected override void OnPropertyChanged(PropertyChangedEventArgs e)
302 {
303 base.OnPropertyChanged(e);
304
305 switch (e.PropertyName)
306 {
307 case nameof(this.SelectedContact):
308 ContactInfoModel? Contact = this.SelectedContact;
309
310 if (Contact is not null)
311 {
312 MainThread.BeginInvokeOnMainThread(async () =>
313 {
314 this.IsOverlayVisible = true;
315
316 try
317 {
318 switch (this.Action)
319 {
320 case SelectContactAction.MakePayment:
321 StringBuilder sb = new();
322
323 sb.Append("edaler:");
324
325 if (ServiceRef.TagProfile.LegalIdentity is null)
326 {
327 sb.Append("f=");
328 sb.Append(ServiceRef.XmppService.BareJid);
329 }
330 else
331 {
332 sb.Append("fi=");
333 sb.Append(ServiceRef.TagProfile.LegalIdentity.Id);
334 }
335
336 if (!string.IsNullOrEmpty(Contact.LegalId))
337 {
338 sb.Append(";ti=");
339 sb.Append(Contact.LegalId);
340 }
341 else if (!string.IsNullOrEmpty(Contact.BareJid))
342 {
343 sb.Append(";t=");
344 sb.Append(Contact.BareJid);
345 }
346
347 Balance Balance = await ServiceRef.XmppService.GetEDalerBalance();
348
349 sb.Append(";cu=");
350 sb.Append(Balance.Currency);
351
352 if (!EDalerUri.TryParse(sb.ToString(), out EDalerUri Parsed))
353 break;
354
355 EDalerUriNavigationArgs Args = new(Parsed);
356 // Inherit the back method here from the parrent
357 await ServiceRef.UiService.GoToAsync(nameof(PaymentPage), Args, BackMethod.Pop2);
358
359 break;
360
361 case SelectContactAction.ViewIdentity:
362 default:
363 if (Contact.LegalIdentity is not null)
364 {
365 ViewIdentityNavigationArgs ViewIdentityArgs = new(Contact.LegalIdentity);
366
367 await ServiceRef.UiService.GoToAsync(nameof(ViewIdentityPage), ViewIdentityArgs);
368 }
369 else if (!string.IsNullOrEmpty(Contact.LegalId))
370 {
371 await ServiceRef.ContractOrchestratorService.OpenLegalIdentity(Contact.LegalId,
372 ServiceRef.Localizer[nameof(AppResources.ScannedQrCode)]);
373 }
374 else if (!string.IsNullOrEmpty(Contact.BareJid) && Contact.Contact is not null)
375 {
376 ChatNavigationArgs ChatArgs = new(Contact.Contact);
377 await ServiceRef.UiService.GoToAsync(nameof(ChatPage), ChatArgs, BackMethod.Inherited, Contact.BareJid);
378 }
379
380 break;
381
382 case SelectContactAction.Select:
383 this.SelectedContact = Contact;
384 await this.GoBack();
385 this.selection?.TrySetResult(Contact);
386 break;
387 }
388 }
389 finally
390 {
391 this.IsOverlayVisible = false;
392 }
393 });
394 }
395 break;
396 }
397 }
398
402 [RelayCommand]
403 private async Task ScanQrCode()
404 {
405 string? Code = await QrCode.ScanQrCode(ServiceRef.Localizer[nameof(AppResources.ScanQRCode)], [Constants.UriSchemes.IotId]);
406 if (string.IsNullOrEmpty(Code))
407 return;
408
410 {
411 this.SelectedContact = new ContactInfoModel(new ContactInfo()
412 {
413 LegalId = Constants.UriSchemes.RemoveScheme(Code)
414 });
415 }
416 else if (!string.IsNullOrEmpty(Code))
417 {
418 await ServiceRef.UiService.DisplayAlert(ServiceRef.Localizer[nameof(AppResources.ErrorTitle)],
419 ServiceRef.Localizer[nameof(AppResources.TheSpecifiedCodeIsNotALegalIdentity)]);
420 }
421 }
422
426 [RelayCommand]
427 private void Anonymous()
428 {
429 this.SelectedContact = new ContactInfoModel(null, []);
430 }
431
432 private Task Xmpp_OnPresence(object? Sender, PresenceEventArgs e)
433 {
434 if (this.byBareJid.TryGetValue(e.FromBareJID, out List<ContactInfoModel>? Contacts))
435 {
436 foreach (ContactInfoModel Contact in Contacts)
437 Contact.PresenceUpdated();
438 }
439
440 return Task.CompletedTask;
441 }
442
443 private void NotificationService_OnNewNotification(object? Sender, NotificationEventArgs e)
444 {
445 if (e.Event.Type == NotificationEventType.Contacts)
446 this.UpdateNotifications(e.Event.Category ?? string.Empty);
447 }
448
449 private void UpdateNotifications()
450 {
452 this.UpdateNotifications(Category);
453 }
454
455 private void UpdateNotifications(CaseInsensitiveString Category)
456 {
457 if (this.byBareJid.TryGetValue(Category, out List<ContactInfoModel>? Contacts))
458 {
460 Events = [];
461
462 foreach (ContactInfoModel Contact in Contacts)
463 Contact.NotificationsUpdated(Events);
464 }
465 }
466
467 private void NotificationService_OnNotificationsDeleted(object? Sender, NotificationEventsArgs e)
468 {
469 Dictionary<CaseInsensitiveString, bool> Categories = [];
470
471 foreach (NotificationEvent Event in e.Events)
472 Categories[Event.Category ?? string.Empty] = true;
473
474 foreach (CaseInsensitiveString Category in Categories.Keys)
475 this.UpdateNotifications(Category);
476 }
477 }
478}
Contains information about a balance.
Definition: Balance.cs:11
CaseInsensitiveString Currency
Currency of amount.
Definition: Balance.cs:54
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 bool StartsWithIdScheme(string Url)
Checks if the specified code starts with the IoT ID scheme.
Definition: Constants.cs:177
static ? string RemoveScheme(string Url)
Removes the URI Schema from an URL.
Definition: Constants.cs:218
const string IotId
The IoT ID URI Scheme (iotid)
Definition: Constants.cs:99
A set of never changing property constants and helpful values.
Definition: Constants.cs:7
Contains information about a contact.
Definition: ContactInfo.cs:21
bool? AllowSubscriptionFrom
Allow subscriptions from this contact
Definition: ContactInfo.cs:145
static Task< ContactInfo > FindByBareJid(string BareJid)
Finds information about a contact, given its Bare JID.
Definition: ContactInfo.cs:220
CaseInsensitiveString LegalId
Legal ID of contact.
Definition: ContactInfo.cs:71
CaseInsensitiveString BareJid
Bare JID of contact.
Definition: ContactInfo.cs:62
Base class that references services in the app.
Definition: ServiceRef.cs:31
static IUiService UiService
Service serializing and managing UI-related tasks.
Definition: ServiceRef.cs:55
static INotificationService NotificationService
Service for managing notifications for the user.
Definition: ServiceRef.cs:211
static ITagProfile TagProfile
TAG Profile service.
Definition: ServiceRef.cs:79
static IStringLocalizer Localizer
Localization service
Definition: ServiceRef.cs:235
static IContractOrchestratorService ContractOrchestratorService
Contract orchestrator service.
Definition: ServiceRef.cs:115
static IXmppService XmppService
The XMPP service for XMPP communication.
Definition: ServiceRef.cs:67
Helper class to perform scanning of QR Codes by displaying the UI and handling async results.
Definition: QrCode.cs:17
static async Task< string?> ScanQrCode(string? QrTitle, string[] AllowedSchemas)
Navigates to the Scan QR Code Page, waits for scan to complete, and returns the result....
Definition: QrCode.cs:159
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.
A page that displays a list of the current user's contacts.
Contact Information model, including related notification information.
CaseInsensitiveString? LegalId
Legal ID of contact.
ContactInfo? Contact
Contact Information object in database.
CaseInsensitiveString? BareJid
Bare JID of contact.
Holds navigation parameters specific to views displaying a list of contacts.
SelectContactAction? Action
Action to take when a contact has been selected.
TaskCompletionSource< ContactInfoModel?>? Selection
Selection source, if selecting identity.
bool AllowAnonymous
If user is allowed to select an Anonymous option.
The view model to bind to when displaying the list of contacts.
override async Task OnAppearing()
Method called when view is appearing on the screen.
ContactListViewModel(ContactListNavigationArgs? Args)
Creates an instance of the ContactListViewModel class.
ObservableCollection< ContactInfoModel?> Contacts
Holds the list of contacts to display.
override async Task OnInitialize()
Method called when view is initialized for the first time. Use this method to implement registration ...
override Task OnDispose()
Method called when the view is disposed, and will not be used more. Use this method to unregister eve...
A page to display when the user wants to view an identity.
Holds navigation parameters specific to eDaler URIs.
A page that allows the user to realize payments.
Maintains information about an item in the roster.
Definition: RosterItem.cs:75
string NameOrBareJid
Returns the name of the contact, or the Bare JID, if there's no name provided.
Definition: RosterItem.cs:436
string BareJid
Bare JID of the roster item.
Definition: RosterItem.cs:276
Represents a case-insensitive string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
Definition: Database.cs:19
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
Definition: Database.cs:247
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
Definition: Database.cs:95
bool TryGetNotificationEvents(NotificationEventType Type, CaseInsensitiveString Category, [NotNullWhen(true)] out NotificationEvent[]? Events)
Tries to get available notification events.
CaseInsensitiveString[] GetCategories(NotificationEventType Type)
Gets available categories for a button.
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.
abstract class NotificationEvent()
Abstract base class of notification events.
class NotificationEventsArgs(NotificationEvent[] Events)
Event argument for notification events.
class NotificationEventArgs(NotificationEvent Event)
Event argument for notification events.
NotificationEventType
Button on which event is to be displayed.
BackMethod
Navigation Back Method
Definition: BackMethod.cs:7
SelectContactAction
Actions to take when a contact has been selected.
delegate string ToString(IElement Element)
Delegate for callback methods that convert an element value to a string.