mirror of
https://github.com/sudosylabs/vnidrop.git
synced 2026-08-05 02:29:55 +02:00
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".
49 lines
1.9 KiB
Swift
49 lines
1.9 KiB
Swift
import XCTest
|
|
@testable import VniDrop
|
|
|
|
/// Ports `preferences/AppPreferencesRepositoryTest.kt` — values persist to the
|
|
/// backing store and reload identically.
|
|
@MainActor
|
|
final class AppPreferencesRepositoryTests: XCTestCase {
|
|
|
|
private func defaults() -> UserDefaults { UserDefaults(suiteName: "vnidrop.prefs.\(UUID().uuidString)")! }
|
|
private func fallback() -> AppPreferencesDefaults {
|
|
AppPreferencesDefaults(
|
|
username: "Default",
|
|
receiveFolder: ReceiveFolder(kind: .fileSystemPath, value: "/tmp", displayName: "Downloads"),
|
|
themeMode: .system
|
|
)
|
|
}
|
|
|
|
func testFallbacksWhenEmpty() {
|
|
let repo = AppPreferencesRepository(defaults: defaults(), fallback: fallback())
|
|
XCTAssertEqual(repo.preferences.username, "Default")
|
|
XCTAssertEqual(repo.preferences.themeMode, .system)
|
|
}
|
|
|
|
func testValuesPersistAndReload() {
|
|
let store = defaults()
|
|
let fb = fallback()
|
|
let repo = AppPreferencesRepository(defaults: store, fallback: fb)
|
|
repo.setUsername("Bob")
|
|
repo.setThemeMode(.dark)
|
|
repo.setReceiveFolder(ReceiveFolder(kind: .iosSecurityScopedUrl, value: "file:///x", displayName: "Custom"))
|
|
|
|
// A fresh repository over the same store reflects the persisted values.
|
|
let reloaded = AppPreferencesRepository(defaults: store, fallback: fb)
|
|
XCTAssertEqual(reloaded.preferences.username, "Bob")
|
|
XCTAssertEqual(reloaded.preferences.themeMode, .dark)
|
|
XCTAssertEqual(reloaded.preferences.receiveFolder.displayName, "Custom")
|
|
XCTAssertEqual(reloaded.preferences.receiveFolder.kind, .iosSecurityScopedUrl)
|
|
}
|
|
|
|
func testResetReceiveFolderRestoresFallback() {
|
|
let store = defaults()
|
|
let fb = fallback()
|
|
let repo = AppPreferencesRepository(defaults: store, fallback: fb)
|
|
repo.setReceiveFolder(ReceiveFolder(kind: .fileSystemPath, value: "/custom", displayName: "Custom"))
|
|
repo.resetReceiveFolder()
|
|
XCTAssertEqual(repo.preferences.receiveFolder.value, "/tmp")
|
|
}
|
|
}
|