mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 08:15:24 +00:00
Move any visible account settings into AccountSettings. (#6308)
* Remove the now unused setting to disable in-app notifications. * Move visible account settings into AccountSettings. Have ignored enableNotifications for now as that will require reworking the NotificationManager. * Allow different sounds to be selected for each account. * Move defaultNotificationTone into AccountSettings.
This commit is contained in:
@@ -28,9 +28,6 @@ final nonisolated class AppHooks: AppHooksProtocol {
|
||||
@AppHook(default: DefaultOAuthPresenterHook())
|
||||
var oAuthPresenterHook: OAuthPresenterHookProtocol
|
||||
|
||||
@AppHook(default: DefaultUserSessionHook())
|
||||
var userSessionHook: UserSessionHookProtocol
|
||||
|
||||
@AppHook(default: DefaultRoomScreenHook())
|
||||
var roomScreenHook: RoomScreenHookProtocol
|
||||
|
||||
@@ -64,6 +61,9 @@ final nonisolated class AppHooks: AppHooksProtocol {
|
||||
|
||||
@AppHook(default: DefaultRemoteSettingsHook())
|
||||
var remoteSettingsHook: RemoteSettingsHookProtocol
|
||||
|
||||
@AppHook(default: DefaultUserSessionHook())
|
||||
var userSessionHook: UserSessionHookProtocol
|
||||
}
|
||||
|
||||
nonisolated protocol AppHooksProtocol: Sendable {
|
||||
|
||||
@@ -8,9 +8,19 @@
|
||||
import MatrixRustSDK
|
||||
|
||||
nonisolated protocol UserSessionHookProtocol: Sendable {
|
||||
#if IS_MAIN_APP
|
||||
func configure(with userSession: UserSessionProtocol?) async
|
||||
#endif
|
||||
|
||||
func configure(_ userSettings: UserSettings, forHomeserver homeserver: String) -> UserSettings
|
||||
}
|
||||
|
||||
nonisolated struct DefaultUserSessionHook: UserSessionHookProtocol {
|
||||
#if IS_MAIN_APP
|
||||
func configure(with userSession: UserSessionProtocol?) async { }
|
||||
#endif
|
||||
|
||||
func configure(_ userSettings: UserSettings, forHomeserver homeserver: String) -> UserSettings {
|
||||
userSettings
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,10 +467,28 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
MXLog.info("Migrating to version 25.07.4, log files have been moved.")
|
||||
}
|
||||
|
||||
if oldVersion < Version(26, 10, 2) {
|
||||
MXLog.info("Migrating to version 26.10.2, moving notification sounds.")
|
||||
let soundsDirectory = URL.libraryDirectory.appending(component: "Sounds", directoryHint: .isDirectory)
|
||||
|
||||
// Sounds are now referenced directly, the duplicate file can go.
|
||||
try? FileManager.default.removeItem(at: soundsDirectory.appending(component: "currentAlert.caf"))
|
||||
|
||||
let oldCustomTonesLocation = soundsDirectory.appending(component: "AvailableSounds", directoryHint: .isDirectory)
|
||||
if FileManager.default.fileExists(atPath: oldCustomTonesLocation.path(percentEncoded: false)) {
|
||||
do {
|
||||
try FileManager.default.moveItem(at: oldCustomTonesLocation, to: URL.customTonesDirectory)
|
||||
} catch {
|
||||
MXLog.error("Failed moving custom notification sounds: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the old version to run additional migrations on the user session once it has been set up.
|
||||
userSessionMigrationsOldVersion = oldVersion
|
||||
}
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
private func performUserSessionMigrations(_ userSession: UserSessionProtocol) async {
|
||||
guard let oldVersion = userSessionMigrationsOldVersion else { return }
|
||||
|
||||
@@ -515,6 +533,23 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
||||
userSession.userSettings.account.migrateAppSettingsValue(\.searchBreadcrumbsKey)
|
||||
}
|
||||
|
||||
if oldVersion < Version(26, 10, 2) {
|
||||
MXLog.info("Migrating to version 26.10.2, moving visible account settings.")
|
||||
userSession.userSettings.account.migrateAppSettingsValue(\.showAllRoomListActivityKey)
|
||||
userSession.userSettings.account.migrateAppSettingsValue(\.selectedNotificationToneKey)
|
||||
userSession.userSettings.account.migrateAppSettingsValue(\.sharePresenceKey)
|
||||
|
||||
// Make sure system notification sounds are available in the new directory.
|
||||
if let selectedNotificationTone = userSession.userSettings.account.selectedNotificationTone {
|
||||
do {
|
||||
MXLog.info("Copying the selected system notification sound.")
|
||||
try NotificationToneManager.copySystemToneIfNeeded(selectedNotificationTone)
|
||||
} catch {
|
||||
MXLog.error("Failed migrating the selected notification sound: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userSessionMigrationsOldVersion = nil
|
||||
}
|
||||
|
||||
|
||||
@@ -14,23 +14,34 @@ final nonisolated class AccountSettings: @unchecked Sendable {
|
||||
private let store: AccountUserDefaults
|
||||
private let userID: String
|
||||
|
||||
// MARK: Accounts
|
||||
// MARK: Session Activity
|
||||
|
||||
/// When this account was last selected, used to order the accounts most recently selected first.
|
||||
@UserPreference
|
||||
var lastSelectedDate: Date?
|
||||
|
||||
// MARK: Session
|
||||
|
||||
@UserPreference(defaultValue: false)
|
||||
var hasRunIdentityConfirmationOnboarding: Bool
|
||||
|
||||
// MARK: Search
|
||||
|
||||
/// The queries the user searched for and the rooms they opened from the results, most recent first.
|
||||
@UserPreference(defaultValue: [SearchBreadcrumb]())
|
||||
var searchBreadcrumbs: [SearchBreadcrumb]
|
||||
|
||||
// MARK: Visible Preferences
|
||||
|
||||
@UserPreference(defaultValue: true)
|
||||
var showAllRoomListActivity: Bool
|
||||
|
||||
/// The tone played when the user hasn't selected one, which the homeserver can configure remotely.
|
||||
let defaultNotificationTone: RemotePreference<NotificationTone> = .init(.default)
|
||||
|
||||
/// The sound played when delivering noisy notifications. If nil, use ``defaultNotificationTone``.
|
||||
@UserPreference
|
||||
var selectedNotificationTone: NotificationTone?
|
||||
|
||||
@UserPreference(defaultValue: true)
|
||||
var sharePresence: Bool
|
||||
|
||||
init(userID: String, store: UserDefaultsProtocol) {
|
||||
self.store = .init(userID: userID, store: store)
|
||||
self.userID = userID
|
||||
|
||||
@@ -235,9 +235,6 @@ final nonisolated class AppSettings: @unchecked Sendable {
|
||||
@UserPreference(defaultValue: true)
|
||||
var enableNotifications: Bool
|
||||
|
||||
@UserPreference(defaultValue: true)
|
||||
var enableInAppNotifications: Bool
|
||||
|
||||
@UserPreference(defaultValue: false)
|
||||
var hideQuietNotificationAlerts: Bool
|
||||
|
||||
@@ -253,10 +250,6 @@ final nonisolated class AppSettings: @unchecked Sendable {
|
||||
@UserPreference(defaultValue: 0)
|
||||
var lastKnownBadgeCount: Int
|
||||
|
||||
/// The sound played when delivering noisy notifications. If nil, use the ElementX default
|
||||
@UserPreference
|
||||
var selectedNotificationTone: NotificationTone?
|
||||
|
||||
// MARK: - Logging
|
||||
|
||||
@UserPreference(defaultValue: LogLevel.info)
|
||||
@@ -331,11 +324,6 @@ final nonisolated class AppSettings: @unchecked Sendable {
|
||||
@UserPreference(defaultValue: false)
|
||||
var liveLocationDisclaimerDisplayed: Bool
|
||||
|
||||
// MARK: - Home Screen
|
||||
|
||||
@UserPreference(defaultValue: true)
|
||||
var showAllRoomListActivity: Bool
|
||||
|
||||
// MARK: - Room Screen
|
||||
|
||||
@UserPreference(defaultValue: AppBuildType.current == .debug)
|
||||
@@ -382,11 +370,6 @@ final nonisolated class AppSettings: @unchecked Sendable {
|
||||
/// The MapTiler configuration used to build map URLs, which defaults to the bundled one.
|
||||
private(set) var mapTilerConfiguration = RemotePreference(AppSettings.bundledMapTilerConfiguration)
|
||||
|
||||
// MARK: - Presence
|
||||
|
||||
@UserPreference(defaultValue: true)
|
||||
var sharePresence: Bool
|
||||
|
||||
// MARK: - Feature Flags
|
||||
|
||||
/// Others
|
||||
|
||||
@@ -8692,6 +8692,11 @@ nonisolated class MediaProviderMock: MediaProviderProtocol, @unchecked Sendable
|
||||
}
|
||||
}
|
||||
nonisolated class NSEUserSessionMock: NSEUserSessionProtocol, @unchecked Sendable {
|
||||
var userSettings: UserSettings {
|
||||
get { return underlyingUserSettings }
|
||||
set(value) { underlyingUserSettings = value }
|
||||
}
|
||||
nonisolated(unsafe) var underlyingUserSettings: UserSettings!
|
||||
nonisolated(unsafe) var inviteAvatarsVisibilityCallsCount = 0
|
||||
var inviteAvatarsVisibilityCalled: Bool {
|
||||
return inviteAvatarsVisibilityCallsCount > 0
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import MatrixRustSDK
|
||||
|
||||
struct NSEUserSessionMockConfiguration {
|
||||
var userSettings = UserSettings.mock()
|
||||
var inviteAvatarsVisibility = InviteAvatars.on
|
||||
var mediaPreviewVisibility = MediaPreviews.on
|
||||
var threadsEnabled = true
|
||||
@@ -17,6 +18,7 @@ extension NSEUserSessionMock {
|
||||
convenience init(_ configuration: NSEUserSessionMockConfiguration) {
|
||||
self.init()
|
||||
|
||||
underlyingUserSettings = configuration.userSettings
|
||||
underlyingInviteAvatarsVisibility = configuration.inviteAvatarsVisibility
|
||||
underlyingMediaPreviewVisibility = configuration.mediaPreviewVisibility
|
||||
threadsEnabled = configuration.threadsEnabled
|
||||
|
||||
@@ -103,6 +103,16 @@ nonisolated extension URL {
|
||||
return systemRoot.appending(components: "System", "Library", "Audio", "UISounds", directoryHint: .isDirectory)
|
||||
}()
|
||||
|
||||
/// The directory holding notification tones imported by the user, inside the Library/Sounds directory searched by the system.
|
||||
static var customTonesDirectory: URL {
|
||||
libraryDirectory.appending(components: "Sounds", "Custom", directoryHint: .isDirectory)
|
||||
}
|
||||
|
||||
/// The directory holding copies of selected system tones, inside the Library/Sounds directory searched by the system.
|
||||
static var systemTonesDirectory: URL {
|
||||
libraryDirectory.appending(components: "Sounds", "System", directoryHint: .isDirectory)
|
||||
}
|
||||
|
||||
var globalProxy: String? {
|
||||
guard let proxySettings = CFNetworkCopySystemProxySettings()?.takeRetainedValue() else {
|
||||
MXLog.error("Failed retrieving proxy settings")
|
||||
|
||||
@@ -115,7 +115,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
.weakAssign(to: \.state.selectedRoomID, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
userSettings.app.showAllRoomListActivityPublisher
|
||||
userSettings.account.showAllRoomListActivityPublisher
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] _ in
|
||||
self?.updateRooms()
|
||||
@@ -277,7 +277,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
case .success:
|
||||
analyticsService.trackInteraction(name: .MobileRoomListRoomContextMenuUnreadToggle)
|
||||
|
||||
if case .failure(let error) = await roomProxy.markAsRead(receiptType: userSettings.app.sharePresence ? .read : .readPrivate) {
|
||||
if case .failure(let error) = await roomProxy.markAsRead(receiptType: userSettings.account.sharePresence ? .read : .readPrivate) {
|
||||
MXLog.error("Failed marking room \(roomIdentifier) as read with error: \(error)")
|
||||
}
|
||||
case .failure(let error):
|
||||
@@ -395,7 +395,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
for summary in roomSummaryProvider.roomListPublisher.value {
|
||||
let room = HomeScreenRoom(summary: summary,
|
||||
showAllActivity: userSettings.app.showAllRoomListActivity,
|
||||
showAllActivity: userSettings.account.showAllRoomListActivity,
|
||||
seenInvites: seenInvites)
|
||||
rooms.append(room)
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
Task {
|
||||
// When navigating away from the room, we need to mark the room as both read
|
||||
// and fully read for Synapse to clear this room from the app's badge count.
|
||||
_ = await roomProxy.markAsRead(receiptType: userSettings.app.sharePresence ? .read : .readPrivate)
|
||||
_ = await roomProxy.markAsRead(receiptType: userSettings.account.sharePresence ? .read : .readPrivate)
|
||||
_ = await roomProxy.markAsRead(receiptType: .fullyRead)
|
||||
}
|
||||
// Work around QLPreviewController dismissal issues, see the InteractiveQuickLookModifier.
|
||||
|
||||
+2
-2
@@ -32,8 +32,8 @@ struct ModerationAndSafetySettingsScreenViewStateBindings {
|
||||
}
|
||||
|
||||
var sharePresence: Bool {
|
||||
get { userSettings.app.sharePresence }
|
||||
set { userSettings.app.sharePresence = newValue }
|
||||
get { userSettings.account.sharePresence }
|
||||
set { userSettings.account.sharePresence = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-6
@@ -39,10 +39,10 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
||||
self.notificationToneManager = notificationToneManager
|
||||
|
||||
let bindings = NotificationSettingsScreenViewStateBindings(enableNotifications: userSettings.app.enableNotifications,
|
||||
showAllRoomListActivity: userSettings.app.showAllRoomListActivity)
|
||||
showAllRoomListActivity: userSettings.account.showAllRoomListActivity)
|
||||
super.init(initialViewState: NotificationSettingsScreenViewState(bindings: bindings,
|
||||
isModallyPresented: isModallyPresented,
|
||||
selectedAlertTone: userSettings.app.selectedNotificationTone ?? NotificationToneManager.defaultElementXMessageTone,
|
||||
selectedAlertTone: userSettings.account.selectedNotificationTone ?? userSettings.account.defaultNotificationTone.publisher.value,
|
||||
availableCustomTones: notificationToneManager.customTones(),
|
||||
// macos lacks default sounds and its sandbox Sounds directory is immutable
|
||||
customToneSelectionEnabled: !ProcessInfo.processInfo.isiOSAppOnMac))
|
||||
@@ -52,8 +52,8 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
||||
.weakAssign(to: \.state.bindings.enableNotifications, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
userSettings.app.selectedNotificationTonePublisher
|
||||
.map { $0 ?? NotificationToneManager.defaultElementXMessageTone }
|
||||
userSettings.account.selectedNotificationTonePublisher
|
||||
.map { $0 ?? userSettings.account.defaultNotificationTone.publisher.value }
|
||||
.weakAssign(to: \.state.selectedAlertTone, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
@@ -104,7 +104,7 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
||||
}
|
||||
Task { await enableInvitations(state.bindings.invitationsEnabled) }
|
||||
case .showAllRoomListActivityChanged:
|
||||
userSettings.app.showAllRoomListActivity = state.bindings.showAllRoomListActivity
|
||||
userSettings.account.showAllRoomListActivity = state.bindings.showAllRoomListActivity
|
||||
case .close:
|
||||
actionsSubject.send(.close)
|
||||
case .fixConfigurationMismatchTapped:
|
||||
@@ -302,7 +302,7 @@ class NotificationSettingsScreenViewModel: NotificationSettingsScreenViewModelTy
|
||||
try notificationToneManager.deleteCustomTone(tone)
|
||||
|
||||
if tone == state.selectedAlertTone {
|
||||
userSettings.app.selectedNotificationTone = nil
|
||||
userSettings.account.selectedNotificationTone = nil
|
||||
}
|
||||
} catch {
|
||||
MXLog.error("Error deleting alert tone \(tone.label): \(error)")
|
||||
|
||||
@@ -272,7 +272,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
case .voiceMessage(let voiceMessageAction):
|
||||
processVoiceMessageAction(voiceMessageAction)
|
||||
case .contentChanged(let isEmpty):
|
||||
guard userSettings.app.sharePresence else {
|
||||
guard userSettings.account.sharePresence else {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -508,7 +508,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
|
||||
roomProxy.typingMembersPublisher
|
||||
.receive(on: DispatchQueue.main)
|
||||
.filter { [weak self] _ in self?.userSettings.app.sharePresence ?? false }
|
||||
.filter { [weak self] _ in self?.userSettings.account.sharePresence ?? false }
|
||||
.weakAssign(to: \.state.typingMembers, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
@@ -572,7 +572,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
|
||||
}
|
||||
|
||||
private func setupUserSettingsSubscriptions() {
|
||||
userSettings.app.sharePresencePublisher
|
||||
userSettings.account.sharePresencePublisher
|
||||
.weakAssign(to: \.state.showReadReceipts, on: self)
|
||||
.store(in: &cancellables)
|
||||
|
||||
|
||||
@@ -43,6 +43,9 @@ nonisolated struct NotificationTone: Hashable, Comparable, Codable {
|
||||
case appLibrary
|
||||
}
|
||||
|
||||
/// The default bundled message tone.
|
||||
static let `default`: NotificationTone = .createBundledSound(label: L10n.screenNotificationSettingsSoundElementDefault, filename: "message.caf")
|
||||
|
||||
/// Creates a tone backed by a file in the system sounds directory.
|
||||
static func createSystemSound(label: String?, filename: String, systemSoundsSubdirectory: [String] = []) -> NotificationTone {
|
||||
NotificationTone(labelOverride: label, storageLocationRoot: .system, relativePath: systemSoundsSubdirectory + [filename])
|
||||
|
||||
@@ -31,26 +31,40 @@ nonisolated struct NotificationToneManager: NotificationToneManagerProtocol {
|
||||
|
||||
private let userSettings: UserSettings
|
||||
|
||||
/// The default Element X bundled message tone.
|
||||
static let defaultElementXMessageTone: NotificationTone = .createBundledSound(label: L10n.screenNotificationSettingsSoundElementDefault,
|
||||
filename: "message.caf")
|
||||
|
||||
/// All default tones (system + Element X), sorted by name.
|
||||
static let allDefaultAlerts: [NotificationTone] = (defaultSystemAlerts + defaultElementXAlerts).sorted()
|
||||
static let allDefaultAlerts: [NotificationTone] = (defaultSystemAlerts + defaultBundledAlerts).sorted()
|
||||
|
||||
/// Filename of the active tone file used by the notification service.
|
||||
static let selectedToneFilename = "currentAlert.caf"
|
||||
/// The name the notification service uses to find the tone's file in the app bundle or Library/Sounds.
|
||||
///
|
||||
/// Note: Names with a subdirectory aren't documented for `UNNotificationSound(named:)` but do work.
|
||||
static func soundName(for tone: NotificationTone) -> String {
|
||||
switch tone.storageLocationRoot {
|
||||
case .appBundle:
|
||||
tone.filename
|
||||
case .appLibrary:
|
||||
"\(URL.customTonesDirectory.lastPathComponent)/\(tone.filename)"
|
||||
case .system:
|
||||
"\(URL.systemTonesDirectory.lastPathComponent)/\(tone.filename)"
|
||||
}
|
||||
}
|
||||
|
||||
/// Directory where user-imported custom tones are stored.
|
||||
nonisolated static let libraryLocation = URL.libraryDirectory.appending(components: "Sounds", "AvailableSounds", directoryHint: .isDirectory)
|
||||
/// Copies a system tone into Library/Sounds, as the system sounds directory isn't searched for notification sounds.
|
||||
static func copySystemToneIfNeeded(_ tone: NotificationTone) throws {
|
||||
guard tone.storageLocationRoot == .system else { return }
|
||||
|
||||
let copyLocation = URL.systemTonesDirectory.appending(component: tone.filename)
|
||||
guard (try? copyLocation.checkResourceIsReachable()) != true else { return }
|
||||
|
||||
try FileManager.default.createDirectory(at: URL.systemTonesDirectory, withIntermediateDirectories: true)
|
||||
try FileManager.default.copyItem(at: toneLocation(for: tone), to: copyLocation)
|
||||
}
|
||||
|
||||
/// Creates the manager and ensures required library directories exist.
|
||||
init(userSettings: UserSettings) {
|
||||
self.userSettings = userSettings
|
||||
|
||||
do {
|
||||
try FileManager.default.createDirectory(at: NotificationToneManager.libraryLocation, withIntermediateDirectories: true)
|
||||
try FileManager.default.createDirectory(at: Self.selectedToneLocation.deletingLastPathComponent(), withIntermediateDirectories: true)
|
||||
try FileManager.default.createDirectory(at: URL.customTonesDirectory, withIntermediateDirectories: true)
|
||||
} catch {
|
||||
// Don't crash on a recoverable file system error. The underlying problem (directory creation
|
||||
// failing on launch, e.g. a background launch before the container is writable) is acknowledged
|
||||
@@ -60,29 +74,17 @@ nonisolated struct NotificationToneManager: NotificationToneManagerProtocol {
|
||||
}
|
||||
|
||||
/// Sets the given tone as the active notification alert tone.
|
||||
///
|
||||
/// Copies the tone's audio file to `selectedToneLocation` and persists the selection in app settings.
|
||||
func setSelectedTone(_ alertTone: NotificationTone) throws -> URL {
|
||||
do {
|
||||
try? FileManager.default.removeItem(at: Self.selectedToneLocation)
|
||||
let toneLocation = Self.toneLocation(for: alertTone)
|
||||
try FileManager.default.copyItem(at: toneLocation, to: Self.selectedToneLocation)
|
||||
userSettings.app.selectedNotificationTone = alertTone
|
||||
return Self.selectedToneLocation
|
||||
} catch {
|
||||
if (try? Self.selectedToneLocation.checkResourceIsReachable()) != true {
|
||||
// make sure the selected tone is reset if there's no custom tone present
|
||||
userSettings.app.selectedNotificationTone = nil
|
||||
}
|
||||
throw error
|
||||
}
|
||||
try Self.copySystemToneIfNeeded(alertTone)
|
||||
userSettings.account.selectedNotificationTone = alertTone
|
||||
return Self.toneLocation(for: alertTone)
|
||||
}
|
||||
|
||||
/// Returns all user-imported CAF tones from the library directory, sorted by name.
|
||||
func customTones() -> [NotificationTone] {
|
||||
let availableFiles = try? FileManager
|
||||
.default
|
||||
.contentsOfDirectory(at: NotificationToneManager.libraryLocation, includingPropertiesForKeys: nil)
|
||||
.contentsOfDirectory(at: URL.customTonesDirectory, includingPropertiesForKeys: nil)
|
||||
|
||||
return (availableFiles ?? [])
|
||||
.compactMap {
|
||||
@@ -100,7 +102,7 @@ nonisolated struct NotificationToneManager: NotificationToneManagerProtocol {
|
||||
@discardableResult
|
||||
func addNewToneToLibrary(from sourceURL: URL) throws -> URL {
|
||||
let baseName = sourceURL.deletingPathExtension().lastPathComponent
|
||||
let outputURL = NotificationToneManager.libraryLocation.appending(component: baseName).appendingPathExtension("caf")
|
||||
let outputURL = URL.customTonesDirectory.appending(component: baseName).appendingPathExtension("caf")
|
||||
|
||||
guard (try? outputURL.checkResourceIsReachable()) != true else {
|
||||
throw ManagerError.fileAlreadyExists
|
||||
@@ -119,7 +121,7 @@ nonisolated struct NotificationToneManager: NotificationToneManagerProtocol {
|
||||
/// - Throws: `DeletionError.notACustomTone` if the tone is not stored in the library directory.
|
||||
func deleteCustomTone(_ alertTone: NotificationTone) throws {
|
||||
let toneLocation = Self.toneLocation(for: alertTone)
|
||||
guard toneLocation.deletingLastPathComponent() == NotificationToneManager.libraryLocation else {
|
||||
guard toneLocation.deletingLastPathComponent() == URL.customTonesDirectory else {
|
||||
throw ManagerError.notACustomTone
|
||||
}
|
||||
|
||||
@@ -172,9 +174,6 @@ nonisolated struct NotificationToneManager: NotificationToneManagerProtocol {
|
||||
MXLog.info("Converted \(sourceURL.path(percentEncoded: false)) to caf")
|
||||
}
|
||||
|
||||
/// File URL of the active tone copied/linked for use by the system.
|
||||
private static let selectedToneLocation = NotificationToneManager.libraryLocation.deletingLastPathComponent().appending(component: selectedToneFilename)
|
||||
|
||||
/// Pre-defined iOS system tones available for selection, sorted by name.
|
||||
private static let defaultSystemAlerts: [NotificationTone] = [
|
||||
.createSystemSound(label: L10n.screenNotificationSettingsSoundSystemTriToneIos,
|
||||
@@ -256,9 +255,9 @@ nonisolated struct NotificationToneManager: NotificationToneManagerProtocol {
|
||||
}
|
||||
.sorted()
|
||||
|
||||
/// Element X bundled tones available for selection, sorted by name.
|
||||
private static let defaultElementXAlerts: [NotificationTone] = [
|
||||
defaultElementXMessageTone,
|
||||
/// Bundled tones available for selection, sorted by name.
|
||||
private static let defaultBundledAlerts: [NotificationTone] = [
|
||||
.default,
|
||||
.createBundledSound(label: L10n.screenNotificationSettingsSoundElementFade,
|
||||
filename: "sound_01.caf")
|
||||
].sorted()
|
||||
@@ -280,7 +279,7 @@ nonisolated struct NotificationToneManager: NotificationToneManagerProtocol {
|
||||
case .appBundle:
|
||||
root = Self.bundledLocation
|
||||
case .appLibrary:
|
||||
root = Self.libraryLocation
|
||||
root = URL.customTonesDirectory
|
||||
}
|
||||
|
||||
return tone.relativePath.reduce(root) {
|
||||
|
||||
@@ -235,9 +235,6 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
|
||||
extension NotificationManager: UNUserNotificationCenterDelegate {
|
||||
func userNotificationCenter(_ center: UNUserNotificationCenter,
|
||||
willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
|
||||
guard appSettings.enableInAppNotifications else {
|
||||
return []
|
||||
}
|
||||
guard let delegate else {
|
||||
return [.badge, .sound, .list, .banner]
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ final class PresenceService {
|
||||
}
|
||||
|
||||
private var desiredPresence: ClientProxyPresence {
|
||||
switch (isForegroundActive, userSettings.app.sharePresence) {
|
||||
switch (isForegroundActive, userSettings.account.sharePresence) {
|
||||
case (true, true):
|
||||
.online
|
||||
case (true, false):
|
||||
@@ -68,7 +68,7 @@ final class PresenceService {
|
||||
}
|
||||
|
||||
private func observeSharePresence() {
|
||||
userSettings.app.sharePresencePublisher
|
||||
userSettings.account.sharePresencePublisher
|
||||
.removeDuplicates()
|
||||
.sink { [weak self] _ in
|
||||
self?.reportCurrentState()
|
||||
|
||||
@@ -128,7 +128,7 @@ class TimelineController: TimelineControllerProtocol {
|
||||
}
|
||||
|
||||
func sendReadReceipt(for itemID: TimelineItemIdentifier) async {
|
||||
let receiptType: MatrixRustSDK.ReceiptType = userSettings.app.sharePresence ? .read : .readPrivate
|
||||
let receiptType: MatrixRustSDK.ReceiptType = userSettings.account.sharePresence ? .read : .readPrivate
|
||||
|
||||
guard let eventID = itemID.eventID else {
|
||||
return
|
||||
|
||||
@@ -57,7 +57,8 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
return .failure(.unknownAccount)
|
||||
}
|
||||
|
||||
let userSettings = appSettings.userSettings(for: credentials.userID)
|
||||
let userSettings = appHooks.userSessionHook.configure(appSettings.userSettings(for: credentials.userID),
|
||||
forHomeserver: credentials.restorationToken.session.homeserverUrl)
|
||||
|
||||
switch await restorePreviousLogin(credentials, userSettings: userSettings) {
|
||||
case .success(let clientProxy):
|
||||
@@ -81,7 +82,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
||||
do {
|
||||
let session = try client.session()
|
||||
let userID = try client.userId()
|
||||
let userSettings = appSettings.userSettings(for: userID)
|
||||
let userSettings = appHooks.userSessionHook.configure(appSettings.userSettings(for: userID), forHomeserver: client.homeserver())
|
||||
let clientProxy = try await setupProxyForClient(client, userSettings: userSettings)
|
||||
|
||||
keychainController.setRestorationToken(RestorationToken(session: session,
|
||||
|
||||
Reference in New Issue
Block a user