1using CommunityToolkit.Mvvm.ComponentModel;
9using System.Collections.ObjectModel;
10using System.Globalization;
12using System.ComponentModel;
19using CommunityToolkit.Mvvm.Input;
25using System.Runtime.CompilerServices;
31 public char Group {
get;
set; }
32 public ObservableCollection<ContactInfoModel?> Contacts {
get;
set; }
46 private readonly Dictionary<CaseInsensitiveString, List<ContactInfoModel>> byBareJid;
47 private readonly TaskCompletionSource<ContactInfoModel?>? selection;
62 private bool isViewMode =
false;
64 private string? searchText;
70 get => this.searchText;
73 if (this.searchText != value)
75 this.searchText = value;
78 this.searchFilterTask.Run();
89 this.navigationArguments = Args;
100 this.AnonymousText =
string.IsNullOrEmpty(Args.
AnonymousText) ?
107 this.selection =
null;
111 this.IsViewMode =
true;
115 .Named(
"Contacts Search Filter")
116 .WithPolicy(
Policies.Debounce(TimeSpan.FromMilliseconds(300)))
121 await MainThread.InvokeOnMainThreadAsync(this.FilterContacts);
127 .Named(
"Contacts Loader")
132 await this.UpdateContactList(this.navigationArguments?.
Contacts);
133 await MainThread.InvokeOnMainThreadAsync(this.FilterContacts);
141 await base.OnInitializeAsync();
144 this.contactsLoader.Run();
145 await this.contactsLoader.WaitAllAsync();
147 ServiceRef.XmppService.OnPresenceSubscribe += this.Xmpp_OnPresence;
148 ServiceRef.XmppService.OnPresenceUnsubscribed += this.Xmpp_OnPresence;
149 ServiceRef.XmppService.OnPresence += this.Xmpp_OnPresence;
150 ServiceRef.NotificationService.OnNewNotification += this.NotificationService_OnNewNotification;
151 ServiceRef.NotificationService.OnNotificationsDeleted += this.NotificationService_OnNotificationsDeleted;
157 await base.OnAppearingAsync();
159 if (this.selection is not
null && this.selection.Task.IsCompleted)
165 this.SelectedContact =
null;
168 private async Task UpdateContactList(IEnumerable<ContactInfo>?
Contacts)
170 SortedDictionary<CaseInsensitiveString, ContactInfo> Sorted = [];
171 Dictionary<CaseInsensitiveString, bool> Jids = [];
190 if (Jids.ContainsKey(Item.
BareJid))
207 this.Contacts.Clear();
208 this.ShowContactsMissing = Sorted.Count == 0;
214 if (Sorted.TryGetValue(Category, out
ContactInfo? Info))
215 Sorted.Remove(Category);
220 if (Info is not
null)
221 Remove(Sorted, Info.FriendlyName, Info);
227 FriendlyName = Category,
233 this.Contacts.Add(
new ContactInfoModel(Info, Events));
242 this.Contacts.Add(
new ContactInfoModel(Info, Events));
245 this.byBareJid.Clear();
247 foreach (ContactInfoModel? Contact
in this.Contacts)
249 if (
string.IsNullOrEmpty(Contact?.BareJid))
252 if (!this.byBareJid.TryGetValue(Contact.BareJid, out List<ContactInfoModel>? Contacts2))
255 this.byBareJid[Contact.BareJid] = Contacts2;
258 Contacts2.Add(Contact);
262 private void FilterContacts()
264 this.FilteredContacts.Clear();
265 if (this.Contacts.Count == 0)
268 Dictionary<char, List<ContactInfoModel>> Groups = [];
269 string Search = (this.SearchText ??
string.Empty).Trim();
270 bool HasSearch = !
string.IsNullOrEmpty(Search);
272 foreach (ContactInfoModel? Contact
in this.Contacts)
279 string Name = Contact.FriendlyName ??
string.Empty;
280 if (Name.IndexOf(Search, StringComparison.CurrentCultureIgnoreCase) < 0)
284 char Group =
string.IsNullOrEmpty(Contact?.FriendlyName) ?
'#' : Contact.FriendlyName.Substring(0, 1).ToUpper(CultureInfo.CurrentCulture)[0];
285 if (!
char.IsLetter(Group))
288 if (!Groups.TryGetValue(Group, out List<ContactInfoModel>? list))
290 list =
new List<ContactInfoModel>();
291 Groups[Group] = list;
297 foreach (
char Group
in Groups.Keys.OrderBy(s => s))
299 GroupedContacts ContactGroup =
new(Group);
301 foreach (ContactInfoModel Contact
in Groups[Group].OrderBy(Contact => Contact.FriendlyName))
302 ContactGroup.Contacts.Add(Contact);
304 this.FilteredContacts.Add(ContactGroup);
310 if (Sorted.ContainsKey(Name))
317 Suffix =
" " + (++i).
ToString(CultureInfo.InvariantCulture);
319 while (Sorted.ContainsKey(Name + Suffix));
321 Sorted[Name + Suffix] = Info;
330 string Suffix =
string.Empty;
332 while (Sorted.TryGetValue(Name + Suffix, out
ContactInfo? Info2))
334 if (Info2.BareJid == Info.
BareJid &&
337 Info2.NodeId == Info.
NodeId &&
340 Sorted.Remove(Name + Suffix);
343 string Suffix2 =
" " + i.ToString(CultureInfo.InvariantCulture);
345 while (Sorted.TryGetValue(Name + Suffix2, out Info2))
347 Sorted[Name + Suffix] = Info2;
348 Sorted.Remove(Name + Suffix2);
351 Suffix2 =
" " + i.ToString(CultureInfo.InvariantCulture);
358 Suffix =
" " + i.ToString(CultureInfo.InvariantCulture);
362 private bool CanSelectContact => !this.IsViewMode;
364 [RelayCommand(CanExecute = nameof(CanSelectContact))]
365 private Task SelectContact(ContactInfoModel? Contact)
367 if (Contact is not
null)
369 this.SelectedContact = Contact;
372 return Task.CompletedTask;
378 ServiceRef.XmppService.OnPresence -= this.Xmpp_OnPresence;
379 ServiceRef.NotificationService.OnNewNotification -= this.NotificationService_OnNewNotification;
380 ServiceRef.NotificationService.OnNotificationsDeleted -= this.NotificationService_OnNotificationsDeleted;
384 this.ShowContactsMissing =
false;
385 this.Contacts.Clear();
388 this.selection?.TrySetResult(this.SelectedContact);
389 this.contactsLoader.Dispose();
390 this.searchFilterTask.Dispose();
392 return base.OnDisposeAsync();
399 private bool showContactsMissing;
405 private string? description;
411 private bool canScanQrCode;
417 private bool allowAnonymous;
423 private string? anonymousText;
434 public ObservableCollection<ContactInfoModel?>
Contacts {
get; }
436 public ObservableCollection<GroupedContacts> FilteredContacts {
get; } =
new();
442 private ContactInfoModel? selectedContact;
447 base.OnPropertyChanged(e);
449 switch (e.PropertyName)
451 case nameof(this.SelectedContact):
454 if (Contact is not
null)
456 MainThread.BeginInvokeOnMainThread(async () =>
458 this.IsOverlayVisible =
true;
465 StringBuilder sb =
new();
467 sb.Append(
"edaler:");
480 if (!
string.IsNullOrEmpty(Contact.
LegalId))
485 else if (!
string.IsNullOrEmpty(Contact.
BareJid))
509 ViewIdentityNavigationArgs ViewIdentityArgs =
new(Contact.
LegalIdentity);
513 else if (!
string.IsNullOrEmpty(Contact.
LegalId))
518 else if (!
string.IsNullOrEmpty(Contact.
BareJid) && Contact.Contact is not
null)
527 this.SelectedContact = Contact;
529 this.selection?.TrySetResult(Contact);
535 this.IsOverlayVisible =
false;
547 private async Task ScanQrCode()
550 if (
string.IsNullOrEmpty(Code))
560 else if (!
string.IsNullOrEmpty(Code))
571 private void Anonymous()
573 this.SelectedContact =
new ContactInfoModel(
null, []);
580 foreach (ContactInfoModel Contact
in Contacts)
581 Contact.PresenceUpdated();
584 return Task.CompletedTask;
590 this.UpdateNotifications(e.Event.Category ??
string.Empty);
592 return Task.CompletedTask;
595 private void UpdateNotifications()
598 this.UpdateNotifications(Category);
603 if (this.byBareJid.TryGetValue(Category, out List<ContactInfoModel>?
Contacts))
608 foreach (ContactInfoModel Contact
in Contacts)
609 Contact.NotificationsUpdated(Events);
615 Dictionary<CaseInsensitiveString, bool> Categories = [];
618 Categories[Event.Category ??
string.Empty] =
true;
621 this.UpdateNotifications(Category);
623 return Task.CompletedTask;
Contains information about a balance.
CaseInsensitiveString Currency
Currency of amount.
Abstract base class for eDaler URIs
static bool TryParse(string Uri, out EDalerUri Result)
Tries to parse an eDaler URI
static bool StartsWithIdScheme(string Url)
Checks if the specified code starts with the IoT ID scheme.
static ? string RemoveScheme(string Url)
Removes the URI Schema from an URL.
const string IotId
The IoT ID URI Scheme (iotid)
A set of never changing property constants and helpful values.
A strongly-typed resource class, for looking up localized strings, etc.
static string Anonymous
Looks up a localized string similar to Anonymous.
static string ScanQRCode
Looks up a localized string similar to Scan QR Code.
static string ContactsDescription
Looks up a localized string similar to Below are all contacts in your contact book....
static string ScannedQrCode
Looks up a localized string similar to Scanned QR Code.
static string TheSpecifiedCodeIsNotALegalIdentity
Looks up a localized string similar to You need to scan a different type of QR Code,...
static string ErrorTitle
Looks up a localized string similar to An error has occurred.
Base class that references services in the app.
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.
Helper class to perform scanning of QR Codes by displaying the UI and handling async results.
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....
Provides a data-binding friendly mechanism to manage and report the status of asynchronous operations...
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.
LegalIdentity? LegalIdentity
Legal Identity object.
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.
string? AnonymousText
String to display on the anonymous button
string? Description
Description presented to user.
bool AllowAnonymous
If user is allowed to select an Anonymous option.
bool CanScanQrCode
If the user should be able to scane QR Codes.
The view model to bind to when displaying the list of contacts.
override async Task OnAppearingAsync()
Method called when view is appearing on the screen.
ObservableTask< int > ContactsLoader
Exposes the loader responsible for loading contacts. Bind to ObservableTask<TProgress>....
string? SearchText
Text used for filtering contacts (search box).
override 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 ...
ContactListViewModel(ContactListNavigationArgs? Args)
Creates an instance of the ContactListViewModel class.
ObservableCollection< ContactInfoModel?> Contacts
Holds the list of contacts to display.
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.
Event arguments for presence events.
string FromBareJID
Bare JID of resource sending the presence.
Maintains information about an item in the roster.
string NameOrBareJid
Returns the name of the contact, or the Bare JID, if there's no name provided.
string BareJid
Bare JID of the roster item.
Represents a case-insensitive string.
Static interface for database persistence. In order to work, a database provider has to be assigned t...
static Task< IEnumerable< object > > Find(string Collection, params string[] SortOrder)
Finds objects in a given collection.
static async Task Insert(object Object)
Inserts an object into the default collection of the database.
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)
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.
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
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.