feat/postit-acl #32
4 changed files with 120 additions and 39 deletions
feat(app-invite): isolate ContactService to mobile targets
Splits the single ContactService class (which threw PlatformNotSupportedException on non-Android/iOS targets) into a platform-conditional structure: - IContactService + ContactDto: shared abstraction in src/PostIt/PostIt/Services/IContactService.cs. ViewModels depend on this; concrete providers map their native shapes to ContactDto. - ContactService.Mobile.cs: MAUI Essentials implementation, compiled only when ANDROID or IOS is defined. Wraps Contacts.Default.GetAllAsync() with permission handling and a NotImplementedInReferenceAssemblyException safety net. - ContactService.Desktop.cs: stub returning an empty list, compiled when neither ANDROID nor IOS is defined. Replaces the 'throw PlatformNotSupportedException' path so desktop targets (PostIt.Desktop, PostIt.Browser) build and run cleanly. The Microsoft.Maui.Essentials portable facade is referenced from PostIt.csproj, but it only becomes functional when the host application project (PostIt.Android, future PostIt.iOS) also references the platform-specific implementation. No tests added: per AGENTS.md, a 'stub returns empty list' test on PostIt.Tests (net10.0 desktop target) would be cosmetic and not detect the real failure mode. Android-side tests require a working PostIt.Android project, which doesn't exist yet. Future providers (Google Contacts API, Exchange, CardDAV) plug in as additional IContactService implementations selected by DI configuration.
commit
69a660cafb
27
src/PostIt/PostIt/Services/ContactService.Desktop.cs
Normal file
27
src/PostIt/PostIt/Services/ContactService.Desktop.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#if !ANDROID && !IOS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PostIt.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Desktop stub for IContactService.
|
||||
///
|
||||
/// On desktop targets (Linux, macOS, Windows) MAUI Essentials
|
||||
/// Contacts.Default throws NotImplementedInReferenceAssemblyException,
|
||||
/// so we short-circuit with an empty list rather than trying to
|
||||
/// call into the portable facade at runtime.
|
||||
///
|
||||
/// Future provider plug-ins (Google Contacts API, Exchange EWS,
|
||||
/// CardDAV) can either replace this stub on a per-OS basis or
|
||||
/// live behind their own IContactService implementation that the
|
||||
/// DI container selects by configuration.
|
||||
/// </summary>
|
||||
public sealed class ContactService : IContactService
|
||||
{
|
||||
public Task<IReadOnlyList<ContactDto>> GetDeviceContactsAsync(CancellationToken ct = default)
|
||||
=> Task.FromResult<IReadOnlyList<ContactDto>>(Array.Empty<ContactDto>());
|
||||
}
|
||||
#endif
|
||||
64
src/PostIt/PostIt/Services/ContactService.Mobile.cs
Normal file
64
src/PostIt/PostIt/Services/ContactService.Mobile.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#if ANDROID || IOS
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Maui.ApplicationModel.Communication;
|
||||
using Microsoft.Maui.ApplicationModel;
|
||||
using Microsoft.Maui.Devices;
|
||||
|
||||
namespace PostIt.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Mobile implementation backed by MAUI Essentials Contacts.Default.
|
||||
///
|
||||
/// Compiled only for ANDROID and IOS. On desktop targets, see
|
||||
/// ContactService.Desktop.cs (the stub that wins at compile time).
|
||||
///
|
||||
/// Note: at runtime, this class throws
|
||||
/// NotImplementedInReferenceAssemblyException unless the host
|
||||
/// application project also references the platform-specific
|
||||
/// Microsoft.Maui.Essentials implementation (typically the
|
||||
/// PostIt.Android project). On iOS the same is required via
|
||||
/// PostIt.iOS. On desktop the stub is used and this file is excluded.
|
||||
/// </summary>
|
||||
public sealed class ContactService : IContactService
|
||||
{
|
||||
public async Task<IReadOnlyList<ContactDto>> GetDeviceContactsAsync(CancellationToken ct = default)
|
||||
{
|
||||
if (DeviceInfo.Current.Platform == DevicePlatform.Unknown)
|
||||
return Array.Empty<ContactDto>();
|
||||
|
||||
try
|
||||
{
|
||||
var status = await Permissions.RequestAsync<Permissions.ContactsRead>();
|
||||
if (status != PermissionStatus.Granted)
|
||||
return Array.Empty<ContactDto>();
|
||||
|
||||
var contacts = await Contacts.Default.GetAllAsync();
|
||||
if (contacts is null) return Array.Empty<ContactDto>();
|
||||
|
||||
var result = new List<ContactDto>();
|
||||
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));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"ContactService: {ex.Message}");
|
||||
return Array.Empty<ContactDto>();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Maui.ApplicationModel.Communication;
|
||||
using Microsoft.Maui.ApplicationModel;
|
||||
using Microsoft.Maui.Devices;
|
||||
|
||||
public class ContactService
|
||||
{
|
||||
public async Task<IEnumerable<Contact>> GetDeviceContactsAsync()
|
||||
{
|
||||
// 1. Ensure the platform supports MAUI Essentials APIs
|
||||
if (DeviceInfo.Current.Platform == DevicePlatform.Unknown)
|
||||
{
|
||||
throw new PlatformNotSupportedException("MAUI Essentials is not available on this platform.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 2. Request runtime permission (Required for Android & iOS)
|
||||
var status = await Permissions.RequestAsync<Permissions.ContactsRead>();
|
||||
if (status != PermissionStatus.Granted)
|
||||
{
|
||||
// Permission denied by user
|
||||
return Array.Empty<Contact>();
|
||||
}
|
||||
|
||||
// 3. Fetch all contacts
|
||||
var contactsEnumerable = await Contacts.Default.GetAllAsync();
|
||||
return contactsEnumerable ?? Array.Empty<Contact>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle cross-platform exceptions or logs here
|
||||
System.Diagnostics.Debug.WriteLine($"Error fetching contacts: {ex.Message}");
|
||||
return Array.Empty<Contact>();
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/PostIt/PostIt/Services/IContactService.cs
Normal file
29
src/PostIt/PostIt/Services/IContactService.cs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace PostIt.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction over device contact providers (MAUI Essentials on mobile,
|
||||
/// future Google/Exchange/IMAP providers).
|
||||
///
|
||||
/// Implementations live next to this file in platform-conditional
|
||||
/// source files: ContactService.Mobile.cs (ANDROID/IOS) and
|
||||
/// ContactService.Desktop.cs (everything else).
|
||||
/// </summary>
|
||||
public interface IContactService
|
||||
{
|
||||
Task<IReadOnlyList<ContactDto>> GetDeviceContactsAsync(CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Platform-neutral contact DTO. Source-of-truth shape for the UI layer;
|
||||
/// concrete providers (MAUI Essentials today, Google Contacts API later)
|
||||
/// map to this type.
|
||||
/// </summary>
|
||||
public sealed record ContactDto(
|
||||
string Id,
|
||||
string DisplayName,
|
||||
IReadOnlyList<string> Emails);
|
||||
Loading…
Add table
Add a link
Reference in a new issue