From f1ad14dedd3e5674073d95f84a716bd2d80c7932 Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 3 Mar 2022 17:29:41 +0000 Subject: [PATCH 1/9] Add UISIDetector --- .../UISIAutoReporter/UISIDetector.swift | 148 ++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 Riot/Managers/UISIAutoReporter/UISIDetector.swift diff --git a/Riot/Managers/UISIAutoReporter/UISIDetector.swift b/Riot/Managers/UISIAutoReporter/UISIDetector.swift new file mode 100644 index 0000000000..e1865d0806 --- /dev/null +++ b/Riot/Managers/UISIAutoReporter/UISIDetector.swift @@ -0,0 +1,148 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import MatrixSDK +import Foundation + +protocol UISIDetectorDelegate: AnyObject { + var reciprocateToDeviceEventType: String { get } + func uisiDetected(source: E2EMessageDetected) + func uisiReciprocateRequest(source: MXEvent) +} + +enum UISIEventSource { + case initialSync + case incrementalSync + case pagination +} + +struct E2EMessageDetected { + let eventId: String + let roomId: String + let senderUserId: String + let senderDeviceId: String + let senderKey: String + let sessionId: String + let source: UISIEventSource + + static func fromEvent(event: MXEvent, roomId: String, source: UISIEventSource) -> E2EMessageDetected { + return E2EMessageDetected( + eventId: event.eventId ?? "", + roomId: roomId, + senderUserId: event.sender, + senderDeviceId: event.content["device_id"] as? String ?? "", + senderKey: event.content["sender_key"] as? String ?? "", + sessionId: event.content["session_id"] as? String ?? "", + source: source + ) + } +} + +extension E2EMessageDetected: Hashable { + func hash(into hasher: inout Hasher) { + hasher.combine(eventId) + hasher.combine(roomId) + } +} + + +class UISIDetector: MXLiveEventListener { + + weak var delegate: UISIDetectorDelegate? + var enabled = false + + private var trackedEvents = [String: (E2EMessageDetected, DispatchSourceTimer)]() + private let dispatchQueue = DispatchQueue(label: "io.element.UISIDetector.queue") + private static let timeoutSeconds = 30 + + + func onLiveEvent(roomId: String, event: MXEvent) { + guard enabled, !event.isEncrypted else { return } + dispatchQueue.async { + self.handleEventReceived(detectorEvent: E2EMessageDetected.fromEvent(event: event, roomId: roomId, source: .incrementalSync)) + } + } + + func onPaginatedEvent(roomId: String, event: MXEvent) { + guard enabled else { return } + dispatchQueue.async { + self.handleEventReceived(detectorEvent: E2EMessageDetected.fromEvent(event: event, roomId: roomId, source: .pagination)) + } + } + + func onEventDecrypted(eventId: String, roomId: String, clearEvent: [AnyHashable: Any]) { + guard enabled else { return } + dispatchQueue.async { + self.unTrack(eventId: eventId, roomId: roomId) + } + } + + func onEventDecryptionError(eventId: String, roomId: String, error: Error) { + guard enabled else { return } + dispatchQueue.async { + if let event = self.unTrack(eventId: eventId, roomId: roomId) { + self.triggerUISI(source: event) + } + } + } + + func onLiveToDeviceEvent(event: MXEvent) { + guard enabled, event.type == delegate?.reciprocateToDeviceEventType else { return } + delegate?.uisiReciprocateRequest(source: event) + } + + private func handleEventReceived(detectorEvent: E2EMessageDetected) { + guard enabled else { return } + let trackedId = Self.trackedEventId(roomId: detectorEvent.roomId, eventId: detectorEvent.eventId) + guard trackedEvents[trackedId] == nil else { + MXLog.warning("## UISIDetector: Event \(detectorEvent.eventId) is already tracked") + return + } + // track it and start timer + let timer = DispatchSource.makeTimerSource(queue: dispatchQueue) + timer.schedule(deadline: .now() + .seconds(Self.timeoutSeconds)) + timer.setEventHandler { [weak self] in + guard let self = self else { return } + self.unTrack(eventId: detectorEvent.eventId, roomId: detectorEvent.roomId) + MXLog.verbose("## UISIDetector: Timeout on \(detectorEvent.eventId)") + self.triggerUISI(source: detectorEvent) + } + trackedEvents[trackedId] = (detectorEvent, timer) + timer.activate() + } + + private func triggerUISI(source: E2EMessageDetected) { + guard enabled else { return } + MXLog.info("## UISIDetector: Unable To Decrypt \(source)") + self.delegate?.uisiDetected(source: source) + } + + @discardableResult private func unTrack(eventId: String, roomId: String) -> E2EMessageDetected? { + let trackedId = Self.trackedEventId(roomId: roomId, eventId: eventId) + guard let (event, timer) = trackedEvents[trackedId] + else { + return nil + } + trackedEvents[trackedId] = nil + timer.cancel() + return event + } + + static func trackedEventId(roomId: String, eventId: String) -> String { + return "\(roomId)-\(eventId)" + } + +} From 2309908c0ead50245f9e2f2a35d3758159493358 Mon Sep 17 00:00:00 2001 From: David Langley Date: Fri, 11 Mar 2022 16:47:08 +0000 Subject: [PATCH 2/9] Add AutoReported, re-work big client interface and hook up AutoReporter. --- Config/BuildSettings.swift | 4 + Riot/Categories/Codable.swift | 26 ++ .../MXBugReportRestClient+Riot.swift | 117 +++++++++ Riot/Categories/Publisher+Riot.swift | 37 +++ .../Settings/RiotSettings+Publisher.swift | 29 +++ Riot/Managers/Settings/RiotSettings.swift | 5 + .../UISIAutoReporter/UISIAutoReporter.swift | 226 ++++++++++++++++++ .../UISIAutoReporter/UISIDetector.swift | 20 +- Riot/Modules/Application/LegacyAppDelegate.m | 26 ++ .../BugReport/BugReportViewController.m | 98 +------- 10 files changed, 494 insertions(+), 94 deletions(-) create mode 100644 Riot/Categories/Codable.swift create mode 100644 Riot/Categories/MXBugReportRestClient+Riot.swift create mode 100644 Riot/Categories/Publisher+Riot.swift create mode 100644 Riot/Managers/Settings/RiotSettings+Publisher.swift create mode 100644 Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 8c7ebba3fa..46856b850f 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -191,6 +191,7 @@ final class BuildSettings: NSObject { static let bugReportEndpointUrlString = "https://riot.im/bugreports" // Use the name allocated by the bug report server static let bugReportApplicationId = "riot-ios" + static let bugReportUISIId = "element-auto-uisi" // MARK: - Integrations @@ -376,6 +377,9 @@ final class BuildSettings: NSObject { // MARK: - Secrets Recovery static let secretsRecoveryAllowReset = true + // MARK: - UISI Autoreporting + static let cryptoUISIAutoReportingEnabled = true + // MARK: - Polls static var pollsEnabled: Bool { diff --git a/Riot/Categories/Codable.swift b/Riot/Categories/Codable.swift new file mode 100644 index 0000000000..b27c9c7ee0 --- /dev/null +++ b/Riot/Categories/Codable.swift @@ -0,0 +1,26 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +extension Encodable { + /// Convenience method to get the json string of an Encodable + var jsonString: String? { + let encoder = JSONEncoder() + guard let jsonData = try? encoder.encode(self) else { return nil } + return String(data: jsonData, encoding: .utf8) + } +} diff --git a/Riot/Categories/MXBugReportRestClient+Riot.swift b/Riot/Categories/MXBugReportRestClient+Riot.swift new file mode 100644 index 0000000000..fd3fe313ec --- /dev/null +++ b/Riot/Categories/MXBugReportRestClient+Riot.swift @@ -0,0 +1,117 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MatrixSDK +import GBDeviceInfo + +extension MXBugReportRestClient { + + @objc static func vc_bugReportRestClient(appName: String) -> MXBugReportRestClient { + guard let client = MXBugReportRestClient(bugReportEndpoint: BuildSettings.bugReportEndpointUrlString) else { + fatalError("Could not create MXBugReportRestClient") + } + + // App info + client.appName = appName + client.version = AppDelegate.theDelegate().appVersion + client.build = AppDelegate.theDelegate().build + + client.deviceModel = GBDeviceInfo.deviceInfo().modelString + client.deviceOS = "\(UIDevice.current.systemName) \(UIDevice.current.systemVersion)" + return client + } + + @objc func vc_sendBugReport( + description: String, + sendLogs: Bool, + sendCrashLog: Bool, + sendFiles: [URL]? = nil, + additionalLabels: [String]? = nil, + customFields: [String: String]? = nil, + progress: ((MXBugReportState, Progress?) -> Void)? = nil, + success: ((String?) -> Void)? = nil, + failure: ((Error?) -> Void)? = nil + ) { + // User info (TODO: handle multi-account and find a way to expose them in rageshake API) + var userInfo = [String: String]() + let mainAccount = MXKAccountManager.shared().accounts.first + if let userId = mainAccount?.mxSession.myUser.userId { + userInfo["user_id"] = userId + } + if let deviceId = mainAccount?.mxSession.matrixRestClient.credentials.deviceId { + userInfo["device_id"] = deviceId + } + + userInfo["locale"] = NSLocale.preferredLanguages[0] + userInfo["default_app_language"] = Bundle.main.preferredLocalizations[0] // The language chosen by the OS + userInfo["app_language"] = Bundle.mxk_language() ?? userInfo["default_app_language"] // The language chosen by the user + + // Application settings + userInfo["lazy_loading"] = MXKAppSettings.standard().syncWithLazyLoadOfRoomMembers ? "ON" : "OFF" + + let currentDate = Date() + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + userInfo["local_time"] = dateFormatter.string(from: currentDate) + + dateFormatter.timeZone = TimeZone(identifier: "UTC") + userInfo["utc_time"] = dateFormatter.string(from: currentDate) + + if let customFields = customFields { + // combine userInfo with custom fields overriding with custom where there is a conflict + userInfo.merge(customFields) { (_, new) in new } + } + others = userInfo + + var labels: [String] = additionalLabels ?? [String]() + // Add a Github label giving information about the version + if var versionLabel = version, let buildLabel = build { + + // If this is not the app store version, be more accurate on the build origin + if buildLabel == VectorL10n.settingsConfigNoBuildInfo { + // This is a debug session from Xcode + versionLabel += "-debug" + } else if !buildLabel.contains("master") { + // This is a Jenkins build. Add the branch and the build number + let buildString = buildLabel.replacingOccurrences(of: " ", with: "-") + versionLabel += "-\(buildString)" + } + labels += [versionLabel] + } + if sendCrashLog { + labels += ["crash"] + } + + var sendDescription = description + if sendCrashLog, + let crashLogFile = MXLogger.crashLog(), + let crashLog = try? String(contentsOfFile: crashLogFile, encoding: .utf8) { + // Append the crash dump to the user description in order to ease triaging of GH issues + sendDescription += "\n\n\n--------------------------------------------------------------------------------\n\n\(crashLog)" + } + + sendBugReport(sendDescription, + sendLogs: sendLogs, + sendCrashLog: sendCrashLog, + sendFiles: sendFiles, + attachGitHubLabels: labels, + progress: progress, + success: success, + failure: failure) + } + +} diff --git a/Riot/Categories/Publisher+Riot.swift b/Riot/Categories/Publisher+Riot.swift new file mode 100644 index 0000000000..6fa9e20510 --- /dev/null +++ b/Riot/Categories/Publisher+Riot.swift @@ -0,0 +1,37 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Combine + +@available(iOS 14.0, *) +extension Publisher { + + /// + /// Buffer upstream items and guarantee a time interval spacing out the published items. + /// - Parameters: + /// - spacingDelay: A delay in seconds to guarantee between emissions + /// - scheduler: The `DispatchQueue` on which to schedule emissions. + /// - Returns: The new wrapped publisher + func bufferAndSpace(spacingDelay: Int, scheduler: DispatchQueue = DispatchQueue.main) -> Publishers.FlatMap< + Publishers.SetFailureType.Output>, DispatchQueue>, Publishers.Buffer.Failure>, + Publishers.Buffer + > { + return buffer(size: .max, prefetch: .byRequest, whenFull: .dropNewest) + .flatMap(maxPublishers: .max(1)) { + Just($0).delay(for: .seconds(spacingDelay), scheduler: scheduler) + } + } +} diff --git a/Riot/Managers/Settings/RiotSettings+Publisher.swift b/Riot/Managers/Settings/RiotSettings+Publisher.swift new file mode 100644 index 0000000000..f1e6f17cb6 --- /dev/null +++ b/Riot/Managers/Settings/RiotSettings+Publisher.swift @@ -0,0 +1,29 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Combine + +extension RiotSettings { + + @available(iOS 13.0, *) + func publisher(for key: String) -> AnyPublisher { + return NotificationCenter.default.publisher(for: .userDefaultValueUpdated) + .filter({ $0.object as? String == key }) + .eraseToAnyPublisher() + } + +} diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index fdff093ce1..581757b730 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -30,6 +30,7 @@ final class RiotSettings: NSObject { static let pinRoomsWithMissedNotificationsOnHome = "pinRoomsWithMissedNotif" static let pinRoomsWithUnreadMessagesOnHome = "pinRoomsWithUnread" static let showAllRoomsInHomeSpace = "showAllRoomsInHomeSpace" + static let enableUISIAutoReporting = "enableUISIAutoReporting" } static let shared = RiotSettings() @@ -146,6 +147,10 @@ final class RiotSettings: NSObject { @UserDefault(key: "enableThreads", defaultValue: false, storage: defaults) var enableThreads + /// Indicates if threads enabled in the timeline. + @UserDefault(key: UserDefaultsKeys.enableUISIAutoReporting, defaultValue: BuildSettings.cryptoUISIAutoReportingEnabled, storage: defaults) + var enableUISIAutoReporting + // MARK: Calls /// Indicate if `allowStunServerFallback` settings has been set once. diff --git a/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift b/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift new file mode 100644 index 0000000000..2b2d8ff41a --- /dev/null +++ b/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift @@ -0,0 +1,226 @@ +// +// Copyright 2021 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import MatrixSDK +import Combine + +struct UISIAutoReportData { + let eventId: String? + let roomId: String? + let senderKey: String? + let deviceId: String? + let source: UISIEventSource? + let userId: String? + let sessionId: String? +} + +extension UISIAutoReportData: Codable { + enum CodingKeys: String, CodingKey { + case eventId = "event_id" + case roomId = "room_id" + case senderKey = "sender_key" + case deviceId = "device_id" + case source + case userId = "user_id" + case sessionId = "session_id" + } +} + +@available(iOS 14.0, *) +@objcMembers class UISIAutoReporter: NSObject, UISIDetectorDelegate { + + struct ReportInfo: Hashable { + let roomId: String + let sessionId: String + } + + static let autoRsRequest = "im.vector.auto_rs_request" + + private let bugReporter: MXBugReportRestClient + private let dispatchQueue = DispatchQueue(label: "io.element.UISIAutoReporter.queue") + // Simple in memory cache of already sent report + private var alreadyReportedUisi = Set() + private let e2eDetectedSubject = PassthroughSubject() + private let matchingRSRequestSubject = PassthroughSubject() + private var cancellables = Set() + private var sessions = [MXSession]() + private var enabled = false { + didSet { + guard oldValue != enabled else { return } + detector.enabled = enabled + } + } + + override init() { + self.bugReporter = MXBugReportRestClient.vc_bugReportRestClient(appName: BuildSettings.bugReportUISIId) + super.init() + // Simple rate limiting, for any rage-shakes emitted we guarantee a spacing between requests. + e2eDetectedSubject + .bufferAndSpace(spacingDelay: 2) + .sink { [weak self] in + guard let self = self else { return } + self.sendRageShake(source: $0) + }.store(in: &cancellables) + + matchingRSRequestSubject + .bufferAndSpace(spacingDelay: 2) + .sink { [weak self] in + guard let self = self else { return } + self.sendMatchingRageShake(source: $0) + }.store(in: &cancellables) + + self.enabled = RiotSettings.shared.enableUISIAutoReporting + RiotSettings.shared.publisher(for: RiotSettings.UserDefaultsKeys.enableUISIAutoReporting) + .sink { [weak self] _ in + guard let self = self else { return } + self.enabled = RiotSettings.shared.enableUISIAutoReporting + } + .store(in: &cancellables) + } + + private lazy var detector: UISIDetector = { + let detector = UISIDetector() + detector.delegate = self + return detector + }() + + + var reciprocateToDeviceEventType: String { + return Self.autoRsRequest + } + + func uisiDetected(source: E2EMessageDetected) { + guard source.source != UISIEventSource.initialSync else { return } + dispatchQueue.async { + let reportInfo = ReportInfo(roomId: source.roomId, sessionId: source.sessionId) + let alreadySent = self.alreadyReportedUisi.contains(reportInfo) + if !alreadySent { + self.alreadyReportedUisi.insert(reportInfo) + self.e2eDetectedSubject.send(source) + } + } + } + + func uisiReciprocateRequest(source: MXEvent) { + guard source.type == Self.autoRsRequest else { return } + self.matchingRSRequestSubject.send(source) + } + + func sendRageShake(source: E2EMessageDetected) { + MXLog.debug("dl sendRageShake") + guard let session = sessions.first else { return } + let uisiData = UISIAutoReportData( + eventId: source.eventId, + roomId: source.roomId, + senderKey: source.senderKey, + deviceId: source.senderDeviceId, + source: source.source, + userId: source.senderUserId, + sessionId: source.sessionId + ).jsonString ?? "" + + self.bugReporter.vc_sendBugReport( + description: "Auto-reporting decryption error", + sendLogs: true, + sendCrashLog: true, + additionalLabels: [ + "Z-UISI", + "ios", + "uisi-recipient" + ], + customFields: ["auto_uisi": uisiData], + success: { reportUrl in + let contentMap = MXUsersDevicesMap() + let content = [ + "event_id": source.eventId, + "room_id": source.roomId, + "session_id": source.sessionId, + "device_id": source.senderDeviceId, + "user_id": source.senderUserId, + "sender_key": source.senderKey, + "recipient_rageshake": reportUrl + ] + contentMap.setObject(content as NSDictionary, forUser: source.senderUserId, andDevice: source.senderDeviceId) + session.matrixRestClient.sendDirectToDevice( + eventType: Self.autoRsRequest, + contentMap: contentMap, + txnId: nil + ) { response in + if response.isFailure { + MXLog.warning("failed to send auto-uisi to device") + } + } + }, + failure: { [weak self] error in + guard let self = self else { return } + self.dispatchQueue.async { + self.alreadyReportedUisi.remove(ReportInfo(roomId: source.roomId, sessionId: source.sessionId)) + } + }) + } + + func sendMatchingRageShake(source: MXEvent) { + MXLog.debug("dl sendMatchingRageShake") + let eventId = source.content["event_id"] as? String + let roomId = source.content["room_id"] as? String + let sessionId = source.content["session_id"] as? String + let deviceId = source.content["device_id"] as? String + let userId = source.content["user_id"] as? String + let senderKey = source.content["sender_key"] as? String + let matchingIssue = source.content["recipient_rageshake"] as? String ?? "" + + + let uisiData = UISIAutoReportData( + eventId: eventId, + roomId: roomId, + senderKey: senderKey, + deviceId: deviceId, + source: nil, + userId: userId, + sessionId: sessionId + ).jsonString ?? "" + + self.bugReporter.vc_sendBugReport( + description: "Auto-reporting decryption error", + sendLogs: true, + sendCrashLog: true, + additionalLabels: [ + "Z-UISI", + "ios", + "uisi-sender" + ], + customFields: [ + "auto_uisi": uisiData, + "recipient_rageshake": matchingIssue + ] + ) + } + + + func add(_ session: MXSession) { + sessions.append(session) + detector.enabled = enabled + session.eventStreamService.add(eventStreamListener: detector) + } + + func remove(_ session: MXSession) { + if let index = sessions.firstIndex(of: session) { + sessions.remove(at: index) + } + session.eventStreamService.remove(eventStreamListener: detector) + } +} diff --git a/Riot/Managers/UISIAutoReporter/UISIDetector.swift b/Riot/Managers/UISIAutoReporter/UISIDetector.swift index e1865d0806..d16dcfd01f 100644 --- a/Riot/Managers/UISIAutoReporter/UISIDetector.swift +++ b/Riot/Managers/UISIAutoReporter/UISIDetector.swift @@ -23,12 +23,14 @@ protocol UISIDetectorDelegate: AnyObject { func uisiReciprocateRequest(source: MXEvent) } -enum UISIEventSource { - case initialSync - case incrementalSync - case pagination +enum UISIEventSource: String { + case initialSync = "INITIAL_SYNC" + case incrementalSync = "INCREMENTAL_SYNC" + case pagination = "PAGINATION" } +extension UISIEventSource: Equatable, Codable { } + struct E2EMessageDetected { let eventId: String let roomId: String @@ -43,9 +45,9 @@ struct E2EMessageDetected { eventId: event.eventId ?? "", roomId: roomId, senderUserId: event.sender, - senderDeviceId: event.content["device_id"] as? String ?? "", - senderKey: event.content["sender_key"] as? String ?? "", - sessionId: event.content["session_id"] as? String ?? "", + senderDeviceId: event.wireContent["device_id"] as? String ?? "", + senderKey: event.wireContent["sender_key"] as? String ?? "", + sessionId: event.wireContent["session_id"] as? String ?? "", source: source ) } @@ -70,14 +72,14 @@ class UISIDetector: MXLiveEventListener { func onLiveEvent(roomId: String, event: MXEvent) { - guard enabled, !event.isEncrypted else { return } + guard enabled, event.isEncrypted, event.clear == nil else { return } dispatchQueue.async { self.handleEventReceived(detectorEvent: E2EMessageDetected.fromEvent(event: event, roomId: roomId, source: .incrementalSync)) } } func onPaginatedEvent(roomId: String, event: MXEvent) { - guard enabled else { return } + guard enabled, event.isEncrypted, event.clear == nil else { return } dispatchQueue.async { self.handleEventReceived(detectorEvent: E2EMessageDetected.fromEvent(event: event, roomId: roomId, source: .pagination)) } diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 628a765082..9293aac791 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -220,6 +220,7 @@ @interface LegacyAppDelegate () *files; if (_screenshot && _sendScreenshot) @@ -347,56 +308,23 @@ - (IBAction)onSendButtonPress:(id)sender files = @[screenShotFile]; } - - // Prepare labels to attach to the GitHub issue - NSMutableArray *gitHubLabels = [NSMutableArray array]; - if (_reportCrash) - { - // Label the GH issue as "crash" - [gitHubLabels addObject:@"crash"]; - } - - // Add a Github label giving information about the version - if (bugReportRestClient.version && bugReportRestClient.build) - { - NSString *build = bugReportRestClient.build; - NSString *versionLabel = bugReportRestClient.version; - - // If this is not the app store version, be more accurate on the build origin - if ([build isEqualToString:[VectorL10n settingsConfigNoBuildInfo]]) - { - // This is a debug session from Xcode - versionLabel = [versionLabel stringByAppendingString:@"-debug"]; - } - else if (build && ![build containsString:@"master"]) - { - // This is a Jenkins build. Add the branch and the build number - NSString *buildString = [build stringByReplacingOccurrencesOfString:@" " withString:@"-"]; - versionLabel = [[versionLabel stringByAppendingString:@"-"] stringByAppendingString:buildString]; - } - - [gitHubLabels addObject:versionLabel]; - } - + NSMutableString *bugReportDescription = [NSMutableString stringWithString:_bugReportDescriptionTextView.text]; - - if (_reportCrash) - { - // Append the crash dump to the user description in order to ease triaging of GH issues - NSString *crashLogFile = [MXLogger crashLog]; - NSString *crashLog = [NSString stringWithContentsOfFile:crashLogFile encoding:NSUTF8StringEncoding error:nil]; - [bugReportDescription appendFormat:@"\n\n\n--------------------------------------------------------------------------------\n\n%@", crashLog]; - } - + // starting a background task to have a bit of extra time in case of user forgets about the report and sends the app to background __block UIBackgroundTaskIdentifier operationBackgroundId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [[UIApplication sharedApplication] endBackgroundTask:operationBackgroundId]; operationBackgroundId = UIBackgroundTaskInvalid; }]; - // Submit - [bugReportRestClient sendBugReport:bugReportDescription sendLogs:_sendLogs sendCrashLog:_reportCrash sendFiles:files attachGitHubLabels:gitHubLabels progress:^(MXBugReportState state, NSProgress *progress) { - + [bugReportRestClient vc_sendBugReportWithDescription:bugReportDescription + sendLogs:_sendLogs + sendCrashLog:_reportCrash + sendFiles:files + additionalLabels:nil + customFields:nil + progress:^(MXBugReportState state, NSProgress *progress) { + switch (state) { case MXBugReportStateProgressZipping: @@ -413,7 +341,7 @@ - (IBAction)onSendButtonPress:(id)sender self.sendingProgress.progress = progress.fractionCompleted; - } success:^{ + } success:^(NSString *reportUrl){ self->bugReportRestClient = nil; From 5c39eb2f51fe23d0a187262de769992eb34f69c1 Mon Sep 17 00:00:00 2001 From: David Langley Date: Wed, 16 Mar 2022 17:47:41 +0000 Subject: [PATCH 3/9] Update detector to match web implementation(only track decryption attempts), add labs flag, increase rate limit spacing. --- Config/BuildSettings.swift | 2 +- Riot/Assets/en.lproj/Vector.strings | 1 + Riot/Generated/Strings.swift | 4 + .../UISIAutoReporter/UISIAutoReporter.swift | 22 ++-- .../UISIAutoReporter/UISIDetector.swift | 124 ++++++------------ .../Modules/Settings/SettingsViewController.m | 29 +++- 6 files changed, 84 insertions(+), 98 deletions(-) diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index 46856b850f..4e9f197b92 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -378,7 +378,7 @@ final class BuildSettings: NSObject { static let secretsRecoveryAllowReset = true // MARK: - UISI Autoreporting - static let cryptoUISIAutoReportingEnabled = true + static let cryptoUISIAutoReportingEnabled = false // MARK: - Polls diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index 19ea216697..31ae6fd656 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -619,6 +619,7 @@ Tap the + to start adding people."; "settings_labs_enable_ringing_for_group_calls" = "Ring for group calls"; "settings_labs_enabled_polls" = "Polls"; "settings_labs_enable_threads" = "Threaded messaging"; +"settings_labs_enable_auto_report_decryption_errors" = "Auto Report Decryption Errors"; "settings_version" = "Version %@"; "settings_olm_version" = "Olm Version %@"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 7b790ae06b..fd4fa8c7f8 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -4919,6 +4919,10 @@ public class VectorL10n: NSObject { public static var settingsLabsE2eEncryptionPromptMessage: String { return VectorL10n.tr("Vector", "settings_labs_e2e_encryption_prompt_message") } + /// Auto Report Decryption Errors + public static var settingsLabsEnableAutoReportDecryptionErrors: String { + return VectorL10n.tr("Vector", "settings_labs_enable_auto_report_decryption_errors") + } /// Ring for group calls public static var settingsLabsEnableRingingForGroupCalls: String { return VectorL10n.tr("Vector", "settings_labs_enable_ringing_for_group_calls") diff --git a/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift b/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift index 2b2d8ff41a..febd2aee38 100644 --- a/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift +++ b/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift @@ -23,7 +23,6 @@ struct UISIAutoReportData { let roomId: String? let senderKey: String? let deviceId: String? - let source: UISIEventSource? let userId: String? let sessionId: String? } @@ -34,7 +33,6 @@ extension UISIAutoReportData: Codable { case roomId = "room_id" case senderKey = "sender_key" case deviceId = "device_id" - case source case userId = "user_id" case sessionId = "session_id" } @@ -48,13 +46,14 @@ extension UISIAutoReportData: Codable { let sessionId: String } - static let autoRsRequest = "im.vector.auto_rs_request" + private static let autoRsRequest = "im.vector.auto_rs_request" + private static let reportSpacing = 60 private let bugReporter: MXBugReportRestClient private let dispatchQueue = DispatchQueue(label: "io.element.UISIAutoReporter.queue") // Simple in memory cache of already sent report private var alreadyReportedUisi = Set() - private let e2eDetectedSubject = PassthroughSubject() + private let e2eDetectedSubject = PassthroughSubject() private let matchingRSRequestSubject = PassthroughSubject() private var cancellables = Set() private var sessions = [MXSession]() @@ -70,14 +69,14 @@ extension UISIAutoReportData: Codable { super.init() // Simple rate limiting, for any rage-shakes emitted we guarantee a spacing between requests. e2eDetectedSubject - .bufferAndSpace(spacingDelay: 2) + .bufferAndSpace(spacingDelay: Self.reportSpacing) .sink { [weak self] in guard let self = self else { return } self.sendRageShake(source: $0) }.store(in: &cancellables) matchingRSRequestSubject - .bufferAndSpace(spacingDelay: 2) + .bufferAndSpace(spacingDelay: Self.reportSpacing) .sink { [weak self] in guard let self = self else { return } self.sendMatchingRageShake(source: $0) @@ -103,8 +102,7 @@ extension UISIAutoReportData: Codable { return Self.autoRsRequest } - func uisiDetected(source: E2EMessageDetected) { - guard source.source != UISIEventSource.initialSync else { return } + func uisiDetected(source: UISIDetectedMessage) { dispatchQueue.async { let reportInfo = ReportInfo(roomId: source.roomId, sessionId: source.sessionId) let alreadySent = self.alreadyReportedUisi.contains(reportInfo) @@ -120,15 +118,14 @@ extension UISIAutoReportData: Codable { self.matchingRSRequestSubject.send(source) } - func sendRageShake(source: E2EMessageDetected) { - MXLog.debug("dl sendRageShake") + func sendRageShake(source: UISIDetectedMessage) { + MXLog.debug("[UISIAutoReporter] sendRageShake") guard let session = sessions.first else { return } let uisiData = UISIAutoReportData( eventId: source.eventId, roomId: source.roomId, senderKey: source.senderKey, deviceId: source.senderDeviceId, - source: source.source, userId: source.senderUserId, sessionId: source.sessionId ).jsonString ?? "" @@ -174,7 +171,7 @@ extension UISIAutoReportData: Codable { } func sendMatchingRageShake(source: MXEvent) { - MXLog.debug("dl sendMatchingRageShake") + MXLog.debug("[UISIAutoReporter] sendMatchingRageShake") let eventId = source.content["event_id"] as? String let roomId = source.content["room_id"] as? String let sessionId = source.content["session_id"] as? String @@ -189,7 +186,6 @@ extension UISIAutoReportData: Codable { roomId: roomId, senderKey: senderKey, deviceId: deviceId, - source: nil, userId: userId, sessionId: sessionId ).jsonString ?? "" diff --git a/Riot/Managers/UISIAutoReporter/UISIDetector.swift b/Riot/Managers/UISIAutoReporter/UISIDetector.swift index d16dcfd01f..ea0561c7ed 100644 --- a/Riot/Managers/UISIAutoReporter/UISIDetector.swift +++ b/Riot/Managers/UISIAutoReporter/UISIDetector.swift @@ -1,4 +1,4 @@ -// +// // Copyright 2021 New Vector Ltd // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,86 +19,76 @@ import Foundation protocol UISIDetectorDelegate: AnyObject { var reciprocateToDeviceEventType: String { get } - func uisiDetected(source: E2EMessageDetected) + func uisiDetected(source: UISIDetectedMessage) func uisiReciprocateRequest(source: MXEvent) } -enum UISIEventSource: String { - case initialSync = "INITIAL_SYNC" - case incrementalSync = "INCREMENTAL_SYNC" - case pagination = "PAGINATION" -} - -extension UISIEventSource: Equatable, Codable { } - -struct E2EMessageDetected { +struct UISIDetectedMessage { let eventId: String let roomId: String let senderUserId: String let senderDeviceId: String let senderKey: String let sessionId: String - let source: UISIEventSource - static func fromEvent(event: MXEvent, roomId: String, source: UISIEventSource) -> E2EMessageDetected { - return E2EMessageDetected( + static func fromEvent(event: MXEvent) -> UISIDetectedMessage { + return UISIDetectedMessage( eventId: event.eventId ?? "", - roomId: roomId, + roomId: event.roomId, senderUserId: event.sender, senderDeviceId: event.wireContent["device_id"] as? String ?? "", senderKey: event.wireContent["sender_key"] as? String ?? "", - sessionId: event.wireContent["session_id"] as? String ?? "", - source: source + sessionId: event.wireContent["session_id"] as? String ?? "" ) } } -extension E2EMessageDetected: Hashable { - func hash(into hasher: inout Hasher) { - hasher.combine(eventId) - hasher.combine(roomId) - } -} - - class UISIDetector: MXLiveEventListener { weak var delegate: UISIDetectorDelegate? var enabled = false - private var trackedEvents = [String: (E2EMessageDetected, DispatchSourceTimer)]() + var initialSyncCompleted = false + private var trackedUISIs = [String: DispatchSourceTimer]() private let dispatchQueue = DispatchQueue(label: "io.element.UISIDetector.queue") - private static let timeoutSeconds = 30 - - - func onLiveEvent(roomId: String, event: MXEvent) { - guard enabled, event.isEncrypted, event.clear == nil else { return } - dispatchQueue.async { - self.handleEventReceived(detectorEvent: E2EMessageDetected.fromEvent(event: event, roomId: roomId, source: .incrementalSync)) - } - } - - func onPaginatedEvent(roomId: String, event: MXEvent) { - guard enabled, event.isEncrypted, event.clear == nil else { return } - dispatchQueue.async { - self.handleEventReceived(detectorEvent: E2EMessageDetected.fromEvent(event: event, roomId: roomId, source: .pagination)) - } - } + private static let gracePeriodSeconds = 30 - func onEventDecrypted(eventId: String, roomId: String, clearEvent: [AnyHashable: Any]) { - guard enabled else { return } + func onSessionStateChanged(state: MXSessionState) { dispatchQueue.async { - self.unTrack(eventId: eventId, roomId: roomId) + self.initialSyncCompleted = state == .running } } - func onEventDecryptionError(eventId: String, roomId: String, error: Error) { - guard enabled else { return } + func onLiveEventDecryptionAttempted(event: MXEvent, result: MXEventDecryptionResult) { + guard enabled, let eventId = event.eventId, let roomId = event.roomId else { return } dispatchQueue.async { - if let event = self.unTrack(eventId: eventId, roomId: roomId) { - self.triggerUISI(source: event) + let trackedId = Self.trackedEventId(roomId: eventId, eventId: roomId) + + if let timer = self.trackedUISIs[trackedId], + result.clearEvent != nil { + // successfully decrypted during grace period, cancel timer. + self.trackedUISIs[trackedId] = nil + timer.cancel() + return + } + + guard self.initialSyncCompleted, + result.clearEvent == nil + else { return } + + // track uisi and report it only if it is not decrypted before grade period ends + let timer = DispatchSource.makeTimerSource(queue: self.dispatchQueue) + timer.schedule(deadline: .now() + .seconds(Self.gracePeriodSeconds)) + timer.setEventHandler { [weak self] in + guard let self = self else { return } + self.trackedUISIs[trackedId] = nil + MXLog.verbose("[UISIDetector] onLiveEventDecryptionAttempted: Timeout on \(eventId)") + self.triggerUISI(source: UISIDetectedMessage.fromEvent(event: event)) } + self.trackedUISIs[trackedId] = timer + timer.activate() } + } func onLiveToDeviceEvent(event: MXEvent) { @@ -106,45 +96,13 @@ class UISIDetector: MXLiveEventListener { delegate?.uisiReciprocateRequest(source: event) } - private func handleEventReceived(detectorEvent: E2EMessageDetected) { - guard enabled else { return } - let trackedId = Self.trackedEventId(roomId: detectorEvent.roomId, eventId: detectorEvent.eventId) - guard trackedEvents[trackedId] == nil else { - MXLog.warning("## UISIDetector: Event \(detectorEvent.eventId) is already tracked") - return - } - // track it and start timer - let timer = DispatchSource.makeTimerSource(queue: dispatchQueue) - timer.schedule(deadline: .now() + .seconds(Self.timeoutSeconds)) - timer.setEventHandler { [weak self] in - guard let self = self else { return } - self.unTrack(eventId: detectorEvent.eventId, roomId: detectorEvent.roomId) - MXLog.verbose("## UISIDetector: Timeout on \(detectorEvent.eventId)") - self.triggerUISI(source: detectorEvent) - } - trackedEvents[trackedId] = (detectorEvent, timer) - timer.activate() - } - - private func triggerUISI(source: E2EMessageDetected) { + private func triggerUISI(source: UISIDetectedMessage) { guard enabled else { return } - MXLog.info("## UISIDetector: Unable To Decrypt \(source)") + MXLog.info("[UISIDetector] triggerUISI: Unable To Decrypt \(source)") self.delegate?.uisiDetected(source: source) } - - @discardableResult private func unTrack(eventId: String, roomId: String) -> E2EMessageDetected? { - let trackedId = Self.trackedEventId(roomId: roomId, eventId: eventId) - guard let (event, timer) = trackedEvents[trackedId] - else { - return nil - } - trackedEvents[trackedId] = nil - timer.cancel() - return event - } static func trackedEventId(roomId: String, eventId: String) -> String { return "\(roomId)-\(eventId)" } - } diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 00088a4f79..e691be737b 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -159,7 +159,8 @@ typedef NS_ENUM(NSUInteger, LABS_ENABLE) { LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX = 0, LABS_ENABLE_THREADS_INDEX, - LABS_ENABLE_MESSAGE_BUBBLES_INDEX + LABS_ENABLE_MESSAGE_BUBBLES_INDEX, + LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS }; typedef NS_ENUM(NSUInteger, SECURITY) @@ -572,6 +573,7 @@ - (void)updateSections [sectionLabs addRowWithTag:LABS_ENABLE_RINGING_FOR_GROUP_CALLS_INDEX]; [sectionLabs addRowWithTag:LABS_ENABLE_THREADS_INDEX]; [sectionLabs addRowWithTag:LABS_ENABLE_MESSAGE_BUBBLES_INDEX]; + [sectionLabs addRowWithTag:LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS]; sectionLabs.headerTitle = [VectorL10n settingsLabs]; if (sectionLabs.hasAnyRows) { @@ -1490,6 +1492,21 @@ - (UITableViewCell *)buildMessageBubblesCellForTableView:(UITableView*)tableView return labelAndSwitchCell; } +- (UITableViewCell *)buildAutoReportDecryptionErrorsCellForTableView:(UITableView*)tableView + atIndexPath:(NSIndexPath*)indexPath +{ + MXKTableViewCellWithLabelAndSwitch* labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; + + labelAndSwitchCell.mxkLabel.text = [VectorL10n settingsLabsEnableAutoReportDecryptionErrors]; + + labelAndSwitchCell.mxkSwitch.on = RiotSettings.shared.enableUISIAutoReporting; + labelAndSwitchCell.mxkSwitch.onTintColor = ThemeService.shared.theme.tintColor; + labelAndSwitchCell.mxkSwitch.enabled = YES; + [labelAndSwitchCell.mxkSwitch addTarget:self action:@selector(toggleEnableAutoReportDecryptionErrors:) forControlEvents:UIControlEventTouchUpInside]; + + return labelAndSwitchCell; +} + #pragma mark - 3Pid Add - (void)showAuthenticationIfNeededForAdding:(MX3PIDMedium)medium withSession:(MXSession*)session completion:(void (^)(NSDictionary* authParams))completion @@ -2462,6 +2479,10 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N { cell = [self buildMessageBubblesCellForTableView:tableView atIndexPath:indexPath]; } + else if (row == LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS) + { + cell = [self buildAutoReportDecryptionErrorsCellForTableView:tableView atIndexPath:indexPath]; + } } else if (section == SECTION_TAG_FLAIR) { @@ -3890,6 +3911,12 @@ - (void)toggleEnableRoomMessageBubbles:(UISwitch *)sender [roomDataSourceManager reset]; } + +- (void)toggleEnableAutoReportDecryptionErrors:(UISwitch *)sender +{ + RiotSettings.shared.enableUISIAutoReporting = sender.isOn; +} + #pragma mark - TextField listener - (IBAction)textFieldDidChange:(id)sender From 0251a01bb05f9c2139f09847adc2313609a77103 Mon Sep 17 00:00:00 2001 From: David Langley Date: Thu, 17 Mar 2022 09:16:11 +0000 Subject: [PATCH 4/9] fix merge --- Podfile | 8 +- Podfile.lock | 9 +- .../Modules/Settings/SettingsViewController.m | 1 + .../SideMenu/SideMenuCoordinator.swift | 25 +- .../Modules/Common/Mock/MockAppScreens.swift | 2 +- .../TemplateRoomsCoordinator.swift | 8 +- .../TemplateRoomChatCoordinator.swift | 2 +- .../MockTemplateRoomChatScreenState.swift | 2 +- .../MatrixSDK/TemplateRoomChatService.swift | 29 +- .../Mock/MockTemplateRoomChatService.swift | 2 + .../TemplateRoomChatModels.swift | 2 +- .../TemplateRoomChat/View/ImageViewer.swift | 294 ++++++++++++++++++ .../View/TemplateRoomChat.swift | 12 +- .../TemplateRoomChatBubbleContentView.swift | 4 +- .../View/TemplateRoomChatBubbleImage.swift | 21 +- .../View/TemplateRoomChatBubbleView.swift | 4 +- RiotSwiftUI/RiotSwiftUIApp.swift | 2 +- 17 files changed, 391 insertions(+), 36 deletions(-) create mode 100644 RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/ImageViewer.swift diff --git a/Podfile b/Podfile index 07fe178d68..fd699f7d6e 100644 --- a/Podfile +++ b/Podfile @@ -13,9 +13,9 @@ use_frameworks! # - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -$matrixSDKVersion = '= 0.22.6' -# $matrixSDKVersion = :local -# $matrixSDKVersion = { :branch => 'develop'} +# $matrixSDKVersion = '= 0.22.6' + $matrixSDKVersion = :local +#$matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } ######################################## @@ -154,4 +154,4 @@ post_install do |installer| config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['$(inherited)', '-Xcc', '-Wno-nullability-completeness'] end end -end \ No newline at end of file +end diff --git a/Podfile.lock b/Podfile.lock index 1c3e87179e..1a3719d2a0 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -115,8 +115,8 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (= 0.22.6) - - MatrixSDK/JingleCallStack (= 0.22.6) + - MatrixSDK (from `../matrix-ios-sdk/MatrixSDK.podspec`) + - MatrixSDK/JingleCallStack (from `../matrix-ios-sdk/MatrixSDK.podspec`) - OLMKit - PostHog (~> 1.4.4) - ReadMoreTextView (~> 3.0.1) @@ -156,7 +156,6 @@ SPEC REPOS: - libPhoneNumber-iOS - LoggerAPI - Logging - - MatrixSDK - OLMKit - PostHog - ReadMoreTextView @@ -177,6 +176,8 @@ EXTERNAL SOURCES: AnalyticsEvents: :branch: release/swift :git: https://github.com/matrix-org/matrix-analytics-events.git + MatrixSDK: + :path: "../matrix-ios-sdk/MatrixSDK.podspec" CHECKOUT OPTIONS: AnalyticsEvents: @@ -225,6 +226,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: 16aaf5e59ec902619fbfd799939f044728a92ab7 +PODFILE CHECKSUM: add4568acff884a72cf19933027bbc5507725185 COCOAPODS: 1.11.2 diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index ed040f53d5..4cff1dac32 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -2478,6 +2478,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N else if (row == LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS) { cell = [self buildAutoReportDecryptionErrorsCellForTableView:tableView atIndexPath:indexPath]; + } else if (row == LABS_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; diff --git a/Riot/Modules/SideMenu/SideMenuCoordinator.swift b/Riot/Modules/SideMenu/SideMenuCoordinator.swift index 7f5280fd87..962228a0fe 100644 --- a/Riot/Modules/SideMenu/SideMenuCoordinator.swift +++ b/Riot/Modules/SideMenu/SideMenuCoordinator.swift @@ -67,6 +67,7 @@ final class SideMenuCoordinator: NSObject, SideMenuCoordinatorType { private var createSpaceCoordinator: SpaceCreationCoordinator? private var createRoomCoordinator: CreateRoomCoordinator? private var spaceSettingsCoordinator: Coordinator? + private var chatCoordinator: TemplateRoomsCoordinator? // MARK: Public @@ -238,10 +239,28 @@ final class SideMenuCoordinator: NSObject, SideMenuCoordinatorType { } private func showInviteFriends(from sourceView: UIView?) { - let myUserId = self.parameters.userSessionsService.mainUserSession?.userId ?? "" +// let myUserId = self.parameters.userSessionsService.mainUserSession?.userId ?? "" - let inviteFriendsPresenter = InviteFriendsPresenter() - inviteFriendsPresenter.present(for: myUserId, from: self.sideMenuViewController, sourceView: sourceView, animated: true) +// let inviteFriendsPresenter = InviteFriendsPresenter() +// inviteFriendsPresenter.present(for: myUserId, from: self.sideMenuViewController, sourceView: sourceView, animated: true) + + guard let session = self.parameters.userSessionsService.mainUserSession?.matrixSession else { + return + } + let coordinator = TemplateRoomsCoordinator(parameters: TemplateRoomsCoordinatorParameters(session: session)) + coordinator.callback = { [weak self] in + guard let self = self else { return } + + coordinator.toPresentable().dismiss(animated: true) { + self.chatCoordinator = nil + } + } + + let presentable = coordinator.toPresentable() + presentable.presentationController?.delegate = self + toPresentable().present(presentable, animated: true, completion: nil) + coordinator.start() + self.chatCoordinator = coordinator } private func showMenu(forSpaceWithId spaceId: String, from sourceView: UIView?) { diff --git a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift index 3c5909a4ce..963b9954a6 100644 --- a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift +++ b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift @@ -17,7 +17,7 @@ import Foundation /// The static list of mocked screens in RiotSwiftUI -@available(iOS 14.0, *) +@available(iOS 15.0, *) enum MockAppScreens { static let appScreens: [MockScreenState.Type] = [ MockOnboardingCongratulationsScreenState.self, diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift index be453b9487..f593eb32d0 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift @@ -46,7 +46,7 @@ final class TemplateRoomsCoordinator: Coordinator, Presentable { func start() { - if #available(iOS 14.0, *) { + if #available(iOS 15.0, *) { MXLog.debug("[TemplateRoomsCoordinator] did start.") let rootCoordinator = self.createTemplateRoomListCoordinator() rootCoordinator.start() @@ -71,7 +71,7 @@ final class TemplateRoomsCoordinator: Coordinator, Presentable { // MARK: - Private - @available(iOS 14.0, *) + @available(iOS 15.0, *) private func createTemplateRoomListCoordinator() -> TemplateRoomListCoordinator { let coordinator: TemplateRoomListCoordinator = TemplateRoomListCoordinator(parameters: TemplateRoomListCoordinatorParameters(session: parameters.session)) @@ -88,13 +88,13 @@ final class TemplateRoomsCoordinator: Coordinator, Presentable { return coordinator } - @available(iOS 14.0, *) + @available(iOS 15.0, *) private func createTemplateRoomChatCoordinator(room: MXRoom) -> TemplateRoomChatCoordinator { let coordinator: TemplateRoomChatCoordinator = TemplateRoomChatCoordinator(parameters: TemplateRoomChatCoordinatorParameters(room: room)) return coordinator } - @available(iOS 14.0, *) + @available(iOS 15.0, *) func showTemplateRoomChat(roomId: String) { guard let room = parameters.session.room(withRoomId: roomId) else { MXLog.error("[TemplateRoomsCoordinator] Failed to find room by selected Id.") diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift index d9a5e10f23..8b4d9cb86d 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift @@ -38,7 +38,7 @@ final class TemplateRoomChatCoordinator: Coordinator, Presentable { // MARK: - Setup - @available(iOS 14.0, *) + @available(iOS 15.0, *) init(parameters: TemplateRoomChatCoordinatorParameters) { self.parameters = parameters let viewModel = TemplateRoomChatViewModel(templateRoomChatService: TemplateRoomChatService(room: parameters.room)) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift index 925c5c6143..6c7289a27d 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift @@ -19,7 +19,7 @@ import SwiftUI /// Using an enum for the screen allows you define the different state cases with /// the relevant associated data for each case. -@available(iOS 14.0, *) +@available(iOS 15.0, *) enum MockTemplateRoomChatScreenState: MockScreenState, CaseIterable { // A case for each state you want to represent // with specific, minimal associated data that will allow you diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift index 6d231f7447..3dc571939c 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift @@ -16,6 +16,7 @@ import Foundation import Combine +import MatrixSDK @available(iOS 14.0, *) class TemplateRoomChatService: TemplateRoomChatServiceProtocol { @@ -96,20 +97,21 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { return events .filter({ event in event.type == kMXEventTypeStringRoomMessage - && event.content[kMXMessageTypeKey] as? String == kMXMessageTypeText + && (event.content[kMXMessageTypeKey] as? String == kMXMessageTypeText + || event.content[kMXMessageTypeKey] as? String == kMXMessageTypeImage) // TODO: New to our SwiftUI Template? Why not implement another message type like image? }) .compactMap({ event -> TemplateRoomChatMessage? in guard let eventId = event.eventId, - let body = event.content[kMXMessageBodyKey] as? String, let sender = senderForMessage(event: event) else { return nil } + let messageContent = messageContentForEvent(event: event) return TemplateRoomChatMessage( id: eventId, - content: .text(TemplateRoomChatMessageTextContent(body: body)), + content: messageContent, sender: sender, timestamp: Date(timeIntervalSince1970: TimeInterval(event.originServerTs / 1000)) ) @@ -124,4 +126,25 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { let avatarUrl = eventFormatter.senderAvatarUrl(for: event, with: roomState) return TemplateRoomChatMember(id: sender, avatarUrl: avatarUrl, displayName: displayName) } + + private func messageContentForEvent(event: MXEvent) -> TemplateRoomChatMessageContent { + switch event.content[kMXMessageTypeKey] as? String { + case kMXMessageTypeText: + let body = event.content[kMXMessageBodyKey] as? String ?? "" + return .text(TemplateRoomChatMessageTextContent(body: body)) + case kMXMessageTypeImage: + let url: URL +// room.mxSession.mediaManager. + if let contentURL = event.content["url"] as? String, + let info = event.content["info"] as? [String: Any], + let localImagePath = MXMediaManager.cachePath(forMatrixContentURI: contentURL, andType:info["mimetype"] as? String, inFolder: event.roomId) { + url = URL(fileURLWithPath: localImagePath) + } else{ + url = URL(string: "https://cahilldental.ie/wp-content/uploads/2016/10/orionthemes-placeholder-image.png")! + } + return .image(TemplateRoomChatMessageImageContent(url: url)) + default: break + } + fatalError("unsupported event type content") + } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift index 8fa354911d..3134b2d29f 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift @@ -24,11 +24,13 @@ class MockTemplateRoomChatService: TemplateRoomChatServiceProtocol { static let amadine = TemplateRoomChatMember(id: "@amadine:matrix.org", avatarUrl: "!aaabaa:matrix.org", displayName: "Amadine") static let mathew = TemplateRoomChatMember(id: "@mathew:matrix.org", avatarUrl: "!bbabb:matrix.org", displayName: "Mathew") + static let partyImageUrl = URL(string: "https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.com%2Fimages%2F184375039%2F474927372937%2F1%2Foriginal.20211111-155142?w=800&auto=format%2Ccompress&q=75&sharp=10&rect=0%2C236%2C4724%2C2362&s=18c17c71af6df1e5e46d630fac923834")! static let mockMessages = [ TemplateRoomChatMessage(id: "!0:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Shall I put it live?")) , sender: amadine, timestamp: Date(timeIntervalSinceNow: 60 * -3)), TemplateRoomChatMessage(id: "!1:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Yea go for it! ...and then let's head to the pub")), sender: mathew, timestamp: Date(timeIntervalSinceNow: 60)), TemplateRoomChatMessage(id: "!2:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Deal.")), sender: amadine, timestamp: Date(timeIntervalSinceNow: 60 * -2)), TemplateRoomChatMessage(id: "!3:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Ok, Done. 🍻")), sender: amadine, timestamp: Date(timeIntervalSinceNow: 60 * -1)), + TemplateRoomChatMessage(id: "!3:matrix.org", content: .image(TemplateRoomChatMessageImageContent(url: partyImageUrl)), sender: mathew, timestamp: Date(timeIntervalSinceNow: 60 * -1)), ] var roomInitializationStatus: CurrentValueSubject var chatMessagesSubject: CurrentValueSubject<[TemplateRoomChatMessage], Never> diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift index e9aa09141c..5e5310189e 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift @@ -20,7 +20,7 @@ import UIKit /// An image sent as a message. struct TemplateRoomChatMessageImageContent: Equatable { - var image: UIImage + var url: URL } /// The text content of a message sent by a user. diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/ImageViewer.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/ImageViewer.swift new file mode 100644 index 0000000000..37c78dabbd --- /dev/null +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/ImageViewer.swift @@ -0,0 +1,294 @@ +import SwiftUI +import UIKit + +@available(iOS 13.0, *) +public struct ImageViewer: View { + @Binding var viewerShown: Bool + @Binding var image: Image + @Binding var imageOpt: Image? + @State var caption: Text? + @State var closeButtonTopRight: Bool? + + var aspectRatio: Binding? + + @State var dragOffset: CGSize = CGSize.zero + @State var dragOffsetPredicted: CGSize = CGSize.zero + + public init(image: Binding, viewerShown: Binding, aspectRatio: Binding? = nil, caption: Text? = nil, closeButtonTopRight: Bool? = false) { + _image = image + _viewerShown = viewerShown + _imageOpt = .constant(nil) + self.aspectRatio = aspectRatio + _caption = State(initialValue: caption) + _closeButtonTopRight = State(initialValue: closeButtonTopRight) + } + + public init(image: Binding, viewerShown: Binding, aspectRatio: Binding? = nil, caption: Text? = nil, closeButtonTopRight: Bool? = false) { + _image = .constant(Image(systemName: "")) + _imageOpt = image + _viewerShown = viewerShown + self.aspectRatio = aspectRatio + _caption = State(initialValue: caption) + _closeButtonTopRight = State(initialValue: closeButtonTopRight) + } + + func getImage() -> Image { + if(self.imageOpt == nil) { + return self.image + } + else { + return self.imageOpt ?? Image(systemName: "questionmark.diamond") + } + } + + @ViewBuilder + public var body: some View { + VStack { + if(viewerShown) { + ZStack { + VStack { + HStack { + + if self.closeButtonTopRight == true { + Spacer() + } + + Button(action: { self.viewerShown = false }) { + Image(systemName: "xmark") + .foregroundColor(Color(UIColor.white)) + .font(.system(size: UIFontMetrics.default.scaledValue(for: 24))) + } + + if self.closeButtonTopRight != true { + Spacer() + } + } + + Spacer() + } + .padding() + .zIndex(2) + + VStack { + ZStack { + self.getImage() + .resizable() + .aspectRatio(self.aspectRatio?.wrappedValue, contentMode: .fit) + .offset(x: self.dragOffset.width, y: self.dragOffset.height) + .rotationEffect(.init(degrees: Double(self.dragOffset.width / 30))) + .pinchToZoom() + .gesture(DragGesture() + .onChanged { value in + self.dragOffset = value.translation + self.dragOffsetPredicted = value.predictedEndTranslation + } + .onEnded { value in + if((abs(self.dragOffset.height) + abs(self.dragOffset.width) > 570) || ((abs(self.dragOffsetPredicted.height)) / (abs(self.dragOffset.height)) > 3) || ((abs(self.dragOffsetPredicted.width)) / (abs(self.dragOffset.width))) > 3) { + withAnimation(.spring()) { + self.dragOffset = self.dragOffsetPredicted + } + self.viewerShown = false + + return + } + withAnimation(.interactiveSpring()) { + self.dragOffset = .zero + } + } + ) + + if(self.caption != nil) { + VStack { + Spacer() + + VStack { + Spacer() + + HStack { + Spacer() + + self.caption + .foregroundColor(.white) + .multilineTextAlignment(.center) + + Spacer() + } + } + .padding() + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(red: 0.12, green: 0.12, blue: 0.12, opacity: (1.0 - Double(abs(self.dragOffset.width) + abs(self.dragOffset.height)) / 1000)).edgesIgnoringSafeArea(.all)) + .zIndex(1) + } + .transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.2))) + .onAppear() { + self.dragOffset = .zero + self.dragOffsetPredicted = .zero + } + } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} + +@available(iOS 13.0, *) +class PinchZoomView: UIView { + + weak var delegate: PinchZoomViewDelgate? + + private(set) var scale: CGFloat = 0 { + didSet { + delegate?.pinchZoomView(self, didChangeScale: scale) + } + } + + private(set) var anchor: UnitPoint = .center { + didSet { + delegate?.pinchZoomView(self, didChangeAnchor: anchor) + } + } + + private(set) var offset: CGSize = .zero { + didSet { + delegate?.pinchZoomView(self, didChangeOffset: offset) + } + } + + private(set) var isPinching: Bool = false { + didSet { + delegate?.pinchZoomView(self, didChangePinching: isPinching) + } + } + + private var startLocation: CGPoint = .zero + private var location: CGPoint = .zero + private var numberOfTouches: Int = 0 + + init() { + super.init(frame: .zero) + + let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinch(gesture:))) + pinchGesture.cancelsTouchesInView = false + addGestureRecognizer(pinchGesture) + } + + required init?(coder: NSCoder) { + fatalError() + } + + @objc private func pinch(gesture: UIPinchGestureRecognizer) { + + switch gesture.state { + case .began: + isPinching = true + startLocation = gesture.location(in: self) + anchor = UnitPoint(x: startLocation.x / bounds.width, y: startLocation.y / bounds.height) + numberOfTouches = gesture.numberOfTouches + + case .changed: + if gesture.numberOfTouches != numberOfTouches { + // If the number of fingers being used changes, the start location needs to be adjusted to avoid jumping. + let newLocation = gesture.location(in: self) + let jumpDifference = CGSize(width: newLocation.x - location.x, height: newLocation.y - location.y) + startLocation = CGPoint(x: startLocation.x + jumpDifference.width, y: startLocation.y + jumpDifference.height) + + numberOfTouches = gesture.numberOfTouches + } + + scale = gesture.scale + + location = gesture.location(in: self) + offset = CGSize(width: location.x - startLocation.x, height: location.y - startLocation.y) + + case .ended, .cancelled, .failed: + withAnimation(.interactiveSpring()) { + isPinching = false + scale = 1.0 + anchor = .center + offset = .zero + } + default: + break + } + } + +} +@available(iOS 13.0, *) +protocol PinchZoomViewDelgate: AnyObject { + func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangePinching isPinching: Bool) + func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeScale scale: CGFloat) + func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeAnchor anchor: UnitPoint) + func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeOffset offset: CGSize) +} + +@available(iOS 13.0, *) +struct PinchZoom: UIViewRepresentable { + + @Binding var scale: CGFloat + @Binding var anchor: UnitPoint + @Binding var offset: CGSize + @Binding var isPinching: Bool + + func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + func makeUIView(context: Context) -> PinchZoomView { + let pinchZoomView = PinchZoomView() + pinchZoomView.delegate = context.coordinator + return pinchZoomView + } + + func updateUIView(_ pageControl: PinchZoomView, context: Context) { } + + class Coordinator: NSObject, PinchZoomViewDelgate { + var pinchZoom: PinchZoom + + init(_ pinchZoom: PinchZoom) { + self.pinchZoom = pinchZoom + } + + func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangePinching isPinching: Bool) { + pinchZoom.isPinching = isPinching + } + + func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeScale scale: CGFloat) { + pinchZoom.scale = scale + } + + func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeAnchor anchor: UnitPoint) { + pinchZoom.anchor = anchor + } + + func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeOffset offset: CGSize) { + pinchZoom.offset = offset + } + } +} + +@available(iOS 13.0, *) +struct PinchToZoom: ViewModifier { + @State var scale: CGFloat = 1.0 + @State var anchor: UnitPoint = .center + @State var offset: CGSize = .zero + @State var isPinching: Bool = false + + func body(content: Content) -> some View { + content + .scaleEffect(scale, anchor: anchor) + .offset(offset) + .overlay(PinchZoom(scale: $scale, anchor: $anchor, offset: $offset, isPinching: $isPinching)) + } +} + +@available(iOS 13.0, *) +extension View { + func pinchToZoom() -> some View { + self.modifier(PinchToZoom()) + } +} diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift index c714ff34b6..c47c3300a3 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift @@ -17,7 +17,7 @@ import SwiftUI import Combine -@available(iOS 14.0, *) +@available(iOS 15.0, *) struct TemplateRoomChat: View { // MARK: - Properties @@ -26,6 +26,8 @@ struct TemplateRoomChat: View { @Environment(\.theme) private var theme: ThemeSwiftUI + @State var fullScreenImage: Image? + // MARK: Public @ObservedObject var viewModel: TemplateRoomChatViewModel.Context @@ -63,6 +65,12 @@ struct TemplateRoomChat: View { } } + + + .onTapGesture { + showImageViewer.toggle() + } + .overlay(ImageViewer(image: $image, viewerShown: self.$showImageViewer)) @ViewBuilder private var roomContent: some View { if case .notInitialized = viewModel.viewState.roomInitializationStatus { @@ -127,7 +135,7 @@ struct TemplateRoomChat: View { // MARK: - Previews -@available(iOS 14.0, *) +@available(iOS 15.0, *) struct TemplateRoomChat_Previews: PreviewProvider { static let stateRenderer = MockTemplateRoomChatScreenState.stateRenderer static var previews: some View { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift index 6efb16b4f0..1c9958bd2e 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift @@ -16,7 +16,7 @@ import SwiftUI -@available(iOS 14.0, *) +@available(iOS 15.0, *) struct TemplateRoomChatBubbleContentView: View { // MARK: - Properties @@ -44,7 +44,7 @@ struct TemplateRoomChatBubbleContentView: View { // MARK: - Previews -@available(iOS 14.0, *) +@available(iOS 15.0, *) struct TemplateRoomChatBubbleItemView_Previews: PreviewProvider { static var previews: some View { EmptyView() diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift index 16fa077d48..4c178184b2 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift @@ -16,7 +16,7 @@ import SwiftUI -@available(iOS 14.0, *) +@available(iOS 15.0, *) struct TemplateRoomChatBubbleImage: View { // MARK: - Properties @@ -24,22 +24,29 @@ struct TemplateRoomChatBubbleImage: View { // MARK: Private @Environment(\.theme) private var theme: ThemeSwiftUI - + // MARK: Public let imageContent: TemplateRoomChatMessageImageContent - + @State var showImageViewer: Bool = false var body: some View { - EmptyView() + AsyncImage(url: imageContent.url) { image in + image.resizable() + .aspectRatio(contentMode: .fill) + .frame(width: CGFloat(258), height: CGFloat(150)) + .cornerRadius(8) + } placeholder: { + Color.green + } } } // MARK: - Previews -@available(iOS 14.0, *) +@available(iOS 15.0, *) struct TemplateRoomChatBubbleImage_Previews: PreviewProvider { + static let exampleUrl = URL(string: "https://docs-assets.developer.apple.com/published/9c4143a9a48a080f153278c9732c03e7/17400/SwiftUI-Image-waterWheel-resize~dark@2x.png")! static var previews: some View { - EmptyView() - // TODO: New to our SwiftUI Template? Why not implement the image item in the bubble here? + TemplateRoomChatBubbleImage(imageContent: TemplateRoomChatMessageImageContent(url:exampleUrl)) } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift index b324a9565f..6e5a6808ea 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift @@ -16,7 +16,7 @@ import SwiftUI -@available(iOS 14.0, *) +@available(iOS 15.0, *) struct TemplateRoomChatBubbleView: View { // MARK: - Properties @@ -52,7 +52,7 @@ struct TemplateRoomChatBubbleView: View { // MARK: - Previews -@available(iOS 14.0, *) +@available(iOS 15.0, *) struct TemplateRoomChatBubbleView_Previews: PreviewProvider { static let bubble = TemplateRoomChatBubble( id: "111", diff --git a/RiotSwiftUI/RiotSwiftUIApp.swift b/RiotSwiftUI/RiotSwiftUIApp.swift index 902f7327de..b1527da235 100644 --- a/RiotSwiftUI/RiotSwiftUIApp.swift +++ b/RiotSwiftUI/RiotSwiftUIApp.swift @@ -15,7 +15,7 @@ // import SwiftUI -@available(iOS 14.0, *) +@available(iOS 15.0, *) @main /// RiotSwiftUI screens rendered for UI Tests. struct RiotSwiftUIApp: App { From cff98cdd1dfc208080c4bc25f0dfeb9ad6e529fb Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 21 Mar 2022 10:39:50 +0000 Subject: [PATCH 5/9] Revert "fix merge" This reverts commit 0251a01bb05f9c2139f09847adc2313609a77103. --- Podfile | 8 +- Podfile.lock | 9 +- .../Modules/Settings/SettingsViewController.m | 1 - .../SideMenu/SideMenuCoordinator.swift | 25 +- .../Modules/Common/Mock/MockAppScreens.swift | 2 +- .../TemplateRoomsCoordinator.swift | 8 +- .../TemplateRoomChatCoordinator.swift | 2 +- .../MockTemplateRoomChatScreenState.swift | 2 +- .../MatrixSDK/TemplateRoomChatService.swift | 29 +- .../Mock/MockTemplateRoomChatService.swift | 2 - .../TemplateRoomChatModels.swift | 2 +- .../TemplateRoomChat/View/ImageViewer.swift | 294 ------------------ .../View/TemplateRoomChat.swift | 12 +- .../TemplateRoomChatBubbleContentView.swift | 4 +- .../View/TemplateRoomChatBubbleImage.swift | 21 +- .../View/TemplateRoomChatBubbleView.swift | 4 +- RiotSwiftUI/RiotSwiftUIApp.swift | 2 +- 17 files changed, 36 insertions(+), 391 deletions(-) delete mode 100644 RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/ImageViewer.swift diff --git a/Podfile b/Podfile index fd699f7d6e..07fe178d68 100644 --- a/Podfile +++ b/Podfile @@ -13,9 +13,9 @@ use_frameworks! # - `{ :specHash => {sdk spec hash}` to depend on specific pod options (:git => …, :podspec => …) for MatrixSDK repo. Used by Fastfile during CI # # Warning: our internal tooling depends on the name of this variable name, so be sure not to change it -# $matrixSDKVersion = '= 0.22.6' - $matrixSDKVersion = :local -#$matrixSDKVersion = { :branch => 'develop'} +$matrixSDKVersion = '= 0.22.6' +# $matrixSDKVersion = :local +# $matrixSDKVersion = { :branch => 'develop'} # $matrixSDKVersion = { :specHash => { git: 'https://git.io/fork123', branch: 'fix' } } ######################################## @@ -154,4 +154,4 @@ post_install do |installer| config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['$(inherited)', '-Xcc', '-Wno-nullability-completeness'] end end -end +end \ No newline at end of file diff --git a/Podfile.lock b/Podfile.lock index 1a3719d2a0..1c3e87179e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -115,8 +115,8 @@ DEPENDENCIES: - KeychainAccess (~> 4.2.2) - KTCenterFlowLayout (~> 1.3.1) - libPhoneNumber-iOS (~> 0.9.13) - - MatrixSDK (from `../matrix-ios-sdk/MatrixSDK.podspec`) - - MatrixSDK/JingleCallStack (from `../matrix-ios-sdk/MatrixSDK.podspec`) + - MatrixSDK (= 0.22.6) + - MatrixSDK/JingleCallStack (= 0.22.6) - OLMKit - PostHog (~> 1.4.4) - ReadMoreTextView (~> 3.0.1) @@ -156,6 +156,7 @@ SPEC REPOS: - libPhoneNumber-iOS - LoggerAPI - Logging + - MatrixSDK - OLMKit - PostHog - ReadMoreTextView @@ -176,8 +177,6 @@ EXTERNAL SOURCES: AnalyticsEvents: :branch: release/swift :git: https://github.com/matrix-org/matrix-analytics-events.git - MatrixSDK: - :path: "../matrix-ios-sdk/MatrixSDK.podspec" CHECKOUT OPTIONS: AnalyticsEvents: @@ -226,6 +225,6 @@ SPEC CHECKSUMS: zxcvbn-ios: fef98b7c80f1512ff0eec47ac1fa399fc00f7e3c ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb -PODFILE CHECKSUM: add4568acff884a72cf19933027bbc5507725185 +PODFILE CHECKSUM: 16aaf5e59ec902619fbfd799939f044728a92ab7 COCOAPODS: 1.11.2 diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index 4cff1dac32..ed040f53d5 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -2478,7 +2478,6 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N else if (row == LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS) { cell = [self buildAutoReportDecryptionErrorsCellForTableView:tableView atIndexPath:indexPath]; - } else if (row == LABS_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; diff --git a/Riot/Modules/SideMenu/SideMenuCoordinator.swift b/Riot/Modules/SideMenu/SideMenuCoordinator.swift index 962228a0fe..7f5280fd87 100644 --- a/Riot/Modules/SideMenu/SideMenuCoordinator.swift +++ b/Riot/Modules/SideMenu/SideMenuCoordinator.swift @@ -67,7 +67,6 @@ final class SideMenuCoordinator: NSObject, SideMenuCoordinatorType { private var createSpaceCoordinator: SpaceCreationCoordinator? private var createRoomCoordinator: CreateRoomCoordinator? private var spaceSettingsCoordinator: Coordinator? - private var chatCoordinator: TemplateRoomsCoordinator? // MARK: Public @@ -239,28 +238,10 @@ final class SideMenuCoordinator: NSObject, SideMenuCoordinatorType { } private func showInviteFriends(from sourceView: UIView?) { -// let myUserId = self.parameters.userSessionsService.mainUserSession?.userId ?? "" + let myUserId = self.parameters.userSessionsService.mainUserSession?.userId ?? "" -// let inviteFriendsPresenter = InviteFriendsPresenter() -// inviteFriendsPresenter.present(for: myUserId, from: self.sideMenuViewController, sourceView: sourceView, animated: true) - - guard let session = self.parameters.userSessionsService.mainUserSession?.matrixSession else { - return - } - let coordinator = TemplateRoomsCoordinator(parameters: TemplateRoomsCoordinatorParameters(session: session)) - coordinator.callback = { [weak self] in - guard let self = self else { return } - - coordinator.toPresentable().dismiss(animated: true) { - self.chatCoordinator = nil - } - } - - let presentable = coordinator.toPresentable() - presentable.presentationController?.delegate = self - toPresentable().present(presentable, animated: true, completion: nil) - coordinator.start() - self.chatCoordinator = coordinator + let inviteFriendsPresenter = InviteFriendsPresenter() + inviteFriendsPresenter.present(for: myUserId, from: self.sideMenuViewController, sourceView: sourceView, animated: true) } private func showMenu(forSpaceWithId spaceId: String, from sourceView: UIView?) { diff --git a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift index 963b9954a6..3c5909a4ce 100644 --- a/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift +++ b/RiotSwiftUI/Modules/Common/Mock/MockAppScreens.swift @@ -17,7 +17,7 @@ import Foundation /// The static list of mocked screens in RiotSwiftUI -@available(iOS 15.0, *) +@available(iOS 14.0, *) enum MockAppScreens { static let appScreens: [MockScreenState.Type] = [ MockOnboardingCongratulationsScreenState.self, diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift index f593eb32d0..be453b9487 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/Coordinator/TemplateRoomsCoordinator.swift @@ -46,7 +46,7 @@ final class TemplateRoomsCoordinator: Coordinator, Presentable { func start() { - if #available(iOS 15.0, *) { + if #available(iOS 14.0, *) { MXLog.debug("[TemplateRoomsCoordinator] did start.") let rootCoordinator = self.createTemplateRoomListCoordinator() rootCoordinator.start() @@ -71,7 +71,7 @@ final class TemplateRoomsCoordinator: Coordinator, Presentable { // MARK: - Private - @available(iOS 15.0, *) + @available(iOS 14.0, *) private func createTemplateRoomListCoordinator() -> TemplateRoomListCoordinator { let coordinator: TemplateRoomListCoordinator = TemplateRoomListCoordinator(parameters: TemplateRoomListCoordinatorParameters(session: parameters.session)) @@ -88,13 +88,13 @@ final class TemplateRoomsCoordinator: Coordinator, Presentable { return coordinator } - @available(iOS 15.0, *) + @available(iOS 14.0, *) private func createTemplateRoomChatCoordinator(room: MXRoom) -> TemplateRoomChatCoordinator { let coordinator: TemplateRoomChatCoordinator = TemplateRoomChatCoordinator(parameters: TemplateRoomChatCoordinatorParameters(room: room)) return coordinator } - @available(iOS 15.0, *) + @available(iOS 14.0, *) func showTemplateRoomChat(roomId: String) { guard let room = parameters.session.room(withRoomId: roomId) else { MXLog.error("[TemplateRoomsCoordinator] Failed to find room by selected Id.") diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift index 8b4d9cb86d..d9a5e10f23 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Coordinator/TemplateRoomChatCoordinator.swift @@ -38,7 +38,7 @@ final class TemplateRoomChatCoordinator: Coordinator, Presentable { // MARK: - Setup - @available(iOS 15.0, *) + @available(iOS 14.0, *) init(parameters: TemplateRoomChatCoordinatorParameters) { self.parameters = parameters let viewModel = TemplateRoomChatViewModel(templateRoomChatService: TemplateRoomChatService(room: parameters.room)) diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift index 6c7289a27d..925c5c6143 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/MockTemplateRoomChatScreenState.swift @@ -19,7 +19,7 @@ import SwiftUI /// Using an enum for the screen allows you define the different state cases with /// the relevant associated data for each case. -@available(iOS 15.0, *) +@available(iOS 14.0, *) enum MockTemplateRoomChatScreenState: MockScreenState, CaseIterable { // A case for each state you want to represent // with specific, minimal associated data that will allow you diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift index 3dc571939c..6d231f7447 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/MatrixSDK/TemplateRoomChatService.swift @@ -16,7 +16,6 @@ import Foundation import Combine -import MatrixSDK @available(iOS 14.0, *) class TemplateRoomChatService: TemplateRoomChatServiceProtocol { @@ -97,21 +96,20 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { return events .filter({ event in event.type == kMXEventTypeStringRoomMessage - && (event.content[kMXMessageTypeKey] as? String == kMXMessageTypeText - || event.content[kMXMessageTypeKey] as? String == kMXMessageTypeImage) + && event.content[kMXMessageTypeKey] as? String == kMXMessageTypeText // TODO: New to our SwiftUI Template? Why not implement another message type like image? }) .compactMap({ event -> TemplateRoomChatMessage? in guard let eventId = event.eventId, + let body = event.content[kMXMessageBodyKey] as? String, let sender = senderForMessage(event: event) else { return nil } - let messageContent = messageContentForEvent(event: event) return TemplateRoomChatMessage( id: eventId, - content: messageContent, + content: .text(TemplateRoomChatMessageTextContent(body: body)), sender: sender, timestamp: Date(timeIntervalSince1970: TimeInterval(event.originServerTs / 1000)) ) @@ -126,25 +124,4 @@ class TemplateRoomChatService: TemplateRoomChatServiceProtocol { let avatarUrl = eventFormatter.senderAvatarUrl(for: event, with: roomState) return TemplateRoomChatMember(id: sender, avatarUrl: avatarUrl, displayName: displayName) } - - private func messageContentForEvent(event: MXEvent) -> TemplateRoomChatMessageContent { - switch event.content[kMXMessageTypeKey] as? String { - case kMXMessageTypeText: - let body = event.content[kMXMessageBodyKey] as? String ?? "" - return .text(TemplateRoomChatMessageTextContent(body: body)) - case kMXMessageTypeImage: - let url: URL -// room.mxSession.mediaManager. - if let contentURL = event.content["url"] as? String, - let info = event.content["info"] as? [String: Any], - let localImagePath = MXMediaManager.cachePath(forMatrixContentURI: contentURL, andType:info["mimetype"] as? String, inFolder: event.roomId) { - url = URL(fileURLWithPath: localImagePath) - } else{ - url = URL(string: "https://cahilldental.ie/wp-content/uploads/2016/10/orionthemes-placeholder-image.png")! - } - return .image(TemplateRoomChatMessageImageContent(url: url)) - default: break - } - fatalError("unsupported event type content") - } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift index 3134b2d29f..8fa354911d 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/Service/Mock/MockTemplateRoomChatService.swift @@ -24,13 +24,11 @@ class MockTemplateRoomChatService: TemplateRoomChatServiceProtocol { static let amadine = TemplateRoomChatMember(id: "@amadine:matrix.org", avatarUrl: "!aaabaa:matrix.org", displayName: "Amadine") static let mathew = TemplateRoomChatMember(id: "@mathew:matrix.org", avatarUrl: "!bbabb:matrix.org", displayName: "Mathew") - static let partyImageUrl = URL(string: "https://img.evbuc.com/https%3A%2F%2Fcdn.evbuc.com%2Fimages%2F184375039%2F474927372937%2F1%2Foriginal.20211111-155142?w=800&auto=format%2Ccompress&q=75&sharp=10&rect=0%2C236%2C4724%2C2362&s=18c17c71af6df1e5e46d630fac923834")! static let mockMessages = [ TemplateRoomChatMessage(id: "!0:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Shall I put it live?")) , sender: amadine, timestamp: Date(timeIntervalSinceNow: 60 * -3)), TemplateRoomChatMessage(id: "!1:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Yea go for it! ...and then let's head to the pub")), sender: mathew, timestamp: Date(timeIntervalSinceNow: 60)), TemplateRoomChatMessage(id: "!2:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Deal.")), sender: amadine, timestamp: Date(timeIntervalSinceNow: 60 * -2)), TemplateRoomChatMessage(id: "!3:matrix.org", content: .text(TemplateRoomChatMessageTextContent(body: "Ok, Done. 🍻")), sender: amadine, timestamp: Date(timeIntervalSinceNow: 60 * -1)), - TemplateRoomChatMessage(id: "!3:matrix.org", content: .image(TemplateRoomChatMessageImageContent(url: partyImageUrl)), sender: mathew, timestamp: Date(timeIntervalSinceNow: 60 * -1)), ] var roomInitializationStatus: CurrentValueSubject var chatMessagesSubject: CurrentValueSubject<[TemplateRoomChatMessage], Never> diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift index 5e5310189e..e9aa09141c 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/TemplateRoomChatModels.swift @@ -20,7 +20,7 @@ import UIKit /// An image sent as a message. struct TemplateRoomChatMessageImageContent: Equatable { - var url: URL + var image: UIImage } /// The text content of a message sent by a user. diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/ImageViewer.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/ImageViewer.swift deleted file mode 100644 index 37c78dabbd..0000000000 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/ImageViewer.swift +++ /dev/null @@ -1,294 +0,0 @@ -import SwiftUI -import UIKit - -@available(iOS 13.0, *) -public struct ImageViewer: View { - @Binding var viewerShown: Bool - @Binding var image: Image - @Binding var imageOpt: Image? - @State var caption: Text? - @State var closeButtonTopRight: Bool? - - var aspectRatio: Binding? - - @State var dragOffset: CGSize = CGSize.zero - @State var dragOffsetPredicted: CGSize = CGSize.zero - - public init(image: Binding, viewerShown: Binding, aspectRatio: Binding? = nil, caption: Text? = nil, closeButtonTopRight: Bool? = false) { - _image = image - _viewerShown = viewerShown - _imageOpt = .constant(nil) - self.aspectRatio = aspectRatio - _caption = State(initialValue: caption) - _closeButtonTopRight = State(initialValue: closeButtonTopRight) - } - - public init(image: Binding, viewerShown: Binding, aspectRatio: Binding? = nil, caption: Text? = nil, closeButtonTopRight: Bool? = false) { - _image = .constant(Image(systemName: "")) - _imageOpt = image - _viewerShown = viewerShown - self.aspectRatio = aspectRatio - _caption = State(initialValue: caption) - _closeButtonTopRight = State(initialValue: closeButtonTopRight) - } - - func getImage() -> Image { - if(self.imageOpt == nil) { - return self.image - } - else { - return self.imageOpt ?? Image(systemName: "questionmark.diamond") - } - } - - @ViewBuilder - public var body: some View { - VStack { - if(viewerShown) { - ZStack { - VStack { - HStack { - - if self.closeButtonTopRight == true { - Spacer() - } - - Button(action: { self.viewerShown = false }) { - Image(systemName: "xmark") - .foregroundColor(Color(UIColor.white)) - .font(.system(size: UIFontMetrics.default.scaledValue(for: 24))) - } - - if self.closeButtonTopRight != true { - Spacer() - } - } - - Spacer() - } - .padding() - .zIndex(2) - - VStack { - ZStack { - self.getImage() - .resizable() - .aspectRatio(self.aspectRatio?.wrappedValue, contentMode: .fit) - .offset(x: self.dragOffset.width, y: self.dragOffset.height) - .rotationEffect(.init(degrees: Double(self.dragOffset.width / 30))) - .pinchToZoom() - .gesture(DragGesture() - .onChanged { value in - self.dragOffset = value.translation - self.dragOffsetPredicted = value.predictedEndTranslation - } - .onEnded { value in - if((abs(self.dragOffset.height) + abs(self.dragOffset.width) > 570) || ((abs(self.dragOffsetPredicted.height)) / (abs(self.dragOffset.height)) > 3) || ((abs(self.dragOffsetPredicted.width)) / (abs(self.dragOffset.width))) > 3) { - withAnimation(.spring()) { - self.dragOffset = self.dragOffsetPredicted - } - self.viewerShown = false - - return - } - withAnimation(.interactiveSpring()) { - self.dragOffset = .zero - } - } - ) - - if(self.caption != nil) { - VStack { - Spacer() - - VStack { - Spacer() - - HStack { - Spacer() - - self.caption - .foregroundColor(.white) - .multilineTextAlignment(.center) - - Spacer() - } - } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .background(Color(red: 0.12, green: 0.12, blue: 0.12, opacity: (1.0 - Double(abs(self.dragOffset.width) + abs(self.dragOffset.height)) / 1000)).edgesIgnoringSafeArea(.all)) - .zIndex(1) - } - .transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.2))) - .onAppear() { - self.dragOffset = .zero - self.dragOffsetPredicted = .zero - } - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } -} - -@available(iOS 13.0, *) -class PinchZoomView: UIView { - - weak var delegate: PinchZoomViewDelgate? - - private(set) var scale: CGFloat = 0 { - didSet { - delegate?.pinchZoomView(self, didChangeScale: scale) - } - } - - private(set) var anchor: UnitPoint = .center { - didSet { - delegate?.pinchZoomView(self, didChangeAnchor: anchor) - } - } - - private(set) var offset: CGSize = .zero { - didSet { - delegate?.pinchZoomView(self, didChangeOffset: offset) - } - } - - private(set) var isPinching: Bool = false { - didSet { - delegate?.pinchZoomView(self, didChangePinching: isPinching) - } - } - - private var startLocation: CGPoint = .zero - private var location: CGPoint = .zero - private var numberOfTouches: Int = 0 - - init() { - super.init(frame: .zero) - - let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinch(gesture:))) - pinchGesture.cancelsTouchesInView = false - addGestureRecognizer(pinchGesture) - } - - required init?(coder: NSCoder) { - fatalError() - } - - @objc private func pinch(gesture: UIPinchGestureRecognizer) { - - switch gesture.state { - case .began: - isPinching = true - startLocation = gesture.location(in: self) - anchor = UnitPoint(x: startLocation.x / bounds.width, y: startLocation.y / bounds.height) - numberOfTouches = gesture.numberOfTouches - - case .changed: - if gesture.numberOfTouches != numberOfTouches { - // If the number of fingers being used changes, the start location needs to be adjusted to avoid jumping. - let newLocation = gesture.location(in: self) - let jumpDifference = CGSize(width: newLocation.x - location.x, height: newLocation.y - location.y) - startLocation = CGPoint(x: startLocation.x + jumpDifference.width, y: startLocation.y + jumpDifference.height) - - numberOfTouches = gesture.numberOfTouches - } - - scale = gesture.scale - - location = gesture.location(in: self) - offset = CGSize(width: location.x - startLocation.x, height: location.y - startLocation.y) - - case .ended, .cancelled, .failed: - withAnimation(.interactiveSpring()) { - isPinching = false - scale = 1.0 - anchor = .center - offset = .zero - } - default: - break - } - } - -} -@available(iOS 13.0, *) -protocol PinchZoomViewDelgate: AnyObject { - func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangePinching isPinching: Bool) - func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeScale scale: CGFloat) - func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeAnchor anchor: UnitPoint) - func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeOffset offset: CGSize) -} - -@available(iOS 13.0, *) -struct PinchZoom: UIViewRepresentable { - - @Binding var scale: CGFloat - @Binding var anchor: UnitPoint - @Binding var offset: CGSize - @Binding var isPinching: Bool - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - func makeUIView(context: Context) -> PinchZoomView { - let pinchZoomView = PinchZoomView() - pinchZoomView.delegate = context.coordinator - return pinchZoomView - } - - func updateUIView(_ pageControl: PinchZoomView, context: Context) { } - - class Coordinator: NSObject, PinchZoomViewDelgate { - var pinchZoom: PinchZoom - - init(_ pinchZoom: PinchZoom) { - self.pinchZoom = pinchZoom - } - - func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangePinching isPinching: Bool) { - pinchZoom.isPinching = isPinching - } - - func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeScale scale: CGFloat) { - pinchZoom.scale = scale - } - - func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeAnchor anchor: UnitPoint) { - pinchZoom.anchor = anchor - } - - func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeOffset offset: CGSize) { - pinchZoom.offset = offset - } - } -} - -@available(iOS 13.0, *) -struct PinchToZoom: ViewModifier { - @State var scale: CGFloat = 1.0 - @State var anchor: UnitPoint = .center - @State var offset: CGSize = .zero - @State var isPinching: Bool = false - - func body(content: Content) -> some View { - content - .scaleEffect(scale, anchor: anchor) - .offset(offset) - .overlay(PinchZoom(scale: $scale, anchor: $anchor, offset: $offset, isPinching: $isPinching)) - } -} - -@available(iOS 13.0, *) -extension View { - func pinchToZoom() -> some View { - self.modifier(PinchToZoom()) - } -} diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift index c47c3300a3..c714ff34b6 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChat.swift @@ -17,7 +17,7 @@ import SwiftUI import Combine -@available(iOS 15.0, *) +@available(iOS 14.0, *) struct TemplateRoomChat: View { // MARK: - Properties @@ -26,8 +26,6 @@ struct TemplateRoomChat: View { @Environment(\.theme) private var theme: ThemeSwiftUI - @State var fullScreenImage: Image? - // MARK: Public @ObservedObject var viewModel: TemplateRoomChatViewModel.Context @@ -65,12 +63,6 @@ struct TemplateRoomChat: View { } } - - - .onTapGesture { - showImageViewer.toggle() - } - .overlay(ImageViewer(image: $image, viewerShown: self.$showImageViewer)) @ViewBuilder private var roomContent: some View { if case .notInitialized = viewModel.viewState.roomInitializationStatus { @@ -135,7 +127,7 @@ struct TemplateRoomChat: View { // MARK: - Previews -@available(iOS 15.0, *) +@available(iOS 14.0, *) struct TemplateRoomChat_Previews: PreviewProvider { static let stateRenderer = MockTemplateRoomChatScreenState.stateRenderer static var previews: some View { diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift index 1c9958bd2e..6efb16b4f0 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleContentView.swift @@ -16,7 +16,7 @@ import SwiftUI -@available(iOS 15.0, *) +@available(iOS 14.0, *) struct TemplateRoomChatBubbleContentView: View { // MARK: - Properties @@ -44,7 +44,7 @@ struct TemplateRoomChatBubbleContentView: View { // MARK: - Previews -@available(iOS 15.0, *) +@available(iOS 14.0, *) struct TemplateRoomChatBubbleItemView_Previews: PreviewProvider { static var previews: some View { EmptyView() diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift index 4c178184b2..16fa077d48 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleImage.swift @@ -16,7 +16,7 @@ import SwiftUI -@available(iOS 15.0, *) +@available(iOS 14.0, *) struct TemplateRoomChatBubbleImage: View { // MARK: - Properties @@ -24,29 +24,22 @@ struct TemplateRoomChatBubbleImage: View { // MARK: Private @Environment(\.theme) private var theme: ThemeSwiftUI - + // MARK: Public let imageContent: TemplateRoomChatMessageImageContent - @State var showImageViewer: Bool = false + var body: some View { - AsyncImage(url: imageContent.url) { image in - image.resizable() - .aspectRatio(contentMode: .fill) - .frame(width: CGFloat(258), height: CGFloat(150)) - .cornerRadius(8) - } placeholder: { - Color.green - } + EmptyView() } } // MARK: - Previews -@available(iOS 15.0, *) +@available(iOS 14.0, *) struct TemplateRoomChatBubbleImage_Previews: PreviewProvider { - static let exampleUrl = URL(string: "https://docs-assets.developer.apple.com/published/9c4143a9a48a080f153278c9732c03e7/17400/SwiftUI-Image-waterWheel-resize~dark@2x.png")! static var previews: some View { - TemplateRoomChatBubbleImage(imageContent: TemplateRoomChatMessageImageContent(url:exampleUrl)) + EmptyView() + // TODO: New to our SwiftUI Template? Why not implement the image item in the bubble here? } } diff --git a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift index 6e5a6808ea..b324a9565f 100644 --- a/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift +++ b/RiotSwiftUI/Modules/Template/TemplateAdvancedRoomsExample/TemplateRoomChat/View/TemplateRoomChatBubbleView.swift @@ -16,7 +16,7 @@ import SwiftUI -@available(iOS 15.0, *) +@available(iOS 14.0, *) struct TemplateRoomChatBubbleView: View { // MARK: - Properties @@ -52,7 +52,7 @@ struct TemplateRoomChatBubbleView: View { // MARK: - Previews -@available(iOS 15.0, *) +@available(iOS 14.0, *) struct TemplateRoomChatBubbleView_Previews: PreviewProvider { static let bubble = TemplateRoomChatBubble( id: "111", diff --git a/RiotSwiftUI/RiotSwiftUIApp.swift b/RiotSwiftUI/RiotSwiftUIApp.swift index b1527da235..902f7327de 100644 --- a/RiotSwiftUI/RiotSwiftUIApp.swift +++ b/RiotSwiftUI/RiotSwiftUIApp.swift @@ -15,7 +15,7 @@ // import SwiftUI -@available(iOS 15.0, *) +@available(iOS 14.0, *) @main /// RiotSwiftUI screens rendered for UI Tests. struct RiotSwiftUIApp: App { From fc9e4a9847060f5fbf0dae0aefd9b6e4c305acb6 Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 21 Mar 2022 10:41:10 +0000 Subject: [PATCH 6/9] Actually fix merge. --- Riot/Modules/Settings/SettingsViewController.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Riot/Modules/Settings/SettingsViewController.m b/Riot/Modules/Settings/SettingsViewController.m index ed040f53d5..4cff1dac32 100644 --- a/Riot/Modules/Settings/SettingsViewController.m +++ b/Riot/Modules/Settings/SettingsViewController.m @@ -2478,6 +2478,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(N else if (row == LABS_ENABLE_AUTO_REPORT_DECRYPTION_ERRORS) { cell = [self buildAutoReportDecryptionErrorsCellForTableView:tableView atIndexPath:indexPath]; + } else if (row == LABS_USE_ONLY_LATEST_USER_AVATAR_AND_NAME_INDEX) { MXKTableViewCellWithLabelAndSwitch *labelAndSwitchCell = [self getLabelAndSwitchCell:tableView forIndexPath:indexPath]; From e031cb5b4bc1eb27f87c761e53ec7b63fc14e3d7 Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 21 Mar 2022 14:45:54 +0000 Subject: [PATCH 7/9] Fix documentation and update big report client init. --- Riot/Categories/MXBugReportRestClient+Riot.swift | 5 +---- Riot/Managers/Settings/RiotSettings.swift | 2 +- Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift | 5 ++--- Riot/Managers/UISIAutoReporter/UISIDetector.swift | 9 +++++++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Riot/Categories/MXBugReportRestClient+Riot.swift b/Riot/Categories/MXBugReportRestClient+Riot.swift index fd3fe313ec..93eff76aaa 100644 --- a/Riot/Categories/MXBugReportRestClient+Riot.swift +++ b/Riot/Categories/MXBugReportRestClient+Riot.swift @@ -21,10 +21,7 @@ import GBDeviceInfo extension MXBugReportRestClient { @objc static func vc_bugReportRestClient(appName: String) -> MXBugReportRestClient { - guard let client = MXBugReportRestClient(bugReportEndpoint: BuildSettings.bugReportEndpointUrlString) else { - fatalError("Could not create MXBugReportRestClient") - } - + let client = MXBugReportRestClient(bugReportEndpoint: BuildSettings.bugReportEndpointUrlString) // App info client.appName = appName client.version = AppDelegate.theDelegate().appVersion diff --git a/Riot/Managers/Settings/RiotSettings.swift b/Riot/Managers/Settings/RiotSettings.swift index b1536b05a6..f90021ba37 100644 --- a/Riot/Managers/Settings/RiotSettings.swift +++ b/Riot/Managers/Settings/RiotSettings.swift @@ -147,7 +147,7 @@ final class RiotSettings: NSObject { @UserDefault(key: "enableThreads", defaultValue: false, storage: defaults) var enableThreads - /// Indicates if threads enabled in the timeline. + /// Indicates if auto reporting of decryption errors is enabled @UserDefault(key: UserDefaultsKeys.enableUISIAutoReporting, defaultValue: BuildSettings.cryptoUISIAutoReportingEnabled, storage: defaults) var enableUISIAutoReporting diff --git a/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift b/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift index febd2aee38..ca64cace55 100644 --- a/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift +++ b/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift @@ -39,6 +39,8 @@ extension UISIAutoReportData: Codable { } @available(iOS 14.0, *) +/// Listens for failed decryption events and silently sends reports RageShake server. +/// Also requests that message senders send a matching report to have both sides of the interaction. @objcMembers class UISIAutoReporter: NSObject, UISIDetectorDelegate { struct ReportInfo: Hashable { @@ -97,7 +99,6 @@ extension UISIAutoReportData: Codable { return detector }() - var reciprocateToDeviceEventType: String { return Self.autoRsRequest } @@ -180,7 +181,6 @@ extension UISIAutoReportData: Codable { let senderKey = source.content["sender_key"] as? String let matchingIssue = source.content["recipient_rageshake"] as? String ?? "" - let uisiData = UISIAutoReportData( eventId: eventId, roomId: roomId, @@ -206,7 +206,6 @@ extension UISIAutoReportData: Codable { ) } - func add(_ session: MXSession) { sessions.append(session) detector.enabled = enabled diff --git a/Riot/Managers/UISIAutoReporter/UISIDetector.swift b/Riot/Managers/UISIAutoReporter/UISIDetector.swift index ea0561c7ed..47c587a628 100644 --- a/Riot/Managers/UISIAutoReporter/UISIDetector.swift +++ b/Riot/Managers/UISIAutoReporter/UISIDetector.swift @@ -43,6 +43,8 @@ struct UISIDetectedMessage { } } +/// Detects decryption errors that occur and don't recover within a grace period. +/// see `UISIDetectorDelegate` for listening to detections. class UISIDetector: MXLiveEventListener { weak var delegate: UISIDetectorDelegate? @@ -88,7 +90,6 @@ class UISIDetector: MXLiveEventListener { self.trackedUISIs[trackedId] = timer timer.activate() } - } func onLiveToDeviceEvent(event: MXEvent) { @@ -96,13 +97,17 @@ class UISIDetector: MXLiveEventListener { delegate?.uisiReciprocateRequest(source: event) } + // MARK: - Private + private func triggerUISI(source: UISIDetectedMessage) { guard enabled else { return } MXLog.info("[UISIDetector] triggerUISI: Unable To Decrypt \(source)") self.delegate?.uisiDetected(source: source) } - static func trackedEventId(roomId: String, eventId: String) -> String { + // MARK: - Static + + private static func trackedEventId(roomId: String, eventId: String) -> String { return "\(roomId)-\(eventId)" } } From 18ca9017acbde19174439324d485f77899f26550 Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 21 Mar 2022 15:01:20 +0000 Subject: [PATCH 8/9] cleanup marks and make a couple of functions private. --- .../UISIAutoReporter/UISIAutoReporter.swift | 40 ++++++++++++------- .../UISIAutoReporter/UISIDetector.swift | 2 + 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift b/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift index ca64cace55..a29dcf2b9b 100644 --- a/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift +++ b/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift @@ -38,9 +38,10 @@ extension UISIAutoReportData: Codable { } } -@available(iOS 14.0, *) + /// Listens for failed decryption events and silently sends reports RageShake server. /// Also requests that message senders send a matching report to have both sides of the interaction. +@available(iOS 14.0, *) @objcMembers class UISIAutoReporter: NSObject, UISIDetectorDelegate { struct ReportInfo: Hashable { @@ -48,6 +49,8 @@ extension UISIAutoReportData: Codable { let sessionId: String } + // MARK: - Properties + private static let autoRsRequest = "im.vector.auto_rs_request" private static let reportSpacing = 60 @@ -66,6 +69,8 @@ extension UISIAutoReportData: Codable { } } + // MARK: - Setup + override init() { self.bugReporter = MXBugReportRestClient.vc_bugReportRestClient(appName: BuildSettings.bugReportUISIId) super.init() @@ -103,6 +108,8 @@ extension UISIAutoReportData: Codable { return Self.autoRsRequest } + // MARK: - Public + func uisiDetected(source: UISIDetectedMessage) { dispatchQueue.async { let reportInfo = ReportInfo(roomId: source.roomId, sessionId: source.sessionId) @@ -114,12 +121,27 @@ extension UISIAutoReportData: Codable { } } + func add(_ session: MXSession) { + sessions.append(session) + detector.enabled = enabled + session.eventStreamService.add(eventStreamListener: detector) + } + + func remove(_ session: MXSession) { + if let index = sessions.firstIndex(of: session) { + sessions.remove(at: index) + } + session.eventStreamService.remove(eventStreamListener: detector) + } + func uisiReciprocateRequest(source: MXEvent) { guard source.type == Self.autoRsRequest else { return } self.matchingRSRequestSubject.send(source) } - func sendRageShake(source: UISIDetectedMessage) { + // MARK: - Private + + private func sendRageShake(source: UISIDetectedMessage) { MXLog.debug("[UISIAutoReporter] sendRageShake") guard let session = sessions.first else { return } let uisiData = UISIAutoReportData( @@ -171,7 +193,7 @@ extension UISIAutoReportData: Codable { }) } - func sendMatchingRageShake(source: MXEvent) { + private func sendMatchingRageShake(source: MXEvent) { MXLog.debug("[UISIAutoReporter] sendMatchingRageShake") let eventId = source.content["event_id"] as? String let roomId = source.content["room_id"] as? String @@ -206,16 +228,4 @@ extension UISIAutoReportData: Codable { ) } - func add(_ session: MXSession) { - sessions.append(session) - detector.enabled = enabled - session.eventStreamService.add(eventStreamListener: detector) - } - - func remove(_ session: MXSession) { - if let index = sessions.firstIndex(of: session) { - sessions.remove(at: index) - } - session.eventStreamService.remove(eventStreamListener: detector) - } } diff --git a/Riot/Managers/UISIAutoReporter/UISIDetector.swift b/Riot/Managers/UISIAutoReporter/UISIDetector.swift index 47c587a628..5a701230f7 100644 --- a/Riot/Managers/UISIAutoReporter/UISIDetector.swift +++ b/Riot/Managers/UISIAutoReporter/UISIDetector.swift @@ -55,6 +55,8 @@ class UISIDetector: MXLiveEventListener { private let dispatchQueue = DispatchQueue(label: "io.element.UISIDetector.queue") private static let gracePeriodSeconds = 30 + // MARK: - Public + func onSessionStateChanged(state: MXSessionState) { dispatchQueue.async { self.initialSyncCompleted = state == .running From 5a5e77fd548104d0a82a1dfadb9d6a5d6b450c26 Mon Sep 17 00:00:00 2001 From: David Langley Date: Mon, 21 Mar 2022 15:22:39 +0000 Subject: [PATCH 9/9] Add matchingIssue to sender report and fix description, was missing "(sender)" --- Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift b/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift index a29dcf2b9b..893dbbe298 100644 --- a/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift +++ b/Riot/Managers/UISIAutoReporter/UISIAutoReporter.swift @@ -201,7 +201,12 @@ extension UISIAutoReportData: Codable { let deviceId = source.content["device_id"] as? String let userId = source.content["user_id"] as? String let senderKey = source.content["sender_key"] as? String - let matchingIssue = source.content["recipient_rageshake"] as? String ?? "" + let matchingIssue = source.content["recipient_rageshake"] as? String + + var description = "Auto-reporting decryption error (sender)" + if let matchingIssue = matchingIssue { + description += "\nRecipient rageshake: \(matchingIssue)" + } let uisiData = UISIAutoReportData( eventId: eventId, @@ -213,7 +218,7 @@ extension UISIAutoReportData: Codable { ).jsonString ?? "" self.bugReporter.vc_sendBugReport( - description: "Auto-reporting decryption error", + description: description, sendLogs: true, sendCrashLog: true, additionalLabels: [ @@ -223,7 +228,7 @@ extension UISIAutoReportData: Codable { ], customFields: [ "auto_uisi": uisiData, - "recipient_rageshake": matchingIssue + "recipient_rageshake": matchingIssue ?? "" ] ) }