diff --git a/CHANGES.md b/CHANGES.md index e4d8ef4d86..3484fcf48f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,15 @@ +## Changes in 1.9.17 (2023-01-26) + +🙌 Improvements + +- Analytics: Ensure E2EE never tracks UnknownError ([#7304](https://github.com/vector-im/element-ios/pull/7304)) + +🐛 Bugfixes + +- Fix a deadlock when updating the summary of a room that has a voice broadcast. ([#7300](https://github.com/vector-im/element-ios/pull/7300)) +- Space Switcher: Fix a bug where the avatars would all be the same. ([#7305](https://github.com/vector-im/element-ios/issues/7305)) + + ## Changes in 1.9.16 (2023-01-24) ✨ Features diff --git a/Config/AppVersion.xcconfig b/Config/AppVersion.xcconfig index 7271fabb83..210603b235 100644 --- a/Config/AppVersion.xcconfig +++ b/Config/AppVersion.xcconfig @@ -15,5 +15,5 @@ // // Version -MARKETING_VERSION = 1.9.16 -CURRENT_PROJECT_VERSION = 1.9.16 +MARKETING_VERSION = 1.9.17 +CURRENT_PROJECT_VERSION = 1.9.17 diff --git a/Riot/Modules/Analytics/DecryptionFailure.swift b/Riot/Modules/Analytics/DecryptionFailure.swift index 2f33f6369f..1c991db88f 100644 --- a/Riot/Modules/Analytics/DecryptionFailure.swift +++ b/Riot/Modules/Analytics/DecryptionFailure.swift @@ -16,12 +16,10 @@ import AnalyticsEvents -/// Failure reasons as defined in https://docs.google.com/document/d/1es7cTCeJEXXfRCTRgZerAM2Wg5ZerHjvlpfTW-gsOfI. @objc enum DecryptionFailureReason: Int { case unspecified case olmKeysNotSent case olmIndexError - case unexpected var errorName: AnalyticsEvent.Error.Name { switch self { @@ -31,8 +29,6 @@ import AnalyticsEvents return .OlmKeysNotSentError case .olmIndexError: return .OlmIndexError - case .unexpected: - return .UnknownError } } } diff --git a/Riot/Modules/Analytics/DecryptionFailureTracker.m b/Riot/Modules/Analytics/DecryptionFailureTracker.m index d175f9fd17..4a749b71aa 100644 --- a/Riot/Modules/Analytics/DecryptionFailureTracker.m +++ b/Riot/Modules/Analytics/DecryptionFailureTracker.m @@ -105,12 +105,9 @@ - (void)reportUnableToDecryptErrorForEvent:(MXEvent *)event withRoomState:(MXRoo reason = DecryptionFailureReasonOlmIndexError; break; - case MXDecryptingErrorEncryptionNotEnabledCode: - case MXDecryptingErrorUnableToDecryptCode: - reason = DecryptionFailureReasonUnexpected; - break; - default: + // All other error codes will be tracked as `OlmUnspecifiedError` and will include `context` containing + // the actual error code and localized description reason = DecryptionFailureReasonUnspecified; break; } diff --git a/Riot/Modules/Analytics/Helpers/MXCallHangupReason+Analytics.swift b/Riot/Modules/Analytics/Helpers/MXCallHangupReason+Analytics.swift index 4b8911ce89..c60f354466 100644 --- a/Riot/Modules/Analytics/Helpers/MXCallHangupReason+Analytics.swift +++ b/Riot/Modules/Analytics/Helpers/MXCallHangupReason+Analytics.swift @@ -21,6 +21,9 @@ extension __MXCallHangupReason { switch self { case .userHangup: return .VoipUserHangup + case .userBusy: + // There is no dedicated analytics event for `userBusy` error + return .UnknownError case .inviteTimeout: return .VoipInviteTimeout case .iceFailed: @@ -32,6 +35,9 @@ extension __MXCallHangupReason { case .unknownError: return .UnknownError default: + MXLog.failure("Unknown or unhandled hangup reason", context: [ + "reason": rawValue + ]) return .UnknownError } } diff --git a/Riot/Utils/EventFormatter.m b/Riot/Utils/EventFormatter.m index 21cd496dde..db526607a8 100644 --- a/Riot/Utils/EventFormatter.m +++ b/Riot/Utils/EventFormatter.m @@ -567,7 +567,8 @@ - (BOOL)session:(MXSession *)session updateRoomSummary:(MXRoomSummary *)summary return [self session:session updateRoomSummary:summary withVoiceBroadcastInfoStateEvent:lastVoiceBroadcastInfoEvent - voiceBroadcastInfoStartedEvent:voiceBroadcastInfoStartedEvent roomState:roomState]; + voiceBroadcastInfoStartedEvent:voiceBroadcastInfoStartedEvent + roomState:roomState]; } } @@ -624,22 +625,8 @@ - (MXEvent *)voiceBroadcastInfoStartedEventWithEvent:(MXEvent *)voiceBroadcastIn } else { - dispatch_group_t group = dispatch_group_create(); - dispatch_group_enter(group); - - __block MXEvent *voiceBroadcastInfoStartedEvent; - - [session eventWithEventId:voiceBroadcastInfo.voiceBroadcastId inRoom:roomId success:^(MXEvent *resultEvent) { - voiceBroadcastInfoStartedEvent = resultEvent; - dispatch_group_leave(group); - } failure:^(NSError *error) { - MXLogErrorDetails(@"[EventFormatter] Fetch eventWithEventId with error = %@", error.description); - dispatch_group_leave(group); - }]; - - dispatch_group_wait(group, DISPATCH_TIME_FOREVER); - - return voiceBroadcastInfoStartedEvent; + // Search for the event only in the store to avoid network calls while updating the room summary (this a synchronous process and we cannot delay it). + return [mxSession.store eventWithEventId:voiceBroadcastInfo.voiceBroadcastId inRoom:roomId]; } } diff --git a/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift b/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift index b143d4d306..3ddd4d472c 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/View/AvatarImage.swift @@ -26,9 +26,11 @@ struct AvatarImage: View { var displayName: String? var size: AvatarSize + @State private var avatar: AvatarViewState = .empty + var body: some View { Group { - switch viewModel.viewState { + switch avatar { case .empty: ProgressView() case .placeholder(let firstCharacter, let colorIndex): @@ -42,13 +44,16 @@ struct AvatarImage: View { .frame(maxWidth: CGFloat(size.rawValue), maxHeight: CGFloat(size.rawValue)) .clipShape(Circle()) .onAppear { - viewModel.loadAvatar( - mxContentUri: mxContentUri, - matrixItemId: matrixItemId, - displayName: displayName, - colorCount: theme.colors.namesAndAvatars.count, - avatarSize: size - ) + avatar = viewModel.placeholderAvatar(matrixItemId: matrixItemId, + displayName: displayName, + colorCount: theme.colors.namesAndAvatars.count) + viewModel.loadAvatar(mxContentUri: mxContentUri, + matrixItemId: matrixItemId, + displayName: displayName, + colorCount: theme.colors.namesAndAvatars.count, + avatarSize: size ) { newState in + avatar = newState + } } } } diff --git a/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift b/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift index 2662831e15..708c26a2b5 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/View/SpaceAvatarImage.swift @@ -26,9 +26,11 @@ struct SpaceAvatarImage: View { var displayName: String? var size: AvatarSize + @State private var avatar: AvatarViewState = .empty + var body: some View { Group { - switch viewModel.viewState { + switch avatar { case .empty: ProgressView() case .placeholder(let firstCharacter, let colorIndex): @@ -48,23 +50,27 @@ struct SpaceAvatarImage: View { .clipShape(RoundedRectangle(cornerRadius: 8)) } } - .onChange(of: displayName, perform: { value in - viewModel.loadAvatar( - mxContentUri: mxContentUri, - matrixItemId: matrixItemId, - displayName: value, - colorCount: theme.colors.namesAndAvatars.count, - avatarSize: size - ) - }) + .onChange(of: displayName) { value in + guard case .placeholder = avatar else { return } + viewModel.loadAvatar(mxContentUri: mxContentUri, + matrixItemId: matrixItemId, + displayName: value, + colorCount: theme.colors.namesAndAvatars.count, + avatarSize: size) { newState in + avatar = newState + } + } .onAppear { - viewModel.loadAvatar( - mxContentUri: mxContentUri, - matrixItemId: matrixItemId, - displayName: displayName, - colorCount: theme.colors.namesAndAvatars.count, - avatarSize: size - ) + avatar = viewModel.placeholderAvatar(matrixItemId: matrixItemId, + displayName: displayName, + colorCount: theme.colors.namesAndAvatars.count) + viewModel.loadAvatar(mxContentUri: mxContentUri, + matrixItemId: matrixItemId, + displayName: displayName, + colorCount: theme.colors.namesAndAvatars.count, + avatarSize: size) { newState in + avatar = newState + } } } } diff --git a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift index 10055738d4..68037f5521 100644 --- a/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift +++ b/RiotSwiftUI/Modules/Common/Avatar/ViewModel/AvatarViewModel.swift @@ -22,14 +22,22 @@ import Foundation final class AvatarViewModel: ObservableObject { private let avatarService: AvatarServiceProtocol - @Published private(set) var viewState = AvatarViewState.empty - init(avatarService: AvatarServiceProtocol) { self.avatarService = avatarService } private var cancellables = Set() + func placeholderAvatar(matrixItemId: String, + displayName: String?, + colorCount: Int) -> AvatarViewState { + let placeholderViewModel = PlaceholderAvatarViewModel(displayName: displayName, + matrixItemId: matrixItemId, + colorCount: colorCount) + + return .placeholder(placeholderViewModel.firstCharacterCapitalized, placeholderViewModel.stableColorIndex) + } + /// Load an avatar /// - Parameters: /// - mxContentUri: The matrix content URI of the avatar. @@ -41,14 +49,10 @@ final class AvatarViewModel: ObservableObject { matrixItemId: String, displayName: String?, colorCount: Int, - avatarSize: AvatarSize) { - let placeholderViewModel = PlaceholderAvatarViewModel(displayName: displayName, - matrixItemId: matrixItemId, - colorCount: colorCount) - - viewState = .placeholder(placeholderViewModel.firstCharacterCapitalized, placeholderViewModel.stableColorIndex) - + avatarSize: AvatarSize, + avatarCompletion: @escaping (AvatarViewState) -> Void) { guard let mxContentUri = mxContentUri, mxContentUri.count > 0 else { + avatarCompletion(placeholderAvatar(matrixItemId: matrixItemId, displayName: displayName, colorCount: colorCount)) return } @@ -56,8 +60,9 @@ final class AvatarViewModel: ObservableObject { .sink { completion in guard case let .failure(error) = completion else { return } UILog.error("[AvatarService] Failed to retrieve avatar", context: error) + // No need to call the completion, there's nothing we can do and the error is logged. } receiveValue: { image in - self.viewState = .avatar(image) + avatarCompletion(.avatar(image)) } .store(in: &cancellables) }