mirror of
https://github.com/vector-im/element-ios.git
synced 2024-09-28 15:22:39 +00:00
Inform the user about decryption errors during a voice broadcast
This commit is contained in:
parent
971a9f0cfe
commit
c0e5697f07
10 changed files with 120 additions and 15 deletions
|
@ -2225,6 +2225,7 @@ Tap the + to start adding people.";
|
|||
"voice_broadcast_connection_error_title" = "Connection error";
|
||||
"voice_broadcast_connection_error_message" = "Unfortunately we’re unable to start a recording right now. Please try again later.";
|
||||
"voice_broadcast_recorder_connection_error" = "Connection error - Recording paused";
|
||||
"voice_broadcast_playback_unable_to_decrypt" = "Unable to decrypt this voice broadcast.";
|
||||
|
||||
// MARK: - Version check
|
||||
|
||||
|
|
|
@ -9235,6 +9235,10 @@ public class VectorL10n: NSObject {
|
|||
public static var voiceBroadcastPlaybackLockScreenPlaceholder: String {
|
||||
return VectorL10n.tr("Vector", "voice_broadcast_playback_lock_screen_placeholder")
|
||||
}
|
||||
/// Unable to decrypt this voice broadcast.
|
||||
public static var voiceBroadcastPlaybackUnableToDecrypt: String {
|
||||
return VectorL10n.tr("Vector", "voice_broadcast_playback_unable_to_decrypt")
|
||||
}
|
||||
/// Connection error - Recording paused
|
||||
public static var voiceBroadcastRecorderConnectionError: String {
|
||||
return VectorL10n.tr("Vector", "voice_broadcast_recorder_connection_error")
|
||||
|
|
|
@ -1053,8 +1053,13 @@ static NSString *const kRepliedTextPattern = @"<mx-reply>.*<blockquote>.*<br>(.*
|
|||
else if ([event.decryptionError.domain isEqualToString:MXDecryptingErrorDomain]
|
||||
&& event.decryptionError.code == MXDecryptingErrorUnknownInboundSessionIdCode)
|
||||
{
|
||||
// Make the unknown inbound session id error description more user friendly
|
||||
errorDescription = [VectorL10n noticeCryptoErrorUnknownInboundSessionId];
|
||||
// Hide the decryption error for event related to another one (like voicebroadcast chunks)
|
||||
if ([event.relatesTo.relationType isEqualToString:MXEventRelationTypeReference]) {
|
||||
displayText = nil;
|
||||
} else {
|
||||
// Make the unknown inbound session id error description more user friendly
|
||||
errorDescription = [VectorL10n noticeCryptoErrorUnknownInboundSessionId];
|
||||
}
|
||||
}
|
||||
else if ([event.decryptionError.domain isEqualToString:MXDecryptingErrorDomain]
|
||||
&& event.decryptionError.code == MXDecryptingErrorDuplicateMessageIndexCode)
|
||||
|
|
|
@ -35,6 +35,7 @@ public protocol VoiceBroadcastAggregatorDelegate: AnyObject {
|
|||
func voiceBroadcastAggregator(_ aggregator: VoiceBroadcastAggregator, didReceiveChunk: VoiceBroadcastChunk)
|
||||
func voiceBroadcastAggregator(_ aggregator: VoiceBroadcastAggregator, didReceiveState: VoiceBroadcastInfoState)
|
||||
func voiceBroadcastAggregatorDidUpdateData(_ aggregator: VoiceBroadcastAggregator)
|
||||
func voiceBroadcastAggregator(_ aggregator: VoiceBroadcastAggregator, didUpdateUndecryptableEventList events: Set<MXEvent>)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -58,6 +59,7 @@ public class VoiceBroadcastAggregator {
|
|||
private var referenceEventsListener: Any?
|
||||
|
||||
private var events: [MXEvent] = []
|
||||
private var undecryptableEvents: Set<MXEvent> = []
|
||||
|
||||
public private(set) var voiceBroadcast: VoiceBroadcast! {
|
||||
didSet {
|
||||
|
@ -84,7 +86,7 @@ public class VoiceBroadcastAggregator {
|
|||
|
||||
try buildVoiceBroadcastStartContent()
|
||||
}
|
||||
|
||||
|
||||
private func buildVoiceBroadcastStartContent() throws {
|
||||
guard let event = session.store.event(withEventId: voiceBroadcastStartEventId, inRoom: room.roomId),
|
||||
let eventContent = VoiceBroadcastInfo(fromJSON: event.content),
|
||||
|
@ -118,7 +120,11 @@ public class VoiceBroadcastAggregator {
|
|||
|
||||
@objc private func eventDidDecrypt(sender: Notification) {
|
||||
guard let event = sender.object as? MXEvent else { return }
|
||||
|
||||
|
||||
if undecryptableEvents.remove(event) != nil {
|
||||
delegate?.voiceBroadcastAggregator(self, didUpdateUndecryptableEventList: undecryptableEvents)
|
||||
}
|
||||
|
||||
self.handleEvent(event: event)
|
||||
}
|
||||
|
||||
|
@ -138,8 +144,19 @@ public class VoiceBroadcastAggregator {
|
|||
private func updateVoiceBroadcast(event: MXEvent) {
|
||||
guard event.sender == self.voiceBroadcastSenderId,
|
||||
let relatedEventId = event.relatesTo?.eventId,
|
||||
relatedEventId == self.voiceBroadcastStartEventId,
|
||||
event.content[VoiceBroadcastSettings.voiceBroadcastContentKeyChunkType] != nil else {
|
||||
relatedEventId == self.voiceBroadcastStartEventId else {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle decryption errors
|
||||
if event.decryptionError != nil {
|
||||
self.undecryptableEvents.insert(event)
|
||||
self.delegate?.voiceBroadcastAggregator(self, didUpdateUndecryptableEventList: self.undecryptableEvents)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
guard event.content[VoiceBroadcastSettings.voiceBroadcastContentKeyChunkType] != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -192,15 +209,22 @@ public class VoiceBroadcastAggregator {
|
|||
}
|
||||
|
||||
self.events.removeAll()
|
||||
self.undecryptableEvents.removeAll()
|
||||
self.voiceBroadcastLastChunkSequence = 0
|
||||
|
||||
let filteredChunk = response.chunk.filter { event in
|
||||
event.sender == self.voiceBroadcastSenderId &&
|
||||
event.content[VoiceBroadcastSettings.voiceBroadcastContentKeyChunkType] != nil
|
||||
}
|
||||
|
||||
self.events.append(contentsOf: filteredChunk)
|
||||
|
||||
|
||||
let decryptionFailure = response.chunk.filter { event in
|
||||
event.sender == self.voiceBroadcastSenderId &&
|
||||
event.decryptionError != nil
|
||||
}
|
||||
self.undecryptableEvents.formUnion(decryptionFailure)
|
||||
self.delegate?.voiceBroadcastAggregator(self, didUpdateUndecryptableEventList: self.undecryptableEvents)
|
||||
|
||||
let eventTypes = [VoiceBroadcastSettings.voiceBroadcastInfoContentKeyType, kMXEventTypeStringRoomMessage]
|
||||
self.referenceEventsListener = self.room.listen(toEventsOfTypes: eventTypes, onEvent: { [weak self] event, direction, roomState in
|
||||
self?.handleEvent(event: event, direction: direction, roomState: roomState)
|
||||
|
|
|
@ -111,7 +111,8 @@ class VoiceBroadcastPlaybackViewModel: VoiceBroadcastPlaybackViewModelType, Voic
|
|||
broadcastState: voiceBroadcastAggregator.voiceBroadcastState,
|
||||
playbackState: .stopped,
|
||||
playingState: VoiceBroadcastPlayingState(duration: Float(voiceBroadcastAggregator.voiceBroadcast.duration), isLive: false, canMoveForward: false, canMoveBackward: false),
|
||||
bindings: VoiceBroadcastPlaybackViewStateBindings(progress: 0))
|
||||
bindings: VoiceBroadcastPlaybackViewStateBindings(progress: 0),
|
||||
decryptionState: VoiceBroadcastPlaybackDecryptionState(errorCount: 0))
|
||||
super.init(initialViewState: viewState)
|
||||
|
||||
displayLink = CADisplayLink(target: WeakTarget(self, selector: #selector(handleDisplayLinkTick)), selector: WeakTarget.triggerSelector)
|
||||
|
@ -486,6 +487,17 @@ extension VoiceBroadcastPlaybackViewModel: VoiceBroadcastAggregatorDelegate {
|
|||
handleVoiceBroadcastChunksProcessing()
|
||||
}
|
||||
}
|
||||
|
||||
func voiceBroadcastAggregator(_ aggregator: VoiceBroadcastAggregator, didUpdateUndecryptableEventList events: Set<MXEvent>) {
|
||||
state.decryptionState.errorCount = events.count
|
||||
if events.count > 0 {
|
||||
MXLog.debug("[VoiceBroadcastPlaybackViewModel] voice broadcast decryption error count: \(events.count)/\(aggregator.voiceBroadcast.chunks.count)")
|
||||
|
||||
if [.playing, .buffering].contains(state.playbackState) {
|
||||
pause()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
//
|
||||
// Copyright 2023 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 SwiftUI
|
||||
|
||||
struct VoiceBroadcastPlaybackDecryptionErrorView: View {
|
||||
// MARK: - Properties
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@Environment(\.theme) private var theme: ThemeSwiftUI
|
||||
|
||||
// MARK: Public
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
HStack(spacing: 0) {
|
||||
Image(uiImage: Asset.Images.errorIcon.image)
|
||||
.frame(width: 40, height: 40)
|
||||
Text(VectorL10n.voiceBroadcastPlaybackUnableToDecrypt)
|
||||
.multilineTextAlignment(.center)
|
||||
.font(theme.fonts.caption1)
|
||||
.foregroundColor(theme.colors.alert)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}
|
||||
|
||||
struct VoiceBroadcastPlaybackDecryptionErrorView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VoiceBroadcastPlaybackDecryptionErrorView()
|
||||
}
|
||||
}
|
|
@ -91,7 +91,7 @@ struct VoiceBroadcastPlaybackView: View {
|
|||
}
|
||||
}
|
||||
}.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
|
||||
if viewModel.viewState.broadcastState != .stopped {
|
||||
Label {
|
||||
Text(VectorL10n.voiceBroadcastLive)
|
||||
|
@ -109,7 +109,12 @@ struct VoiceBroadcastPlaybackView: View {
|
|||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(EdgeInsets(top: 0.0, leading: 0.0, bottom: 4.0, trailing: 0.0))
|
||||
|
||||
if viewModel.viewState.playbackState == .error {
|
||||
if viewModel.viewState.decryptionState.errorCount > 0 {
|
||||
VoiceBroadcastPlaybackDecryptionErrorView()
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.accessibilityIdentifier("decryptionErrorView")
|
||||
}
|
||||
else if viewModel.viewState.playbackState == .error {
|
||||
VoiceBroadcastPlaybackErrorView()
|
||||
} else {
|
||||
HStack (spacing: 34.0) {
|
||||
|
@ -156,8 +161,8 @@ struct VoiceBroadcastPlaybackView: View {
|
|||
}
|
||||
|
||||
VoiceBroadcastSlider(value: $viewModel.progress,
|
||||
minValue: 0.0,
|
||||
maxValue: viewModel.viewState.playingState.duration) { didChange in
|
||||
minValue: 0.0,
|
||||
maxValue: viewModel.viewState.playingState.duration) { didChange in
|
||||
viewModel.send(viewAction: .sliderChange(didChange: didChange))
|
||||
}
|
||||
|
||||
|
|
|
@ -48,12 +48,17 @@ struct VoiceBroadcastPlayingState {
|
|||
var canMoveBackward: Bool
|
||||
}
|
||||
|
||||
struct VoiceBroadcastPlaybackDecryptionState {
|
||||
var errorCount: Int
|
||||
}
|
||||
|
||||
struct VoiceBroadcastPlaybackViewState: BindableState {
|
||||
var details: VoiceBroadcastPlaybackDetails
|
||||
var broadcastState: VoiceBroadcastInfoState
|
||||
var playbackState: VoiceBroadcastPlaybackState
|
||||
var playingState: VoiceBroadcastPlayingState
|
||||
var bindings: VoiceBroadcastPlaybackViewStateBindings
|
||||
var decryptionState: VoiceBroadcastPlaybackDecryptionState
|
||||
}
|
||||
|
||||
struct VoiceBroadcastPlaybackViewStateBindings {
|
||||
|
|
|
@ -43,11 +43,12 @@ enum MockVoiceBroadcastPlaybackScreenState: MockScreenState, CaseIterable {
|
|||
var screenView: ([Any], AnyView) {
|
||||
|
||||
let details = VoiceBroadcastPlaybackDetails(senderDisplayName: "Alice", avatarData: AvatarInput(mxContentUri: "", matrixItemId: "!fakeroomid:matrix.org", displayName: "The name of the room"))
|
||||
let viewModel = MockVoiceBroadcastPlaybackViewModel(initialViewState: VoiceBroadcastPlaybackViewState(details: details, broadcastState: .started, playbackState: .stopped, playingState: VoiceBroadcastPlayingState(duration: 10.0, isLive: true, canMoveForward: false, canMoveBackward: false), bindings: VoiceBroadcastPlaybackViewStateBindings(progress: 0)))
|
||||
let viewModel = MockVoiceBroadcastPlaybackViewModel(initialViewState: VoiceBroadcastPlaybackViewState(details: details, broadcastState: .started, playbackState: .stopped, playingState: VoiceBroadcastPlayingState(duration: 10.0, isLive: true, canMoveForward: false, canMoveBackward: false), bindings: VoiceBroadcastPlaybackViewStateBindings(progress: 0), decryptionState: VoiceBroadcastPlaybackDecryptionState(errorCount: 0)))
|
||||
|
||||
return (
|
||||
[false, viewModel],
|
||||
AnyView(VoiceBroadcastPlaybackView(viewModel: viewModel.context))
|
||||
AnyView(VoiceBroadcastPlaybackView(viewModel: viewModel.context)
|
||||
.environmentObject(AvatarViewModel.withMockedServices()))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
1
changelog.d/7189.change
Normal file
1
changelog.d/7189.change
Normal file
|
@ -0,0 +1 @@
|
|||
Voice Broadcast: Inform the user about decryption errors during a voice broadcast.
|
Loading…
Reference in a new issue