Run SwiftFormat.

This commit is contained in:
Doug
2026-05-27 14:38:50 +01:00
committed by Doug
parent 5138e05e47
commit fa67713726
573 changed files with 2426 additions and 2426 deletions
@@ -13,16 +13,16 @@ struct NotificationTone: Hashable, Comparable, Codable {
var label: String {
labelOverride ?? URL(filePath: "/\(relativePath.last, default: "")").deletingPathExtension().lastPathComponent
}
private let labelOverride: String?
let storageLocationRoot: StorageLocation
let relativePath: [String]
/// The audio filename including its extension.
var filename: String {
relativePath.last ?? ""
}
/// - Parameters:
/// - labelOverride: Optional custom display name. If `nil`, the filename stem is used.
/// - storageLocationRoot: Where the file lives (system, bundle, or library).
@@ -32,7 +32,7 @@ struct NotificationTone: Hashable, Comparable, Codable {
self.storageLocationRoot = storageLocationRoot
self.relativePath = relativePath
}
/// Indicates which storage root backs the tone's audio file.
enum StorageLocation: Codable, Hashable {
/// The device's system sounds directory.
@@ -42,22 +42,22 @@ struct NotificationTone: Hashable, Comparable, Codable {
/// The app's Library directory (user-imported tones).
case appLibrary
}
/// 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])
}
/// Creates a tone backed by a file in the app bundle.
static func createBundledSound(label: String?, filename: String) -> NotificationTone {
NotificationTone(labelOverride: label, storageLocationRoot: .appBundle, relativePath: [filename])
}
/// Creates a tone backed by a user-imported file in the app library.
static func createCustomUserSound(filename: String) -> NotificationTone {
NotificationTone(labelOverride: nil, storageLocationRoot: .appLibrary, relativePath: [filename])
}
static func < (lhs: NotificationTone, rhs: NotificationTone) -> Bool {
lhs.label < rhs.label
}
@@ -170,10 +170,10 @@ struct NotificationToneManager: NotificationToneManagerProtocol {
try FileManager.default.moveItem(at: tempURL, to: destURL)
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.screenNotificationSettingsSoundSystemTriTone,
@@ -261,7 +261,7 @@ struct NotificationToneManager: NotificationToneManagerProtocol {
.createBundledSound(label: L10n.screenNotificationSettingsSoundElementFade,
filename: "sound_01.caf")
].sorted()
private static let systemLocation = {
let systemRoot: URL
if let simulatorRoot = ProcessInfo.processInfo.environment["SIMULATOR_ROOT"] {
@@ -271,7 +271,7 @@ struct NotificationToneManager: NotificationToneManagerProtocol {
}
return systemRoot.appending(components: "System", "Library", "Audio", "UISounds", directoryHint: .isDirectory)
}()
private static let bundledLocation: URL = {
guard let url = Bundle.app.resourceURL else {
fatalError("The app is seriously corrupt if resourceURL is missing.")
@@ -15,7 +15,7 @@ protocol AnalyticsClientProtocol {
/// Starts the analytics client reporting data.
func start(analyticsConfiguration: AnalyticsConfiguration)
/// Reset all stored properties and any event queues on the client. Note that
/// the client will remain active, but in a fresh unidentified state.
func reset()
@@ -42,7 +42,7 @@ class AnalyticsService: AnalyticsServiceProtocol {
// The order is important here. PostHog ignores the reset if stopped.
reset()
client.stop()
MXLog.info("Stopped.")
}
@@ -50,7 +50,7 @@ class AnalyticsService: AnalyticsServiceProtocol {
guard isEnabled, !client.isRunning, let configuration = appSettings.analyticsConfiguration else { return }
client.start(analyticsConfiguration: configuration)
// Sanity check in case something went wrong.
guard client.isRunning else { return }
@@ -139,17 +139,17 @@ extension AnalyticsService {
}
capture(event: AnalyticsEvent.JoinedRoom(isDM: isDM, isSpace: isSpace, roomSize: roomSize, trigger: nil))
}
func trackPollCreated(isUndisclosed: Bool, numberOfAnswers: Int) {
capture(event: AnalyticsEvent.PollCreation(action: .Create,
isUndisclosed: isUndisclosed,
numberOfAnswers: numberOfAnswers))
}
func trackPollVote() {
capture(event: AnalyticsEvent.PollVote(doNotUse: nil))
}
func trackPollEnd() {
capture(event: AnalyticsEvent.PollEnd(doNotUse: nil))
}
@@ -185,7 +185,7 @@ extension AnalyticsService {
case .settingUp:
return
}
let event = AnalyticsEvent.CryptoSessionStateChange(recoveryState: analyticsRecoveryState, verificationState: analyticsVerificationState)
client.capture(event)
}
@@ -27,7 +27,7 @@ protocol AnalyticsServiceProtocol: AnyObject {
/// Whether analytics tracking is currently enabled.
var isEnabled: Bool { get }
/// Opts in to analytics tracking.
func optIn()
@@ -45,7 +45,7 @@ protocol AnalyticsServiceProtocol: AnyObject {
/// Resets the consent state for analytics.
func resetConsentState()
/// Track the presentation of a screen.
/// - Parameter screen: The screen that was shown.
/// - Parameter milliseconds: An optional value representing how long the screen was shown for in milliseconds.
@@ -121,11 +121,11 @@ extension AnalyticsServiceProtocol {
func track(screen: AnalyticsEvent.MobileScreen.ScreenName) {
track(screen: screen, duration: nil)
}
func trackInteraction(name: AnalyticsEvent.Interaction.Name) {
trackInteraction(index: nil, name: name)
}
@_disfavoredOverload // make sure this doesn't cause infinite recursion
func trackError(context: String?,
domain: AnalyticsEvent.Error.Domain,
@@ -146,7 +146,7 @@ extension AnalyticsServiceProtocol {
userTrustsOwnIdentity: userTrustsOwnIdentity,
wasVisibleToUser: wasVisibleToUser)
}
func trackComposer(inThread: Bool,
isEditing: Bool,
isReply: Bool,
@@ -49,7 +49,7 @@ class AudioPlayer: NSObject, AudioPlayerProtocol {
private(set) var playbackURL: URL?
private(set) var playbackSpeed: Float = 1.0
private var deinitInProgress = false
var duration: TimeInterval {
@@ -129,14 +129,14 @@ class AudioPlayer: NSObject, AudioPlayerProtocol {
let time = progress * duration
await internalAudioPlayer.seek(to: CMTime(seconds: time, preferredTimescale: 60))
}
func setPlaybackSpeed(_ speed: Float) {
playbackSpeed = speed
if state == .playing {
internalAudioPlayer?.rate = speed
}
}
// MARK: - Private
private func setupAudioSession() {
@@ -178,7 +178,7 @@ class AudioPlayer: NSObject, AudioPlayerProtocol {
playerItem = nil
removeObservers()
}
private func addObservers() {
guard let internalAudioPlayer, let playerItem else {
return
@@ -197,7 +197,7 @@ class AudioPlayer: NSObject, AudioPlayerProtocol {
break
}
}
rateObserver = internalAudioPlayer.observe(\.rate, options: [.old, .new]) { [weak self] _, _ in
guard let self else { return }
@@ -211,7 +211,7 @@ class AudioPlayer: NSObject, AudioPlayerProtocol {
setInternalState(.playing)
}
}
NotificationCenter.default.publisher(for: Notification.Name.AVPlayerItemDidPlayToEndTime)
.sink { [weak self] _ in
guard let self else { return }
@@ -50,7 +50,7 @@ class AudioPlayerState: ObservableObject, Identifiable {
var showProgressIndicator: Bool {
progress > 0
}
var isAttached: Bool {
audioPlayer != nil
}
@@ -124,7 +124,7 @@ class AudioPlayerState: ObservableObject, Identifiable {
playbackSpeed = speed
audioPlayer?.setPlaybackSpeed(speed.rawValue)
}
// MARK: - Private
private func subscribeToAudioPlayer(audioPlayer: AudioPlayerProtocol) {
@@ -246,7 +246,7 @@ class AudioPlayerState: ObservableObject, Identifiable {
}
audioPlayer.pause()
return MPRemoteCommandHandlerStatus.success
}
@@ -37,7 +37,7 @@ class AudioRecorder: AudioRecorderProtocol {
private let maximumRecordingTime: TimeInterval = 1800 // 30 minutes
private let silenceThreshold: Float = -50.0
private var meterLevel: Float = 0
private(set) var audioFileURL: URL?
var currentTime: TimeInterval = .zero
var isRecording: Bool {
@@ -94,7 +94,7 @@ class AudioRecorder: AudioRecorderProtocol {
}
}
}
func averagePower() -> Float {
meterLevel
}
@@ -161,7 +161,7 @@ class AudioRecorder: AudioRecorderProtocol {
// Initialize a new audio engine
let audioEngine = AVAudioEngine()
self.audioEngine = audioEngine
let inputNode = audioEngine.inputNode
let inputFormat = inputNode.outputFormat(forBus: 0)
let hardwareSampleRate = audioEngine.inputNode.outputFormat(forBus: 0).sampleRate
@@ -204,12 +204,12 @@ class AudioRecorder: AudioRecorderProtocol {
} else {
audioConverter = nil
}
// Install tap to process audio buffers coming from the mixer
mixer.installTap(onBus: 0, bufferSize: 1024, format: mixerFormat) { [weak self] buffer, _ in
self?.processAudioBuffer(buffer)
}
do {
try audioEngine.start()
completion(.success(()))
@@ -304,7 +304,7 @@ class AudioRecorder: AudioRecorderProtocol {
// Write the buffer into the audio file
do {
try audioFile.write(from: inputBuffer)
// Compute the sample value for the waveform
updateMeterLevel(inputBuffer)
@@ -366,10 +366,10 @@ class AudioRecorder: AudioRecorderProtocol {
setInternalState(.suspended)
case .ended:
MXLog.info("Interruption ended: \(notification)")
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
do {
try audioEngine?.start()
@@ -413,7 +413,7 @@ class AudioRecorder: AudioRecorderProtocol {
actionsSubject.send(.didStopRecording)
case .error(let error):
cleanupAudioEngine()
actionsSubject.send(.didFailWithError(error: error))
}
}
@@ -25,7 +25,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> {
homeserverSubject.asCurrentValuePublisher()
}
private(set) var flow: AuthenticationFlow
let classicAppAccount: ClassicAppAccount?
@@ -195,7 +195,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
progressSubject.send(completion: .failure(.qrCodeError(.deviceNotSignedIn)))
return progressSubject.asCurrentValuePublisher()
}
// n.b. We deliberatley don't check whether the received server is in our appSettings.accountProviders
let listener = SDKListener { progress in
@@ -382,7 +382,7 @@ extension AuthenticationService {
static var mock: AuthenticationService {
mock(classicAppManager: nil)
}
static func mock(classicAppManager: ClassicAppManagerProtocol?) -> AuthenticationService {
AuthenticationService(userSessionStore: UserSessionStoreMock(.init()),
encryptionKeyProvider: EncryptionKeyProvider(),
@@ -43,7 +43,7 @@ protocol AuthenticationServiceProtocol: QRCodeLoginServiceProtocol {
var homeserver: CurrentValuePublisher<LoginHomeserver, Never> { get }
/// The type of flow the service is currently configured with.
var flow: AuthenticationFlow { get }
/// Sets up the service for login on the specified homeserver address.
func configure(for homeserverAddress: String, flow: AuthenticationFlow) async -> Result<Void, AuthenticationServiceError>
/// Performs login using OAuth for the current homeserver.
@@ -25,7 +25,7 @@ class BugReportService: NSObject, BugReportServiceProtocol {
var isEnabled: Bool {
rageshakeURL != .disabled
}
var lastCrashEventID: String?
init(rageshakeURLPublisher: CurrentValuePublisher<RageshakeConfiguration, Never>,
@@ -45,7 +45,7 @@ class BugReportService: NSObject, BugReportServiceProtocol {
.weakAssign(to: \.rageshakeURL, on: self)
.store(in: &cancellables)
}
// MARK: - BugReportServiceProtocol
var crashedLastRun: Bool {
@@ -110,7 +110,7 @@ class BugReportService: NSObject, BugReportServiceProtocol {
for url in bugReport.files {
params.append(MultipartFormData(key: "file", type: .file(url: url)))
}
let boundary = "Boundary-\(UUID().uuidString)"
var body = Data()
for param in params {
@@ -122,13 +122,13 @@ class BugReportService: NSObject, BugReportServiceProtocol {
}
}
body.appendString(string: "--\(boundary)--\r\n")
var request = URLRequest(url: rageshakeURL)
request.addValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
request.httpBody = body as Data
progressSubject
.receive(on: DispatchQueue.main)
.weakAssign(to: \.value, on: progressListener)
@@ -164,9 +164,9 @@ class BugReportService: NSObject, BugReportServiceProtocol {
return .failure(.uploadFailure(error))
}
}
// MARK: - Private
private var defaultParams: [MultipartFormData] {
let (localTime, utcTime) = localAndUTCTime(for: Date())
let version = "\(InfoPlistReader.main.bundleShortVersionString) (\(InfoPlistReader.main.bundleVersion))"
@@ -185,7 +185,7 @@ class BugReportService: NSObject, BugReportServiceProtocol {
MultipartFormData(key: "base_bundle_identifier", type: .text(value: InfoPlistReader.main.baseBundleIdentifier))
]
}
private func localAndUTCTime(for date: Date) -> (String, String) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
@@ -194,7 +194,7 @@ class BugReportService: NSObject, BugReportServiceProtocol {
let utcTime = dateFormatter.string(from: date)
return (localTime, utcTime)
}
private var os: String {
if ProcessInfo.processInfo.isiOSAppOnMac {
// The other APIs report macOS's equivalent iOS version, so lets use the right one to get the macOS version.
@@ -203,7 +203,7 @@ class BugReportService: NSObject, BugReportServiceProtocol {
"\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)"
}
}
private func zipFiles(_ logFiles: [URL]) async -> Logs {
MXLog.info("zipFiles")
@@ -219,7 +219,7 @@ class BugReportService: NSObject, BugReportServiceProtocol {
}
MXLog.info("zipFiles: originalSize: \(compressedLogs.originalSize), zippedSize: \(compressedLogs.zippedSize)")
return compressedLogs
}
@@ -263,7 +263,7 @@ private extension Data {
append(data)
}
}
mutating func appendParam(_ param: MultipartFormData, boundary: String) throws {
appendString(string: "--\(boundary)\r\n")
appendString(string: "Content-Disposition:form-data; name=\"\(param.key)\"")
@@ -27,7 +27,7 @@ class ClientProxy: ClientProxyProtocol {
private var roomListStateUpdateTaskHandle: TaskHandle?
// periphery: ignore - only for retain
private var roomListStateLoadingStateUpdateTaskHandle: TaskHandle?
private var syncService: SyncService
// periphery: ignore - only for retain
private var syncServiceStateUpdateTaskHandle: TaskHandle?
@@ -46,7 +46,7 @@ class ClientProxy: ClientProxyProtocol {
// periphery:ignore - required for instance retention in the rust codebase
private var mediaPreviewConfigListenerTaskHandle: TaskHandle?
// periphery:ignore - required for instance retention in the rust codebase
private var liveLocationOwnInfoUpdatesListenerTaskHandle: TaskHandle?
@@ -60,7 +60,7 @@ class ClientProxy: ClientProxyProtocol {
private(set) var staticRoomSummaryProvider: StaticRoomSummaryProviderProtocol
let notificationSettings: NotificationSettingsProxyProtocol
let secureBackupController: SecureBackupControllerProtocol
private(set) var sessionVerificationController: SessionVerificationControllerProxyProtocol?
@@ -128,7 +128,7 @@ class ClientProxy: ClientProxyProtocol {
users: [:],
events: [:])
}
private var loadCachedAvatarURLTask: Task<Void, Never>?
private let userAvatarURLSubject = CurrentValueSubject<URL?, Never>(nil)
var userAvatarURLPublisher: CurrentValuePublisher<URL?, Never> {
@@ -191,12 +191,12 @@ class ClientProxy: ClientProxyProtocol {
}
var roomsToAwait: Set<String> = []
private let liveLocationOwnInfoUpdatesSubject = PassthroughSubject<LiveLocationOwnInfoUpdate, Never>()
var liveLocationOwnInfoUpdatesPublisher: AnyPublisher<LiveLocationOwnInfoUpdate, Never> {
liveLocationOwnInfoUpdatesSubject.eraseToAnyPublisher()
}
private let sendQueueStatusSubject = CurrentValueSubject<Bool, Never>(false)
init(client: ClientProtocol,
@@ -240,7 +240,7 @@ class ClientProxy: ClientProxyProtocol {
syncServiceStateUpdateTaskHandle = createSyncServiceStateObserver(syncService)
roomListStateUpdateTaskHandle = createRoomListServiceObserver(roomListService)
roomListStateLoadingStateUpdateTaskHandle = createRoomListLoadingStateUpdateObserver(roomListService)
delegateHandle = try client.setDelegate(delegate: ClientDelegateWrapper { [weak self] isSoftLogout in
self?.hasEncounteredAuthError = true
self?.actionsSubject.send(.receivedAuthError(isSoftLogout: isSoftLogout))
@@ -278,7 +278,7 @@ class ClientProxy: ClientProxyProtocol {
Task {
mediaPreviewConfigListenerTaskHandle = await createMediaPreviewConfigObserver()
}
liveLocationOwnInfoUpdatesListenerTaskHandle = createLiveLocationOwnInfoUpdatesObserver()
}
@@ -290,7 +290,7 @@ class ClientProxy: ClientProxyProtocol {
return "Unknown user identifier"
}
}
var deviceID: String? {
do {
return try client.deviceId()
@@ -299,7 +299,7 @@ class ClientProxy: ClientProxyProtocol {
return nil
}
}
var homeserver: String {
client.homeserver()
}
@@ -360,7 +360,7 @@ class ClientProxy: ClientProxyProtocol {
}
}
}
private(set) lazy var pusherNotificationClientIdentifier: String? = {
// NOTE: The result is stored as part of the restoration token. Any changes
// here would require a migration to correctly match incoming notifications.
@@ -378,7 +378,7 @@ class ClientProxy: ClientProxyProtocol {
return .failure(.sdkError(error))
}
}
func hasDevicesToVerifyAgainst() async -> Result<Bool, ClientProxyError> {
do {
let result = try await client.encryption().hasDevicesToVerifyAgainst()
@@ -388,7 +388,7 @@ class ClientProxy: ClientProxyProtocol {
return .failure(.sdkError(error))
}
}
func startSync() {
guard !hasEncounteredAuthError else {
MXLog.warning("Ignoring request, this client has an unknown token.")
@@ -566,7 +566,7 @@ class ClientProxy: ClientProxyProtocol {
func joinRoom(_ roomID: String, via: [String]) async -> Result<Void, ClientProxyError> {
do {
let _ = try await client.joinRoomByIdOrAlias(roomIdOrAlias: roomID, serverNames: via)
await waitForRoomToSync(roomID: roomID, timeout: .seconds(30))
return .success(())
@@ -652,7 +652,7 @@ class ClientProxy: ClientProxyProtocol {
return .failure(ClientProxyError.sdkError(error))
}
}
func roomForIdentifier(_ identifier: String) async -> RoomProxyType? {
let shouldAwait = roomsToAwait.remove(identifier) != nil
@@ -706,7 +706,7 @@ class ClientProxy: ClientProxyProtocol {
return .failure(.sdkError(error))
}
}
func loadUserDisplayName() async -> Result<Void, ClientProxyError> {
do {
let displayName = try await client.displayName()
@@ -730,7 +730,7 @@ class ClientProxy: ClientProxyProtocol {
return .failure(.sdkError(error))
}
}
func loadUserAvatarURL() async -> Result<Void, ClientProxyError> {
do {
let urlString = try await client.avatarUrl()
@@ -748,7 +748,7 @@ class ClientProxy: ClientProxyProtocol {
MXLog.error("Failed uploading, invalid media: \(media)")
return .failure(.invalidMedia)
}
do {
let data = try Data(contentsOf: imageURL)
try await client.uploadAvatar(mimeType: mimeType, data: data)
@@ -875,7 +875,7 @@ class ClientProxy: ClientProxyProtocol {
return .failure(.sdkError(error))
}
}
func markAllRoomsAsRead() async -> Result<Void, ClientProxyError> {
do {
return try await .success(client.markAllRoomsAsRead())
@@ -903,7 +903,7 @@ class ClientProxy: ClientProxyProtocol {
return .failure(.sdkError(error))
}
}
// MARK: Ignored users
func ignoreUser(_ userID: String) async -> Result<Void, ClientProxyError> {
@@ -1123,7 +1123,7 @@ class ClientProxy: ClientProxyProtocol {
MXLog.error("Failed retrieving session verification controller proxy with error: \(error)")
}
}
private func loadUserAvatarURLFromCache() {
loadCachedAvatarURLTask = Task {
do {
@@ -1172,7 +1172,7 @@ class ClientProxy: ClientProxyProtocol {
return nil
}
}
private func createLiveLocationOwnInfoUpdatesObserver() -> TaskHandle? {
do {
return try client.subscribeToOwnBeaconInfoUpdates(listener: SDKListener { [weak self] update in
@@ -1187,7 +1187,7 @@ class ClientProxy: ClientProxyProtocol {
return nil
}
}
private func createRoomListServiceObserver(_ roomListService: RoomListService) -> TaskHandle {
roomListService.state(listener: SDKListener { [weak self] state in
guard let self else { return }
@@ -1272,7 +1272,7 @@ class ClientProxy: ClientProxyProtocol {
MXLog.info("Wait for \(roomID) failed: \(error)")
}
}
private func updateIgnoredUsers() {
Task {
do {
@@ -1355,7 +1355,7 @@ private final class ClientDelegateWrapper: ClientDelegate {
}
// MARK: - ClientDelegate
func didReceiveAuthError(isSoftLogout: Bool) {
MXLog.error("Received authentication error, softlogout=\(isSoftLogout)")
authErrorCallback(isSoftLogout)
@@ -111,9 +111,9 @@ protocol ClientProxyProtocol: AnyObject {
var homeserverReachabilityPublisher: CurrentValuePublisher<NetworkMonitorReachability, Never> { get }
var userID: String { get }
var deviceID: String? { get }
var homeserver: String { get }
var canDeactivateAccount: Bool { get }
@@ -121,9 +121,9 @@ protocol ClientProxyProtocol: AnyObject {
var userIDServerName: String? { get }
var userDisplayNamePublisher: CurrentValuePublisher<String?, Never> { get }
var userAvatarURLPublisher: CurrentValuePublisher<URL?, Never> { get }
/// We delay fetching this until after the first sync. Nil until then
var ignoredUsersPublisher: CurrentValuePublisher<[String]?, Never> { get }
@@ -170,13 +170,13 @@ protocol ClientProxyProtocol: AnyObject {
func hasDevicesToVerifyAgainst() async -> Result<Bool, ClientProxyError>
func startSync()
func stopSync()
func stopSync(completion: (() -> Void)?) // Hopefully this will become async once we get SE-0371.
func expireSyncSessions() async
func accountURL(action: AccountManagementAction) async -> URL?
func directRoomForUserID(_ userID: String) -> Result<String?, ClientProxyError>
@@ -217,7 +217,7 @@ protocol ClientProxyProtocol: AnyObject {
@discardableResult func loadUserDisplayName() async -> Result<Void, ClientProxyError>
func setUserDisplayName(_ name: String) async -> Result<Void, ClientProxyError>
@discardableResult func loadUserAvatarURL() async -> Result<Void, ClientProxyError>
func setUserAvatar(media: MediaInfo) async -> Result<Void, ClientProxyError>
@@ -229,7 +229,7 @@ protocol ClientProxyProtocol: AnyObject {
func deactivateAccount(password: String?, eraseData: Bool) async -> Result<Void, ClientProxyError>
func logout() async
func setPusher(with configuration: PusherConfiguration) async throws
func searchUsers(searchTerm: String, limit: UInt) async -> Result<SearchUsersResultsProxy, ClientProxyError>
@@ -245,13 +245,13 @@ protocol ClientProxyProtocol: AnyObject {
@discardableResult func clearCaches() async -> Result<Void, ClientProxyError>
@discardableResult func optimizeStores() async -> Result<Void, ClientProxyError>
@discardableResult func markAllRoomsAsRead() async -> Result<Void, ClientProxyError>
func storeSizes() async -> Result<StoreSizes, ClientProxyError>
func fetchMediaPreviewConfiguration() async -> Result<MediaPreviewConfig?, ClientProxyError>
// MARK: - Ignored users
func ignoreUser(_ userID: String) async -> Result<Void, ClientProxyError>
@@ -277,10 +277,10 @@ protocol ClientProxyProtocol: AnyObject {
func userIdentity(for userID: String, fallBackToServer: Bool) async -> Result<UserIdentityProxyProtocol?, ClientProxyError>
// MARK: - Live Location
/// Publishes updates about the current user's own live location beacon info state changes (start/stop) as echoed by the server.
var liveLocationOwnInfoUpdatesPublisher: AnyPublisher<LiveLocationOwnInfoUpdate, Never> { get }
// MARK: - Moderation & Safety
func setTimelineMediaVisibility(_ value: TimelineMediaVisibility) async -> Result<Void, ClientProxyError>
@@ -154,7 +154,7 @@ class ElementCallService: NSObject, ElementCallServiceProtocol, PKPushRegistryDe
}
}
}
// MARK: - PKPushRegistryDelegate
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { }
@@ -12,7 +12,7 @@ struct EmojiItem: Equatable, Identifiable {
var id: String {
label
}
let label: String
let unicode: String
let keywords: [String]
@@ -13,7 +13,7 @@ import MatrixRustSDK
enum KeychainControllerService: String {
case sessions
case tests
var restorationTokenID: String {
InfoPlistReader.main.baseBundleIdentifier + "." + rawValue
}
@@ -33,14 +33,14 @@ final class KeychainController: KeychainControllerProtocol {
case appLockPINCode
case appLockBiometricState
}
init(service: KeychainControllerService, accessGroup: String) {
restorationTokenKeychain = Keychain(service: service.restorationTokenID, accessGroup: accessGroup)
mainKeychain = Keychain(service: service.mainID, accessGroup: accessGroup)
}
// MARK: - Restoration Tokens
func setRestorationToken(_ restorationToken: RestorationToken, forUsername username: String) {
do {
let tokenData = try JSONEncoder().encode(restorationToken)
@@ -49,7 +49,7 @@ final class KeychainController: KeychainControllerProtocol {
MXLog.error("Failed storing user restore token with error: \(error)")
}
}
func restorationTokenForUsername(_ username: String) -> RestorationToken? {
do {
guard let tokenData = try restorationTokenKeychain.getData(username) else {
@@ -66,17 +66,17 @@ final class KeychainController: KeychainControllerProtocol {
return nil
}
}
func restorationTokens() -> [KeychainCredentials] {
restorationTokenKeychain.allKeys().compactMap { username in
guard let restorationToken = restorationTokenForUsername(username) else {
return nil
}
return KeychainCredentials(userID: username, restorationToken: restorationToken)
}
}
func removeRestorationTokenForUsername(_ username: String) {
MXLog.warning("Removing restoration token for user: \(username).")
@@ -86,7 +86,7 @@ final class KeychainController: KeychainControllerProtocol {
MXLog.error("Failed removing restore token with error: \(error)")
}
}
func removeAllRestorationTokens() {
MXLog.warning("Removing all user restoration tokens.")
@@ -13,7 +13,7 @@ extension KeychainControllerMock {
func retrieveSessionFromKeychain(userId: String) throws -> Session {
fatalError("Not implemented")
}
func saveSessionInKeychain(session: Session) {
fatalError("Not implemented")
}
@@ -198,7 +198,7 @@ class LiveLocationManager: NSObject, LiveLocationManagerProtocol, CLLocationMana
.sink { [weak self] sessions in
guard let self else { return }
syncActiveRoomProxies(with: sessions)
if sessions.isEmpty {
self.stopUpdatingLocation()
} else {
@@ -20,7 +20,7 @@ protocol LiveLocationManagerProtocol: AnyObject {
var hasDisplayedLiveLocationDisclaimer: Bool { get set }
/// Publishes the current location authorization status.
var authorizationStatus: CurrentValuePublisher<CLAuthorizationStatus, Never> { get }
/// Requests "Always" location authorization from the user if the system allows it.
///
/// - Returns: `true` if the request was forwarded to the system and a prompt will be shown;
@@ -19,7 +19,7 @@ final class RoomLiveLocationService: RoomLiveLocationServiceProtocol {
var liveLocationsPublisher: CurrentValuePublisher<[LiveLocationShare], Never> {
liveLocationsSubject.asCurrentValuePublisher()
}
init(liveLocationsObserver: LiveLocationsObserver) {
self.liveLocationsObserver = liveLocationsObserver
observationToken = liveLocationsObserver
@@ -358,7 +358,7 @@ struct MediaUploadingPreprocessor {
return .init(url: thumbnailURL, height: thumbnail.size.height, width: thumbnail.size.width, mimeType: "image/jpeg", blurhash: blurhash)
}
private func resizeImage(at url: URL, maxPixelSize: CGFloat, destination: URL, type: UTType) throws(MediaUploadingPreprocessorError) {
guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil) else {
throw .failedResizingImage
@@ -440,7 +440,7 @@ struct MediaUploadingPreprocessor {
private func convertVideoToMP4(_ url: URL, targetFileSize: UInt) async throws(MediaUploadingPreprocessorError) -> VideoProcessingInfo {
let asset = AVURLAsset(url: url)
let presetName = appSettings.optimizeMediaUploads ? AVAssetExportPreset1280x720 : AVAssetExportPreset1920x1080
guard let exportSession = AVAssetExportSession(asset: asset, presetName: presetName) else {
throw .failedConvertingVideo
}
@@ -524,11 +524,11 @@ private extension AVAssetTrack {
guard mediaType == .video else {
return naturalSize
}
// The naturalSize does not take the preferredTransform into consideration resulting
// in portrait videos reporting inverted values.
let transform = try await load(.preferredTransform)
switch (transform.a, transform.b, transform.c, transform.d) {
case (0, 1, -1, 0), (0, -1, 1, 0):
return CGSize(width: naturalSize.height, height: naturalSize.width)
@@ -29,7 +29,7 @@ actor MediaLoader: MediaLoaderProtocol {
// don't have an accompanying ClientMock to own it.
private weak var client: ClientProtocol?
private var ongoingRequests = [MediaSourceProxy: MediaRequest]()
init(client: ClientProtocol) {
self.client = client
}
@@ -18,7 +18,7 @@ enum MediaLoaderError: Error {
// sourcery: AutoMockable
protocol MediaLoaderProtocol: Sendable {
func loadMediaContentForSource(_ source: MediaSourceProxy) async throws -> Data
func loadMediaThumbnailForSource(_ source: MediaSourceProxy, width: UInt, height: UInt) async throws -> Data
func loadMediaFileForSource(_ source: MediaSourceProxy, filename: String?) async throws -> MediaFileHandleProxy
@@ -44,7 +44,7 @@ struct MediaProvider: MediaProviderProtocol {
let image = cacheResult.image {
return .success(image)
}
do {
let imageData: Data
if let size {
@@ -52,14 +52,14 @@ struct MediaProvider: MediaProviderProtocol {
} else {
imageData = try await mediaLoader.loadMediaContentForSource(source)
}
guard let image = UIImage(data: imageData) else {
MXLog.error("Invalid image data")
return .failure(.invalidImageData)
}
try await imageCache.store(image, forKey: cacheKey)
return .success(image)
} catch {
MXLog.error("Failed retrieving image with error: \(error)")
@@ -11,7 +11,7 @@ import Foundation
struct APSAlert: Encodable {
let locKey: String
let locArgs: [String]
enum CodingKeys: String, CodingKey {
case locKey = "loc-key"
case locArgs = "loc-args"
@@ -21,7 +21,7 @@ struct APSAlert: Encodable {
struct APSInfo: Encodable {
let mutableContent: Int
let alert: APSAlert
enum CodingKeys: String, CodingKey {
case mutableContent = "mutable-content"
case alert
@@ -31,7 +31,7 @@ struct APSInfo: Encodable {
struct APNSPayload: Encodable {
let aps: APSInfo
let pusherNotificationClientIdentifier: String?
enum CodingKeys: String, CodingKey {
case aps
case pusherNotificationClientIdentifier = "pusher_notification_client_identifier"
@@ -26,9 +26,9 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
self.appSettings = appSettings
super.init()
}
// MARK: NotificationManagerProtocol
weak var delegate: NotificationManagerDelegate?
func start() {
@@ -80,14 +80,14 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
}
}
}
func register(with deviceToken: Data) async -> Bool {
guard let userSession else {
return false
}
return await setPusher(with: deviceToken, clientProxy: userSession.clientProxy)
}
func setUserSession(_ userSession: UserSessionProtocol?) {
self.userSession = userSession
@@ -106,11 +106,11 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
MXLog.info("Notification sound enabled: \(settings.soundSetting == .enabled)")
}
}
func registrationFailed(with error: Error) {
MXLog.error("Device token registration failed with error: \(error)")
}
func showLocalNotification(with title: String, subtitle: String?) async {
let content = UNMutableNotificationContent()
content.title = title
@@ -150,7 +150,7 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
let lastMessageDate = roomsToLastMessageDates[roomID] else {
return false
}
return notification.date <= lastMessageDate
}
.map(\.request.identifier)
@@ -161,14 +161,14 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
private func removeReceivedWhileOfflineNotification() {
notificationCenter.removeDeliveredNotifications(withIdentifiers: [NotificationServiceExtensionActor.receivedWhileOfflineNotificationID])
}
private func setPusher(with deviceToken: Data, clientProxy: ClientProxyProtocol) async -> Bool {
do {
let defaultPayload = APNSPayload(aps: APSInfo(mutableContent: 1,
alert: APSAlert(locKey: "Notification",
locArgs: [])),
pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier)
let configuration = try await PusherConfiguration(identifiers: .init(pushkey: deviceToken.base64EncodedString(),
appId: appSettings.pusherAppID),
kind: .http(data: .init(url: appSettings.pushGatewayNotifyEndpoint.absoluteString,
@@ -186,7 +186,7 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
return false
}
}
private func pusherProfileTag() -> String {
if let currentTag = appSettings.pusherProfileTag {
return currentTag
@@ -196,7 +196,7 @@ final class NotificationManager: NSObject, NotificationManagerProtocol {
let offset = Int.random(in: 0..<chars.count)
return String(chars[chars.index(chars.startIndex, offsetBy: offset)])
}.joined()
appSettings.pusherProfileTag = newTag
return newTag
}
@@ -224,14 +224,14 @@ extension NotificationManager: UNUserNotificationCenterDelegate {
guard let delegate else {
return [.badge, .sound, .list, .banner]
}
guard delegate.shouldDisplayInAppNotification(content: notification.request.content) else {
return []
}
return [.badge, .sound, .list, .banner]
}
@MainActor
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse) async {
@@ -24,7 +24,7 @@ protocol NotificationManagerDelegate: AnyObject {
// sourcery: AutoMockable
protocol NotificationManagerProtocol: AnyObject {
var delegate: NotificationManagerDelegate? { get set }
func start()
func register(with deviceToken: Data) async -> Bool
func registrationFailed(with error: Error)
@@ -17,12 +17,12 @@ enum NotificationConstants {
static let pusherNotificationClientIdentifier = "pusher_notification_client_identifier"
static let receiverIdentifier = "receiver_id"
}
enum Category {
static let message = "message"
static let invite = "invite"
}
enum Action {
static let inlineReply = "inline-reply"
}
@@ -30,7 +30,7 @@ final class NotificationSettingsProxy: NotificationSettingsProxyProtocol {
private(set) var notificationSettings: MatrixRustSDK.NotificationSettingsProtocol
let callbacks = PassthroughSubject<NotificationSettingsProxyCallback, Never>()
init(notificationSettings: MatrixRustSDK.NotificationSettingsProtocol) {
self.notificationSettings = notificationSettings
notificationSettings.setDelegate(delegate: WeakNotificationSettingsProxy(proxy: self))
@@ -55,7 +55,7 @@ final class NotificationSettingsProxy: NotificationSettingsProxyProtocol {
let roomNotificationMode = await notificationSettings.getDefaultRoomNotificationMode(isEncrypted: isEncrypted, isOneToOne: isOneToOne)
return RoomNotificationModeProxy.from(roomNotificationMode: roomNotificationMode)
}
func setDefaultRoomNotificationMode(isEncrypted: Bool, isOneToOne: Bool, mode: RoomNotificationModeProxy) async throws {
do {
try await notificationSettings.setDefaultRoomNotificationMode(isEncrypted: isEncrypted, isOneToOne: isOneToOne, mode: mode.roomNotificationMode)
@@ -65,7 +65,7 @@ final class NotificationSettingsProxy: NotificationSettingsProxyProtocol {
MXLog.warning("Unable to find the rule: \(ruleId)")
return
}
await updatedSettings()
}
@@ -73,7 +73,7 @@ final class NotificationSettingsProxy: NotificationSettingsProxyProtocol {
try await notificationSettings.restoreDefaultRoomNotificationMode(roomId: roomId)
await updatedSettings()
}
func unmuteRoom(roomId: String, isEncrypted: Bool, isOneToOne: Bool) async throws {
try await notificationSettings.unmuteRoom(roomId: roomId, isEncrypted: isEncrypted, isOneToOne: isOneToOne)
await updatedSettings()
@@ -16,7 +16,7 @@ class BannedRoomProxy: BannedRoomProxyProtocol {
lazy var ownUserID = room.ownUserId()
let info: BaseRoomInfoProxyProtocol
init(room: Room) async throws {
self.room = room
@@ -18,7 +18,7 @@ class InvitedRoomProxy: InvitedRoomProxyProtocol {
let info: BaseRoomInfoProxyProtocol
let inviter: RoomMemberProxyProtocol?
init(room: Room) async throws {
self.room = room
@@ -55,7 +55,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
var infoPublisher: CurrentValuePublisher<RoomInfoProxyProtocol, Never> {
infoSubject.asCurrentValuePublisher()
}
private let membersSubject = CurrentValueSubject<[RoomMemberProxyProtocol], Never>([])
var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> {
membersSubject.asCurrentValuePublisher()
@@ -119,7 +119,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
}
subscribedForUpdates = true
do {
try await roomListService.subscribeToRooms(roomIds: [id])
} catch {
@@ -353,7 +353,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
MXLog.error("Failed updating members using sync API: \(error)")
}
}
func getMember(userID: String) async -> Result<RoomMemberProxyProtocol, RoomProxyError> {
if let member = membersPublisher.value.filter({ $0.userID == userID }).first {
return .success(member)
@@ -396,7 +396,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
return .failure(.sdkError(error))
}
}
func setTopic(_ topic: String) async -> Result<Void, RoomProxyError> {
do {
return try await .success(room.setTopic(topic: topic))
@@ -420,7 +420,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
MXLog.error("Failed uploading avatar, invalid media: \(media)")
return .failure(.invalidMedia)
}
do {
let data = try Data(contentsOf: imageURL)
return try await .success(room.uploadAvatar(mimeType: mimeType, data: data, mediaInfo: nil))
@@ -790,7 +790,7 @@ class JoinedRoomProxy: JoinedRoomProxyProtocol {
return .failure(.sdkError(error))
}
}
// MARK: - Private
private func subscribeToTypingNotifications() {
@@ -16,7 +16,7 @@ class KnockedRoomProxy: KnockedRoomProxyProtocol {
lazy var ownUserID: String = room.ownUserId()
let info: BaseRoomInfoProxyProtocol
init(room: Room) async throws {
self.room = room
@@ -15,28 +15,28 @@ struct RoomInfoProxy: RoomInfoProxyProtocol {
var id: String {
roomInfo.id
}
var creators: [String] {
roomInfo.creators ?? []
}
var displayName: String? {
roomInfo.displayName
}
var rawName: String? {
roomInfo.rawName
}
var topic: String? {
roomInfo.topic
}
/// The room's avatar URL. Use this for editing and favour ``avatar`` for display.
var avatarURL: URL? {
roomInfo.avatarUrl.flatMap(URL.init)
}
/// Here we're assuming unknown rooms are unencrypted.
/// Fortunately https://github.com/matrix-org/matrix-rust-sdk/pull/4778 makes that very much of an edge case and we
/// also automatically start a `latestEncryptionState` fetch if needed.
@@ -53,7 +53,7 @@ struct RoomInfoProxy: RoomInfoProxyProtocol {
var isDM: Bool {
roomInfo.isDm
}
var isSpace: Bool {
roomInfo.isSpace
}
@@ -61,59 +61,59 @@ struct RoomInfoProxy: RoomInfoProxyProtocol {
var successor: SuccessorRoom? {
roomInfo.successorRoom
}
var isFavourite: Bool {
roomInfo.isFavourite
}
var canonicalAlias: String? {
roomInfo.canonicalAlias
}
var alternativeAliases: [String] {
roomInfo.alternativeAliases
}
var membership: Membership {
roomInfo.membership
}
var inviter: RoomMemberProxyProtocol? {
roomInfo.inviter.map(RoomMemberProxy.init)
}
var heroes: [RoomHero] {
roomInfo.heroes
}
var activeMembersCount: Int {
Int(roomInfo.activeMembersCount)
}
var invitedMembersCount: Int {
Int(roomInfo.invitedMembersCount)
}
var joinedMembersCount: Int {
Int(roomInfo.joinedMembersCount)
}
var highlightCount: Int {
Int(roomInfo.highlightCount)
}
var notificationCount: Int {
Int(roomInfo.notificationCount)
}
var cachedUserDefinedNotificationMode: RoomNotificationMode? {
roomInfo.cachedUserDefinedNotificationMode
}
var hasRoomCall: Bool {
roomInfo.hasRoomCall
}
var activeRoomCallIntent: CallIntent? {
switch roomInfo.activeRoomCallConsensusIntent {
case .full(let intent):
@@ -128,35 +128,35 @@ struct RoomInfoProxy: RoomInfoProxyProtocol {
var activeRoomCallParticipants: [String] {
roomInfo.activeRoomCallParticipants
}
var isMarkedUnread: Bool {
roomInfo.isMarkedUnread
}
var unreadMessagesCount: UInt {
UInt(roomInfo.numUnreadMessages)
}
var unreadNotificationsCount: UInt {
UInt(roomInfo.numUnreadNotifications)
}
var unreadMentionsCount: UInt {
UInt(roomInfo.numUnreadMentions)
}
var fullyReadEventID: String? {
roomInfo.fullyReadEventId
}
var pinnedEventIDs: Set<String> {
Set(roomInfo.pinnedEventIds)
}
var joinRule: JoinRule? {
roomInfo.joinRule.map(JoinRule.init)
}
var historyVisibility: RoomHistoryVisibility {
roomInfo.historyVisibility
}
@@ -174,39 +174,39 @@ struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol {
var id: String {
roomPreviewInfo.roomId
}
var displayName: String? {
roomPreviewInfo.name
}
var heroes: [RoomHero] {
roomPreviewInfo.heroes ?? []
}
var topic: String? {
roomPreviewInfo.topic
}
var canonicalAlias: String? {
roomPreviewInfo.canonicalAlias
}
var avatarURL: URL? {
roomPreviewInfo.avatarUrl.flatMap(URL.init)
}
var isDirect: Bool {
roomPreviewInfo.isDirect ?? false
}
var isSpace: Bool {
roomPreviewInfo.roomType == .space
}
var activeMembersCount: Int {
Int(roomPreviewInfo.numActiveMembers ?? roomPreviewInfo.numJoinedMembers)
}
var joinedMembersCount: Int {
Int(roomPreviewInfo.numJoinedMembers)
}
@@ -214,7 +214,7 @@ struct RoomPreviewInfoProxy: BaseRoomInfoProxyProtocol {
var joinRule: JoinRule? {
roomPreviewInfo.joinRule.map(JoinRule.init)
}
var membership: Membership? {
roomPreviewInfo.membership
}
@@ -33,7 +33,7 @@ protocol RoomInfoProxyProtocol: BaseRoomInfoProxyProtocol {
var topic: String? { get }
/// The room's avatar URL. Use this for editing and favour ``avatar`` for display.
var avatarURL: URL? { get }
var isEncrypted: Bool { get }
var isDirect: Bool { get }
var isDM: Bool { get }
@@ -19,7 +19,7 @@ struct RoomMemberDetails: Identifiable, Hashable {
var isIgnored: Bool
var isBanned: Bool
var isActive: Bool
let role: RoomRole
let powerLevel: RoomPowerLevel
@@ -32,7 +32,7 @@ struct RoomPermissionsSetting: Identifiable {
value = newValue.powerLevelValue
}
}
/// The `RoomPermissions` property that this setting is for.
let keyPath: KeyPath<RoomPermissions, Int64>
/// The `RoomPowerLevelChanges` properties that this setting is saved into.
@@ -12,7 +12,7 @@ import MatrixRustSDK
protocol RoomPowerLevelsProxyProtocol {
var values: RoomPowerLevelsValues { get }
var userPowerLevels: [String: Int64] { get }
func canOwnUser(sendMessage messageType: MessageLikeEventType) -> Bool
func canOwnUser(sendStateEvent event: StateEventType) -> Bool
func canOwnUserInvite() -> Bool
@@ -76,7 +76,7 @@ struct RTCDeclinedEvent {
// sourcery: AutoMockable
protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
var infoPublisher: CurrentValuePublisher<RoomInfoProxyProtocol, Never> { get }
var membersPublisher: CurrentValuePublisher<[RoomMemberProxyProtocol], Never> { get }
var typingMembersPublisher: CurrentValuePublisher<[String], Never> { get }
@@ -116,11 +116,11 @@ protocol JoinedRoomProxyProtocol: RoomProxyProtocol {
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError>
func reportRoom(reason: String) async -> Result<Void, RoomProxyError>
func leaveRoom() async -> Result<Void, RoomProxyError>
func updateMembers() async
func getMember(userID: String) async -> Result<RoomMemberProxyProtocol, RoomProxyError>
func invite(userID: String) async -> Result<Void, RoomProxyError>
@@ -222,7 +222,7 @@ extension JoinedRoomProxyProtocol {
isDirect: infoPublisher.value.isDirect,
historySharingState: historySharingState)
}
func members() async -> [RoomMemberProxyProtocol]? {
await updateMembers()
return membersPublisher.value
@@ -49,7 +49,7 @@ extension RoomRole {
self = .user
}
}
var isAdminOrHigher: Bool {
switch self {
case .administrator, .creator, .owner:
@@ -70,7 +70,7 @@ struct RoomMessageEventStringBuilder {
case .other(_, let body):
message = AttributedString(body)
}
if style == .senderPrefixed {
return prefix(message, with: isOutgoing ? L10n.commonYou : senderDisplayName)
} else {
@@ -90,7 +90,7 @@ struct RoomMessageEventStringBuilder {
return message
}
}
private func buildMessage(for style: Style, caption: String?, type: String) -> AttributedString {
guard let caption else {
return AttributedString(type)
@@ -31,7 +31,7 @@ struct RoomSummary {
}
enum LastMessageState { case sending, failed }
let room: Room
let id: String
@@ -66,15 +66,15 @@ struct RoomSummary {
var hasUnreadMessages: Bool {
unreadMessagesCount > 0
}
var hasUnreadMentions: Bool {
unreadMentionsCount > 0
}
var hasUnreadNotifications: Bool {
unreadNotificationsCount > 0
}
var isMuted: Bool {
notificationMode == .mute
}
@@ -17,7 +17,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
private let shouldUpdateVisibleRange: Bool
private let notificationSettings: NotificationSettingsProxyProtocol
private let appSettings: AppSettings
private let roomListPageSize: UInt32
private let serialDispatchQueue: DispatchQueue
@@ -40,7 +40,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
var roomListPublisher: CurrentValuePublisher<[RoomSummary], Never> {
roomListSubject.asCurrentValuePublisher()
}
var statePublisher: CurrentValuePublisher<RoomSummaryProviderState, Never> {
stateSubject.asCurrentValuePublisher()
}
@@ -233,7 +233,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
return updatedItems
}
private func fetchRoomDetails(from room: Room) -> (roomInfo: RoomInfo?, latestEvent: LatestEventValue?) {
class FetchResult {
var roomInfo: RoomInfo?
@@ -76,7 +76,7 @@ class RoomThreadListProxy: RoomThreadListServiceProxyProtocol {
let sender = TimelineItemSender(senderID: threadListItemEvent.sender, senderProfile: threadListItemEvent.senderProfile)
let timestamp = Date(timeIntervalSince1970: TimeInterval(threadListItemEvent.timestamp / 1000))
let message: AttributedString? = if let content = threadListItemEvent.content {
eventStringBuilder.buildAttributedString(for: content,
sender: sender,
@@ -154,7 +154,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
return .failure(.failedConfirmingRecoveryKey)
}
}
func waitForKeyBackupUpload(uploadStateSubject: CurrentValueSubject<SecureBackupSteadyState, Never>) async -> Result<Void, SecureBackupControllerError> {
do {
MXLog.info("Waiting for backup upload steady state")
@@ -42,7 +42,7 @@ enum SecureBackupControllerError: Error {
case failedGeneratingRecoveryKey
case failedConfirmingRecoveryKey
case failedUploadingForBackup
}
@@ -13,7 +13,7 @@ class UserSession: UserSessionProtocol {
private var cancellables = Set<AnyCancellable>()
private var authErrorCancellable: AnyCancellable?
let clientProxy: ClientProxyProtocol
let mediaProvider: MediaProviderProtocol
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
@@ -95,7 +95,7 @@ class SessionVerificationControllerProxy: SessionVerificationControllerProxyProt
return .failure(.failedAcceptingVerificationRequest)
}
}
func requestDeviceVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
MXLog.info("Requesting device verification")
@@ -54,7 +54,7 @@ protocol SessionVerificationControllerProxyProtocol {
func acknowledgeVerificationRequest(details: SessionVerificationRequestDetails) async -> Result<Void, SessionVerificationControllerProxyError>
func acceptVerificationRequest() async -> Result<Void, SessionVerificationControllerProxyError>
func requestDeviceVerification() async -> Result<Void, SessionVerificationControllerProxyError>
func requestUserVerification(_ userID: String) async -> Result<Void, SessionVerificationControllerProxyError>
@@ -32,7 +32,7 @@ final class LeaveSpaceHandleProxy {
true
}
}
var selectedCount: Int {
rooms.count { $0.isSelected }
}
@@ -33,17 +33,17 @@ class SpaceRoomListProxy: SpaceRoomListProxyProtocol {
init(_ spaceRoomList: SpaceRoomListProtocol) async throws {
guard let spaceRoom = spaceRoomList.space() else { throw SpaceRoomListProxyError.missingSpace }
self.spaceRoomList = spaceRoomList
spaceServiceRoomSubject = .init(SpaceServiceRoom(spaceRoom: spaceRoom))
let paginationStateSubject = CurrentValueSubject<SpaceRoomListPaginationState, Never>(spaceRoomList.paginationState())
paginationStatePublisher = paginationStateSubject.asCurrentValuePublisher()
paginationStateHandle = spaceRoomList.subscribeToPaginationStateUpdates(listener: SDKListener { paginationState in
paginationStateSubject.send(paginationState)
})
spaceRoomsHandle = await spaceRoomList.subscribeToRoomUpdate(listener: SDKListener { [weak self] updates in
self?.handleUpdates(updates)
})
@@ -100,7 +100,7 @@ enum RoomTimelineItemFixtures {
TextRoomTimelineItem(text: "How are you?",
senderDisplayName: "Alice")]
}
/// A small chunk of events, containing 2 text items.
static var smallChunkWithReadReceipts: [RoomTimelineItemProtocol] {
[TextRoomTimelineItem(text: "Hey there 👋",
@@ -219,17 +219,17 @@ enum RoomTimelineItemFixtures {
TextRoomTimelineItem(text: "Pork buffalo mollit culpa strip steak in leberkas flank cow.",
senderDisplayName: "Alice")]
}
static var disclosedPolls: [RoomTimelineItemProtocol] {
[PollRoomTimelineItem.mock(poll: .disclosed(), isOutgoing: false),
PollRoomTimelineItem.mock(poll: .endedDisclosed)]
}
static var undisclosedPolls: [RoomTimelineItemProtocol] {
[PollRoomTimelineItem.mock(poll: .undisclosed(), isOutgoing: false),
PollRoomTimelineItem.mock(poll: .endedUndisclosed)]
}
static var outgoingPolls: [RoomTimelineItemProtocol] {
[PollRoomTimelineItem.mock(poll: .disclosed(createdByAccountOwner: true), isOutgoing: true)]
}
@@ -12,7 +12,7 @@ import Foundation
/// A structure that parses a geo URI (i.e. geo:53.99803101552848,-8.25347900390625;u=10) and constructs their constituent parts.
struct GeoURI: Hashable {
// MARK: - Properties
let latitude: Double
let longitude: Double
let uncertainty: Double?
@@ -20,9 +20,9 @@ struct GeoURI: Hashable {
var bodyMessage: String {
"Location was shared at \(string)"
}
// MARK: - Setup
init?(string: String) {
guard let parsedURI = Self.parseGeoURI(from: string) else {
MXLog.warning("\(Self.self) failed to parse the string: \(string)")
@@ -30,13 +30,13 @@ struct GeoURI: Hashable {
}
self = parsedURI
}
init(latitude: Double, longitude: Double, uncertainty: Double? = nil) {
self.latitude = latitude
self.longitude = longitude
self.uncertainty = uncertainty
}
var string: String {
if let uncertainty {
return "geo:\(string(for: latitude)),\(string(for: longitude));u=\(string(for: uncertainty))"
@@ -44,9 +44,9 @@ struct GeoURI: Hashable {
return "geo:\(string(for: latitude)),\(string(for: longitude))"
}
}
// MARK: - Private
/// Parse a geo URI string like "geo:53.99803101552848,-8.25347900390625;u=10"
private static func parseGeoURI(from string: String) -> GeoURI? {
guard
@@ -56,11 +56,11 @@ struct GeoURI: Hashable {
else {
return nil
}
let uncertainty = matchOutput.uncertainty.flatMap(Double.init)
return .init(latitude: latitude, longitude: longitude, uncertainty: uncertainty)
}
private func string(for number: Double) -> String {
NumberFormatter.decimal.string(from: .init(floatLiteral: number)) ?? "\(number)"
}
@@ -27,7 +27,7 @@ class MockTimelineController: TimelineControllerProtocol {
var roomID: String {
roomProxy?.id ?? "MockRoomIdentifier"
}
var timelineKind: TimelineKind
let callbacks = PassthroughSubject<TimelineControllerCallback, Never>()
@@ -43,7 +43,7 @@ class MockTimelineController: TimelineControllerProtocol {
callbacks.send(.updatedTimelineItems(timelineItems: timelineItems, isSwitchingTimelines: false))
}
}
var timelineItemsTimestamp: [TimelineItemIdentifier: Date] = [:]
private var client: UITestsSignalling.Client?
@@ -120,7 +120,7 @@ class MockTimelineController: TimelineControllerProtocol {
func processItemAppearance(_ itemID: TimelineItemIdentifier) async { }
func processItemDisappearance(_ itemID: TimelineItemIdentifier) async { }
func toggleReaction(_ reaction: String, to eventID: TimelineItemIdentifier.EventOrTransactionID) async {
if let timelineProxy {
_ = await timelineProxy.toggleReaction(reaction, to: eventID)
@@ -173,7 +173,7 @@ class MockTimelineController: TimelineControllerProtocol {
func sendHandle(for itemID: TimelineItemIdentifier) -> SendHandleProxy? {
nil
}
func eventTimestamp(for itemID: TimelineItemIdentifier) -> Date? {
timelineItemsTimestamp[itemID] ?? .now
}
@@ -328,7 +328,7 @@ class MockTimelineController: TimelineControllerProtocol {
}
return .success(())
}
// MARK: - UI Test signalling
/// The cancellable used for UI Tests signalling.
@@ -35,7 +35,7 @@ class TimelineController: TimelineControllerProtocol {
callbacks.send(.paginationState(paginationState))
}
}
var roomID: String {
roomProxy.id
}
@@ -128,7 +128,7 @@ class TimelineController: TimelineControllerProtocol {
guard let eventID = itemID.eventID else {
return
}
_ = await activeTimeline.sendReadReceipt(for: eventID, type: receiptType)
}
@@ -571,7 +571,7 @@ class TimelineController: TimelineControllerProtocol {
guard let eventID = timelineItem.id.eventID else {
return
}
switch timelineItem.properties.replyDetails {
case .notLoaded:
activeTimeline.fetchDetails(for: eventID)
@@ -11,7 +11,7 @@ import MatrixRustSDK
struct TimelineControllerFactory: TimelineControllerFactoryProtocol {
let appSettings: AppSettings
func buildTimelineController(roomProxy: JoinedRoomProxyProtocol,
initialFocussedEventID: String?,
timelineItemFactory: RoomTimelineItemFactoryProtocol,
@@ -80,7 +80,7 @@ protocol TimelineControllerProtocol: Sendable {
func removeCaption(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID) async
func toggleReaction(_ reaction: String, to eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID) async
func redact(_ eventOrTransactionID: TimelineItemIdentifier.EventOrTransactionID) async
func pin(eventID: String) async
@@ -12,58 +12,58 @@ import MatrixRustSDK
class TimelineItemProvider: TimelineItemProviderProtocol {
private var cancellables = Set<AnyCancellable>()
private var roomTimelineObservationToken: TaskHandle?
/// Bridge from the SDK's synchronous callback into Swift Concurrency. Yielding is safe from any
/// thread; a single long-lived `for await` consumer (set up in `init`) applies the diffs on the
/// main actor in FIFO order, guaranteeing one in-flight update at a time.
private let diffContinuation: AsyncStream<[TimelineDiff]>.Continuation
private let paginationStateSubject = CurrentValueSubject<TimelinePaginationState, Never>(.initial)
var paginationState: TimelinePaginationState {
paginationStateSubject.value
}
private let itemProxiesSubject: CurrentValueSubject<[TimelineItemProxy], Never>
private(set) var itemProxies: [TimelineItemProxy] = [] {
didSet {
itemProxiesSubject.send(itemProxies)
}
}
var updatePublisher: AnyPublisher<([TimelineItemProxy], TimelinePaginationState), Never> {
itemProxiesSubject
.combineLatest(paginationStateSubject)
.eraseToAnyPublisher()
}
let kind: TimelineKind
private let membershipChangeSubject = PassthroughSubject<Void, Never>()
var membershipChangePublisher: AnyPublisher<Void, Never> {
membershipChangeSubject
.eraseToAnyPublisher()
}
deinit {
diffContinuation.finish()
roomTimelineObservationToken?.cancel()
}
init(timeline: Timeline, kind: TimelineKind, paginationStatePublisher: AnyPublisher<TimelinePaginationState, Never>) {
itemProxiesSubject = CurrentValueSubject<[TimelineItemProxy], Never>([])
self.kind = kind
let (diffStream, diffContinuation) = AsyncStream<[TimelineDiff]>.makeStream()
self.diffContinuation = diffContinuation
paginationStatePublisher
.sink { [weak self] in
self?.paginationStateSubject.send($0)
}
.store(in: &cancellables)
// `for await` guarantees FIFO ordering and that only one diff is being applied at a time.
// The stream also allows to handle the sendable items in the listener.
Task { [weak self] in
@@ -71,7 +71,7 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
self?.updateItemsWithDiffs(diffs)
}
}
Task {
roomTimelineObservationToken = await timeline.addListener(listener: SDKListener { diffs in
diffContinuation.yield(diffs)
@@ -128,11 +128,11 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
changes.append(.insert(offset: Int(index), element: itemProxy, associatedWith: nil))
case .popBack:
guard let itemProxy = itemProxies.last else { fatalError() }
changes.append(.remove(offset: itemProxies.count - 1, element: itemProxy, associatedWith: nil))
case .popFront:
guard let itemProxy = itemProxies.first else { fatalError() }
changes.append(.remove(offset: 0, element: itemProxy, associatedWith: nil))
case .pushBack(let item):
let itemProxy = TimelineItemProxy(item: item)
@@ -154,7 +154,7 @@ class TimelineItemProvider: TimelineItemProviderProtocol {
for (index, itemProxy) in itemProxies.enumerated() {
changes.append(.remove(offset: index, element: itemProxy, associatedWith: nil))
}
for (index, timelineItem) in items.enumerated() {
changes.append(.insert(offset: index, element: TimelineItemProxy(item: timelineItem), associatedWith: nil))
}
@@ -101,11 +101,11 @@ class EventTimelineItemProxy {
}()
lazy var canBeRepliedTo = item.canBeRepliedTo
lazy var content = item.content
lazy var isOwn = item.isOwn
lazy var isEditable = item.isEditable
lazy var sender = TimelineItemSender(senderID: item.sender, senderProfile: item.senderProfile)
@@ -192,11 +192,11 @@ struct SendHandleProxy: Hashable {
}
// MARK: - Hashable
static func == (lhs: SendHandleProxy, rhs: SendHandleProxy) -> Bool {
lhs.itemID == rhs.itemID
}
func hash(into hasher: inout Hasher) {
hasher.combine(itemID)
}
@@ -38,7 +38,7 @@ struct TimelineItemSender: Identifiable, Hashable {
avatarURL: nil)
}
}
var disambiguatedDisplayName: String? {
guard let displayName else {
return nil
@@ -26,11 +26,11 @@ extension EventBasedTimelineItemProtocol {
var description: String {
"\(String(describing: Self.self)): id: \(id), timestamp: \(timestamp), isOutgoing: \(isOutgoing), properties: \(properties)"
}
var isForwardable: Bool {
isRemoteMessage && !(self is PollRoomTimelineItem)
}
var isRemoteMessage: Bool {
id.eventID != nil
}
@@ -50,11 +50,11 @@ extension EventBasedTimelineItemProtocol {
var hasFailedToSend: Bool {
properties.deliveryStatus?.isSendingFailed == true
}
var hasFailedDecryption: Bool {
self is EncryptedRoomTimelineItem
}
var timelineMenuDescription: String {
switch self {
case is VoiceMessageRoomTimelineItem:
@@ -63,7 +63,7 @@ extension EventBasedTimelineItemProtocol {
return body.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
/// The inline area to reserve at the trailing edge of the message text so the
/// bubble's natural size accommodates the timestamp (which is overlaid on the
/// bubble's bottom-trailing corner). Text wraps around this area: if it fits on
@@ -86,7 +86,7 @@ extension EventBasedTimelineItemProtocol {
width += 4
return CGSize(width: ceil(width), height: ceil(height))
}
/// contains the timestamp and an optional edited localised prefix
/// example: (edited) 12:17 PM
var localizedSendInfo: String {
@@ -96,12 +96,12 @@ extension EventBasedTimelineItemProtocol {
}
return start + timestamp.formattedTime()
}
var isCopyable: Bool {
guard let messageBasedItem = self as? EventBasedMessageTimelineItemProtocol else {
return false
}
switch messageBasedItem.contentType {
case .audio, .file, .image, .video, .location, .voice:
return false
@@ -19,7 +19,7 @@ struct AudioRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
let sender: TimelineItemSender
let content: AudioRoomTimelineItemContent
var properties = RoomTimelineItemProperties()
var body: String {
@@ -14,7 +14,7 @@ struct LiveLocationRoomTimelineItem: EventBasedTimelineItemProtocol, Equatable {
var body: String {
""
}
let timestamp: Date
let isOutgoing: Bool
let isEditable: Bool
@@ -16,15 +16,15 @@ struct LocationRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatabl
let canBeRepliedTo: Bool
let sender: TimelineItemSender
let content: LocationRoomTimelineItemContent
var properties = RoomTimelineItemProperties()
var body: String {
content.body
}
var contentType: EventBasedMessageTimelineItemContentType {
.location(content)
}
@@ -12,7 +12,7 @@ struct LocationRoomTimelineItemContent: Hashable {
let body: String
let geoURI: GeoURI?
let kind: StaticLocationKind
init(body: String,
geoURI: GeoURI? = nil,
kind: StaticLocationKind = .sender) {
@@ -18,7 +18,7 @@ struct VideoRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equatable {
var shouldBoost = false
let sender: TimelineItemSender
let content: VideoRoomTimelineItemContent
var properties = RoomTimelineItemProperties()
@@ -18,7 +18,7 @@ struct VoiceMessageRoomTimelineItem: EventBasedMessageTimelineItemProtocol, Equa
let sender: TimelineItemSender
let content: AudioRoomTimelineItemContent
var properties = RoomTimelineItemProperties()
var body: String {
@@ -29,11 +29,11 @@ struct Poll: Hashable {
let endDate: Date?
/// Whether the poll has been created by the account owner
let createdByAccountOwner: Bool
var hasEnded: Bool {
endDate != nil
}
enum Kind: Hashable {
case disclosed
case undisclosed
@@ -42,7 +42,7 @@ struct Poll: Hashable {
var hasMaxSelections: Bool {
options.filter(\.isSelected).count == maxSelections
}
struct Option: Hashable {
let id: String
let text: String
@@ -384,9 +384,9 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
let allVotes = votes.reduce(0) { count, pair in
count + pair.value.count
}
let maxOptionVotes = votes.map(\.value.count).max()
let options = answers.map { answer in
let optionVotesCount = votes[answer.id]?.count
@@ -404,7 +404,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
case .undisclosed:
.undisclosed
}
let poll = Poll(question: question,
kind: pollKind,
maxSelections: Int(maxSelections),
@@ -412,7 +412,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
votes: votes,
endDate: endTime.map { Date(timeIntervalSince1970: TimeInterval($0 / 1000)) },
createdByAccountOwner: eventItemProxy.sender.id == userID)
return PollRoomTimelineItem(id: eventItemProxy.id,
poll: poll,
body: poll.question,
@@ -538,7 +538,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
if let audioWaveform = messageContent.audio?.waveform {
waveform = EstimatedWaveform(data: audioWaveform)
}
return AudioRoomTimelineItemContent(filename: messageContent.filename,
caption: messageContent.caption,
formattedCaption: formattedCaption,
@@ -602,13 +602,13 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
blurhash: messageContent.info?.blurhash,
contentType: UTType(mimeType: messageContent.info?.mimetype, fallbackFilename: messageContent.filename))
}
private func buildLocationTimelineItemContent(_ locationContent: LocationContent) -> LocationRoomTimelineItemContent {
LocationRoomTimelineItemContent(body: locationContent.body,
geoURI: .init(string: locationContent.geoUri),
kind: .init(from: locationContent.asset))
}
private func buildFileTimelineItemContent(_ messageContent: FileMessageContent) -> FileRoomTimelineItemContent {
let htmlCaption = messageContent.formattedCaption?.format == .html ? messageContent.formattedCaption?.body : nil
let formattedCaption = htmlCaption != nil ? attributedStringBuilder.fromHTML(htmlCaption) : attributedStringBuilder.fromPlain(messageContent.caption)
@@ -636,7 +636,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
let name = senderDisplayName ?? senderID
let htmlBody = messageContent.formatted?.format == .html ? messageContent.formatted?.body : nil
var formattedBody: AttributedString?
if let htmlBody {
formattedBody = buildEmoteFormattedBodyFromHTML(html: htmlBody, name: name)
@@ -686,7 +686,7 @@ struct RoomTimelineItemFactory: RoomTimelineItemFactoryProtocol {
return a.count > b.count
}
}
private func buildOrderedReadReceipts(_ receipts: [String: Receipt]) -> [ReadReceipt] {
receipts
.sorted { firstElement, secondElement in
@@ -25,7 +25,7 @@ struct RoomTimelineItemView: View {
context?.send(viewAction: .itemDisappeared(itemID: viewState.identifier))
}
}
@ViewBuilder private var timelineView: some View {
switch viewState.type {
case .text(let item):
@@ -12,17 +12,17 @@ import MatrixRustSDK
final class RoomTimelineItemViewState: Identifiable, Equatable, ObservableObject {
@Published var type: RoomTimelineItemType
@Published var groupStyle: TimelineGroupStyle
/// Contains all the identification info of the item, `uniqueID`, `eventID` and `transactionID`
var identifier: TimelineItemIdentifier {
type.id
}
init(type: RoomTimelineItemType, groupStyle: TimelineGroupStyle) {
self.type = type
self.groupStyle = groupStyle
}
convenience init(item: RoomTimelineItemProtocol, groupStyle: TimelineGroupStyle) {
self.init(type: .init(item: item), groupStyle: groupStyle)
}
@@ -70,7 +70,7 @@ enum RoomTimelineItemType: Equatable {
case callInvite(CallInviteRoomTimelineItem)
case callNotification(CallNotificationRoomTimelineItem)
case liveLocation(LiveLocationRoomTimelineItem)
init(item: RoomTimelineItemProtocol) {
switch item {
case let item as TextRoomTimelineItem:
@@ -123,7 +123,7 @@ enum RoomTimelineItemType: Equatable {
fatalError("Unknown timeline item")
}
}
var id: TimelineItemIdentifier {
switch self {
case .text(let item as RoomTimelineItemProtocol),
@@ -24,7 +24,7 @@ final class TimelineProxy: TimelineProxyProtocol {
private let forwardPaginationStateSubject = CurrentValueSubject<PaginationState, Never>(.endReached)
private let kind: TimelineKind
private var innerTimelineItemProvider: TimelineItemProviderProtocol!
var timelineItemProvider: TimelineItemProviderProtocol {
innerTimelineItemProvider
@@ -146,7 +146,7 @@ final class TimelineProxy: TimelineProxyProtocol {
case .forwards: try await timeline.paginateForwards(numEvents: requestSize)
}
MXLog.info("Finished paginating \(direction.rawValue)")
subject.send(timelineEndReached ? .endReached : .idle)
return .success(())
} catch {
@@ -170,7 +170,7 @@ final class TimelineProxy: TimelineProxyProtocol {
try await timeline.edit(eventOrTransactionId: eventOrTransactionID.rustValue, newContent: newContent)
MXLog.info("Finished editing timeline item: \(eventOrTransactionID)")
return .success(())
} catch {
MXLog.error("Failed editing timeline item: \(eventOrTransactionID) with error: \(error)")
@@ -412,10 +412,10 @@ final class TimelineProxy: TimelineProxyProtocol {
} else {
MXLog.error("Failed sending message with error: \(error)")
}
return .failure(.sdkError(error))
}
return .success(())
}
@@ -473,7 +473,7 @@ final class TimelineProxy: TimelineProxyProtocol {
}
// MARK: - Polls
func createPoll(question: String, answers: [String],
pollKind: Poll.Kind) async -> Result<Void, TimelineProxyError> {
MXLog.info("Creating poll")
@@ -528,7 +528,7 @@ final class TimelineProxy: TimelineProxyProtocol {
return .failure(.sdkError(error))
}
}
func sendPollResponse(pollStartID: String, answers: [String]) async -> Result<Void, TimelineProxyError> {
MXLog.info("Sending response for poll with eventID: \(pollStartID)")
@@ -543,7 +543,7 @@ final class TimelineProxy: TimelineProxyProtocol {
return .failure(.sdkError(error))
}
}
func buildMessageContentFor(_ message: String,
html: String?,
intentionalMentions: Mentions) -> RoomMessageEventContentWithoutRelation {
@@ -15,7 +15,7 @@ struct SessionDirectories: Hashable, Codable {
var dataPath: String {
dataDirectory.path(percentEncoded: false)
}
var cachePath: String {
cacheDirectory.path(percentEncoded: false)
}
@@ -20,7 +20,7 @@ class UserSessionStore: UserSessionStoreProtocol {
var hasSessions: Bool {
!keychainController.restorationTokens().isEmpty
}
/// All the user IDs managed by the store.
var userIDs: [String] {
keychainController.restorationTokens().map(\.userID)
@@ -100,7 +100,7 @@ class UserSessionStore: UserSessionStoreProtocol {
credentials.restorationToken.sessionDirectories.delete()
}
}
// MARK: - Private
private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) async -> UserSessionProtocol {
@@ -14,10 +14,10 @@ final class UserDiscoveryService: UserDiscoveryServiceProtocol {
init(clientProxy: ClientProxyProtocol) {
self.clientProxy = clientProxy
}
func searchProfiles(with searchQuery: String) async -> Result<[UserProfileProxy], UserDiscoveryErrorType> {
async let queriedProfile = profileIfPossible(with: searchQuery)
do {
async let searchedUsers = clientProxy.searchUsers(searchTerm: searchQuery, limit: 10).get()
let users = try await merge(queriedProfile: queriedProfile, searchResults: searchedUsers)
@@ -31,18 +31,18 @@ final class UserDiscoveryService: UserDiscoveryServiceProtocol {
}
}
}
private func merge(queriedProfile: UserProfileProxy?, searchResults: SearchUsersResultsProxy) -> [UserProfileProxy] {
let searchResults = searchResults.results
guard let queriedProfile else {
return searchResults
}
let filteredSearchResult = searchResults.filter {
$0.userID != queriedProfile.userID
}
return [queriedProfile] + filteredSearchResult
}
@@ -56,7 +56,7 @@ final class UserDiscoveryService: UserDiscoveryServiceProtocol {
// fallback to a "local profile" if the profile api fails
return getProfileResult ?? .init(userID: searchQuery)
}
private func filterAccountOwner(_ profiles: [UserProfileProxy]) -> [UserProfileProxy] {
let accountOwnerID = clientProxy.userID
return profiles.filter { $0.userID != accountOwnerID }
@@ -38,7 +38,7 @@ class VoiceMessageCache: VoiceMessageCacheProtocol {
}
return .success(url)
}
func clearCache() {
if FileManager.default.fileExists(atPath: temporaryFilesFolderURL.path) {
do {
@@ -29,7 +29,7 @@ protocol VoiceMessageCacheProtocol {
/// - move: wheter to move or copy the source file
/// - Returns: the cached URL
func cache(mediaSource: MediaSourceProxy, using fileURL: URL, move: Bool) -> Result<URL, VoiceMessageCacheError>
/// Clears the cache
func clearCache()
}
@@ -36,7 +36,7 @@ class VoiceMessageMediaManager: VoiceMessageMediaManagerProtocol {
self.audioConverter = audioConverter
self.processingQueue = processingQueue
}
deinit {
voiceMessageCache.clearCache()
}
@@ -50,7 +50,7 @@ class VoiceMessageMediaManager: VoiceMessageMediaManagerProtocol {
if let fileURL = voiceMessageCache.fileURL(for: source) {
return fileURL
}
// Otherwise, load the file from source
guard case .success(let fileHandle) = await mediaProvider.loadFileFromSource(source, filename: body) else {
throw MediaProviderError.failedRetrievingFile
@@ -61,14 +61,14 @@ class VoiceMessageMediaManager: VoiceMessageMediaManagerProtocol {
if let fileURL = voiceMessageCache.fileURL(for: source) {
return fileURL
}
// Convert from ogg
guard let url = fileHandle.url else {
throw VoiceMessageMediaManagerError.missingURL
}
let convertedFileURL = URL.temporaryDirectory.appendingPathComponent(url.deletingPathExtension().lastPathComponent).appendingPathExtension(AudioConverterPreferredFileExtension.mpeg4aac.rawValue)
try audioConverter.convertToMPEG4AAC(sourceURL: url, destinationURL: convertedFileURL)
// Cache the file and return the url
let result = voiceMessageCache.cache(mediaSource: source, using: convertedFileURL, move: true)
switch result {
@@ -20,7 +20,7 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
var actions: AnyPublisher<VoiceMessageRecorderAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
private let mp4accMimeType = "audio/m4a"
var isRecording: Bool {
@@ -36,11 +36,11 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
}
private var recordingCancelled = false
private(set) var previewAudioPlayerState: AudioPlayerState?
private(set) var previewAudioPlayer: AudioPlayerProtocol?
private var cancellables = Set<AnyCancellable>()
init(audioRecorder: AudioRecorderProtocol = AudioRecorder(),
mediaPlayerProvider: MediaPlayerProviderProtocol,
voiceMessageCache: VoiceMessageCacheProtocol = VoiceMessageCache()) {
@@ -86,7 +86,7 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
previewAudioPlayer?.reset()
previewAudioPlayerState = nil
}
// MARK: - Preview
func startPlayback() async -> Result<Void, VoiceMessageRecorderError> {
@@ -158,13 +158,13 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
// delete the temporary file
try? FileManager.default.removeItem(at: oggFile)
}
do {
try audioConverter.convertToOpusOgg(sourceURL: url, destinationURL: oggFile)
} catch {
return .failure(.failedSendingVoiceMessage)
}
// send it
let size: UInt64
do {
@@ -189,7 +189,7 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
return .success(())
}
// MARK: - Private
private func addObservers() {
@@ -237,10 +237,10 @@ class VoiceMessageRecorder: VoiceMessageRecorderProtocol {
guard audioRecorder.audioFileURL != nil, audioRecorder.currentTime > 0 else {
return .failure(.previewNotAvailable)
}
// Build the preview audio player state
previewAudioPlayerState = await AudioPlayerState(id: .recorderPreview, title: L10n.commonVoiceMessage, duration: recordingDuration, waveform: EstimatedWaveform(data: []))
// Build the preview audio player
let audioPlayer = await mediaPlayerProvider.player
previewAudioPlayer = audioPlayer
@@ -27,9 +27,9 @@ protocol VoiceMessageRecorderProtocol {
var previewAudioPlayerState: AudioPlayerState? { get }
var isRecording: Bool { get }
var recordingURL: URL? { get }
var actions: AnyPublisher<VoiceMessageRecorderAction, Never> { get }
func startRecording() async
func stopRecording() async
func cancelRecording() async