feat(postit): wire Desktop address book to /api/user-search
Replaces the empty ContactService.Desktop stub with a real implementation backed by UserSearchClient. Closes the loop between the server-side /api/user-search endpoint (b3056f1c), the client wrapper (6e7e0414), and the platform abstraction. IContactService gains: - SearchAsync(string query, CancellationToken): on desktop, hits /api/user-search and appends results to an in-memory cache. On mobile, throws PlatformNotSupportedException — mobile providers use the device-local address book (GetDeviceContactsAsync) and don't talk to a network search. - Contacts (ObservableCollection<ContactDto>): live view of the cache; UI binds directly to it. Mobile populates it inside GetDeviceContactsAsync (eager load); desktop populates it via SearchAsync (lazy, on-demand). ContactDto shape changes: - Emails (IReadOnlyList<string>) -> Email (string?). The /api/user-search endpoint returns one email per user. The use case ('invite / add to a circle') only needs one. - Mobile provider flattens its per-contact email list down to the first non-empty entry (a small functional loss that matches the wire shape). App.axaml.cs constructs a ContactService from the UserSearchClient singleton and registers it as IContactService so future ViewModels can take the interface by constructor injection. Build + 51/51 tests green. The mobile provider is still gated by #if ANDROID || IOS and not exercised by the Desktop test target — runtime behaviour on Android will need a smoke test on device when PostIt.Android lands.
This commit is contained in:
parent
6e7e04141b
commit
d0e0f4c175
4 changed files with 126 additions and 30 deletions
|
|
@ -1,6 +1,7 @@
|
|||
#if ANDROID || IOS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Maui.ApplicationModel.Communication;
|
||||
|
|
@ -24,6 +25,8 @@ namespace PostIt.Services;
|
|||
/// </summary>
|
||||
public sealed class ContactService : IContactService
|
||||
{
|
||||
public ObservableCollection<ContactDto> Contacts { get; } = new();
|
||||
|
||||
public async Task<IReadOnlyList<ContactDto>> GetDeviceContactsAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (DeviceInfo.Current.Platform == DevicePlatform.Unknown)
|
||||
|
|
@ -38,21 +41,18 @@ public sealed class ContactService : IContactService
|
|||
var contacts = await Contacts.Default.GetAllAsync();
|
||||
if (contacts is null) return Array.Empty<ContactDto>();
|
||||
|
||||
var result = new List<ContactDto>();
|
||||
// Flatten the per-contact email list down to one
|
||||
// primary email. The platform-neutral ContactDto only
|
||||
// carries one; the use case ("invite / add to a
|
||||
// circle") only needs one. The first non-empty entry
|
||||
// wins.
|
||||
Contacts.Clear();
|
||||
foreach (var c in contacts)
|
||||
{
|
||||
var emails = new List<string>();
|
||||
if (c.Emails is not null)
|
||||
{
|
||||
foreach (var e in c.Emails)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.EmailAddress))
|
||||
emails.Add(e.EmailAddress);
|
||||
}
|
||||
}
|
||||
result.Add(new ContactDto(c.Id, c.DisplayName ?? string.Empty, emails));
|
||||
var email = FlattenPrimaryEmail(c.Emails);
|
||||
Contacts.Add(new ContactDto(c.Id, c.DisplayName ?? string.Empty, email));
|
||||
}
|
||||
return result;
|
||||
return Contacts.ToArray();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
@ -60,5 +60,22 @@ public sealed class ContactService : IContactService
|
|||
return Array.Empty<ContactDto>();
|
||||
}
|
||||
}
|
||||
|
||||
public Task SearchAsync(string query, CancellationToken ct = default)
|
||||
=> throw new PlatformNotSupportedException(
|
||||
"SearchAsync is not supported on mobile — use GetDeviceContactsAsync " +
|
||||
"to load the local address book. The network search lives on the " +
|
||||
"desktop service, which queries the central user-search endpoint.");
|
||||
|
||||
private static string? FlattenPrimaryEmail(IEnumerable<EmailAddress>? emails)
|
||||
{
|
||||
if (emails is null) return null;
|
||||
foreach (var e in emails)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.EmailAddress))
|
||||
return e.EmailAddress;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue