mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
Custom Notification Tones (#5527)
* added foundation for custom notifications * feat: add localizations for tone names also, if available, include the pro sound in the element x sound list * use a static file location to store the notification tone and update the file at that location when selected * if there's a problem previewing or setting the alert tone, toast the user with the error * rename tone directory * create NotificationToneManager to handle tone business logic * conform to project formatting * assign custom sound to actual notifications * dry up notification generation in the nse * add compilation condition for including notification alert tone and remove from share extension * swift format had some opinions * flatten the notification tone ui, removing system/elementx designations * fix stored file url breaking due to sandbox path changes * tone manager can import custom tones and provide a list of imported tones * ui support for custom tones * can delete custom imported tones * sort the custom tones * add additional preset sounds * can choose caf files for custom sounds and fix location persistence issues * store sound locations with relative links this is a better solution than storing bookmarks since bookmark resolution can be variable and differ from freshly derived urls * add docs for new notification features * run tone conversion in background * resolved several ai found issues * added protocol and mocks for notification tone manager * added tests for notification settings screen vm interactions with the tone manager * add tests for notification tone manager * rename element tone names per feedback * move custom tone option to the top of the notification tone list * rename Tone to Sound for consistency with Android * remove additional bundled tones * restore accidentally deleted Element Default sound file * system alert tones are filtered by what's available on the system prior to finalizing the collection * Add a couple more caf file conversion log statements * Simplify the notification sound conversion logic * Update notification sound strings in sync with Android * update xcodeproj after rebase * remove conditional compilation and include NotificationAlertSound in share extension * Use AudioPlayer instead of custom tone previewer and remove the previewer * rename NotificationTone * Consolidate and move errors to top of NotificationToneManager * tone manager no longer depends on userinterfacecontroller * Move static Tone properties to NotificationToneManager * resolve view model initializer feedback * update xcodeproj * fatalerror when tone manager cannot init * Remove banner icon on alert tone error * fixup review feedback * Moved URL handling to NotificationToneManager
This commit is contained in:
@@ -15382,6 +15382,269 @@ class NotificationSettingsProxyMock: NotificationSettingsProxyProtocol, @uncheck
|
||||
}
|
||||
}
|
||||
}
|
||||
class NotificationToneManagerMock: NotificationToneManagerProtocol, @unchecked Sendable {
|
||||
|
||||
//MARK: - setSelectedTone
|
||||
|
||||
var setSelectedToneThrowableError: Error?
|
||||
var setSelectedToneUnderlyingCallsCount = 0
|
||||
var setSelectedToneCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return setSelectedToneUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = setSelectedToneUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
setSelectedToneUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
setSelectedToneUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var setSelectedToneCalled: Bool {
|
||||
return setSelectedToneCallsCount > 0
|
||||
}
|
||||
var setSelectedToneReceivedAlertTone: NotificationTone?
|
||||
var setSelectedToneReceivedInvocations: [NotificationTone] = []
|
||||
|
||||
var setSelectedToneUnderlyingReturnValue: URL!
|
||||
var setSelectedToneReturnValue: URL! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return setSelectedToneUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: URL? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = setSelectedToneUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
setSelectedToneUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
setSelectedToneUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var setSelectedToneClosure: ((NotificationTone) throws -> URL)?
|
||||
|
||||
@discardableResult
|
||||
func setSelectedTone(_ alertTone: NotificationTone) throws -> URL {
|
||||
if let error = setSelectedToneThrowableError {
|
||||
throw error
|
||||
}
|
||||
setSelectedToneCallsCount += 1
|
||||
setSelectedToneReceivedAlertTone = alertTone
|
||||
DispatchQueue.main.async {
|
||||
self.setSelectedToneReceivedInvocations.append(alertTone)
|
||||
}
|
||||
if let setSelectedToneClosure = setSelectedToneClosure {
|
||||
return try setSelectedToneClosure(alertTone)
|
||||
} else {
|
||||
return setSelectedToneReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - customTones
|
||||
|
||||
var customTonesUnderlyingCallsCount = 0
|
||||
var customTonesCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return customTonesUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = customTonesUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
customTonesUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
customTonesUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var customTonesCalled: Bool {
|
||||
return customTonesCallsCount > 0
|
||||
}
|
||||
|
||||
var customTonesUnderlyingReturnValue: [NotificationTone]!
|
||||
var customTonesReturnValue: [NotificationTone]! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return customTonesUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: [NotificationTone]? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = customTonesUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
customTonesUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
customTonesUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var customTonesClosure: (() -> [NotificationTone])?
|
||||
|
||||
func customTones() -> [NotificationTone] {
|
||||
customTonesCallsCount += 1
|
||||
if let customTonesClosure = customTonesClosure {
|
||||
return customTonesClosure()
|
||||
} else {
|
||||
return customTonesReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - deleteCustomTone
|
||||
|
||||
var deleteCustomToneThrowableError: Error?
|
||||
var deleteCustomToneUnderlyingCallsCount = 0
|
||||
var deleteCustomToneCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return deleteCustomToneUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = deleteCustomToneUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
deleteCustomToneUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
deleteCustomToneUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var deleteCustomToneCalled: Bool {
|
||||
return deleteCustomToneCallsCount > 0
|
||||
}
|
||||
var deleteCustomToneReceivedAlertTone: NotificationTone?
|
||||
var deleteCustomToneReceivedInvocations: [NotificationTone] = []
|
||||
var deleteCustomToneClosure: ((NotificationTone) throws -> Void)?
|
||||
|
||||
func deleteCustomTone(_ alertTone: NotificationTone) throws {
|
||||
if let error = deleteCustomToneThrowableError {
|
||||
throw error
|
||||
}
|
||||
deleteCustomToneCallsCount += 1
|
||||
deleteCustomToneReceivedAlertTone = alertTone
|
||||
DispatchQueue.main.async {
|
||||
self.deleteCustomToneReceivedInvocations.append(alertTone)
|
||||
}
|
||||
try deleteCustomToneClosure?(alertTone)
|
||||
}
|
||||
//MARK: - addNewToneToLibrary
|
||||
|
||||
var addNewToneToLibraryFromThrowableError: Error?
|
||||
var addNewToneToLibraryFromUnderlyingCallsCount = 0
|
||||
var addNewToneToLibraryFromCallsCount: Int {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return addNewToneToLibraryFromUnderlyingCallsCount
|
||||
} else {
|
||||
var returnValue: Int? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = addNewToneToLibraryFromUnderlyingCallsCount
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
addNewToneToLibraryFromUnderlyingCallsCount = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
addNewToneToLibraryFromUnderlyingCallsCount = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var addNewToneToLibraryFromCalled: Bool {
|
||||
return addNewToneToLibraryFromCallsCount > 0
|
||||
}
|
||||
var addNewToneToLibraryFromReceivedSourceURL: URL?
|
||||
var addNewToneToLibraryFromReceivedInvocations: [URL] = []
|
||||
|
||||
var addNewToneToLibraryFromUnderlyingReturnValue: URL!
|
||||
var addNewToneToLibraryFromReturnValue: URL! {
|
||||
get {
|
||||
if Thread.isMainThread {
|
||||
return addNewToneToLibraryFromUnderlyingReturnValue
|
||||
} else {
|
||||
var returnValue: URL? = nil
|
||||
DispatchQueue.main.sync {
|
||||
returnValue = addNewToneToLibraryFromUnderlyingReturnValue
|
||||
}
|
||||
|
||||
return returnValue!
|
||||
}
|
||||
}
|
||||
set {
|
||||
if Thread.isMainThread {
|
||||
addNewToneToLibraryFromUnderlyingReturnValue = newValue
|
||||
} else {
|
||||
DispatchQueue.main.sync {
|
||||
addNewToneToLibraryFromUnderlyingReturnValue = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var addNewToneToLibraryFromClosure: ((URL) throws -> URL)?
|
||||
|
||||
@NotificationToneManager.ConversionActor
|
||||
@discardableResult
|
||||
func addNewToneToLibrary(from sourceURL: URL) throws -> URL {
|
||||
if let error = addNewToneToLibraryFromThrowableError {
|
||||
throw error
|
||||
}
|
||||
addNewToneToLibraryFromCallsCount += 1
|
||||
addNewToneToLibraryFromReceivedSourceURL = sourceURL
|
||||
DispatchQueue.main.async {
|
||||
self.addNewToneToLibraryFromReceivedInvocations.append(sourceURL)
|
||||
}
|
||||
if let addNewToneToLibraryFromClosure = addNewToneToLibraryFromClosure {
|
||||
return try addNewToneToLibraryFromClosure(sourceURL)
|
||||
} else {
|
||||
return addNewToneToLibraryFromReturnValue
|
||||
}
|
||||
}
|
||||
}
|
||||
class OrientationManagerMock: OrientationManagerProtocol, @unchecked Sendable {
|
||||
|
||||
//MARK: - setOrientation
|
||||
|
||||
Reference in New Issue
Block a user