5 Commits

Author SHA1 Message Date
c670dda0a9 fix(apple): run the notification delegate on the main actor (iOS crash)
Tapping an approval notification while the app was backgrounded crashed on iOS
with "Call must be made on main thread". The UNUserNotificationCenterDelegate
methods are `async` and nonisolated, so their continuation resumes off the main
thread at the return point — where UIKit synchronously runs state-restoration /
snapshot work, tripping the main-thread assertion. (The empty iOS `didReceive`
body didn't matter; even an empty async method returns off-main.)

Isolate NotificationPresenter to `@MainActor` so the delegate returns on the main
thread. `@preconcurrency` on the UNUserNotificationCenterDelegate conformance is
required because those requirements are nonisolated with non-Sendable UN*
parameters, which strict concurrency won't otherwise let a main-actor type
witness. The macOS branch's now-redundant `await MainActor.run { … }` is dropped.
2026-07-31 12:46:05 +02:00
d2924f7ce6 fix(apple): focus the running instance on notification tap
Add a UNUserNotificationCenterDelegate didReceive handler so tapping a notification
is handled inside the running app — activating and bringing the existing window
forward — instead of falling through to default launch behavior, which on macOS
can surface a second process. The approval/transfer UI is driven by core state, so
activating the window reveals any pending approval.
2026-07-24 18:32:15 +02:00
3469b122c2 feat(apple): local notifications for transfer lifecycle events
Adds background notifications for the "the thing you were waiting for is
done" moments, alongside the existing incoming-approval-request one:

  - a receive finished downloading            (receive -> done)
  - a receive failed / was interrupted         (receive -> failed)
  - a share you own failed                      (send -> failed)
  - a receiver finished downloading your share  (receiver status completed)

A new TransferNotificationCoordinator observes core state + signals and
publishes these; the decision of which moments notify is a pure function
(plannedTransferNotifications / plannedReceiverNotifications), unit-tested
independently. The first state snapshot only primes existing history as seen
so launch doesn't spam.

Notification permission is now the single source of truth. The in-app
notifications toggle and its decoupled UserDefaults preference are gone;
the Settings section shows an "Allow notifications" button that requests the
OS permission (or deep-links to Settings once decided), and notifications
gate purely on `permission == .granted`.

macOS delivery fixes:
  - add a UNUserNotificationCenterDelegate so banners present even while the
    app is active (the app window is usually open on macOS)
  - present-when-active on macOS, suppress-when-foregrounded on iOS
  - reserve the notification id before awaiting publish: the CombineLatest
    fired several times and re-added the same identifier, which macOS
    coalesces into a silent update with no banner
  - LocalNotificationService seeds its permission at init so gating can't
    race a not-yet-refreshed .notDetermined

Eight localized title/body strings added (apple-only); the shared
notifications_description copy is generalized from "receive requests" to
"transfer activity".
2026-07-23 23:29:40 +02:00
9e8564e013 refactor(platform): remove Apple targets from KMP 2026-07-21 17:09:59 +02:00
ea376a382b feat(apple): native SwiftUI app for iOS and macOS
Add a native SwiftUI VniDrop app (Send/Receive/Settings) talking to the
Rust core via generated UniFFI Swift bindings, plus the uniffi-bindgen
helper crate. iOS uses a TabView, macOS a NavigationSplitView sidebar.
2026-07-18 22:06:30 +02:00