From daa48a87cb356ac66b1078bd2b308ff939990922 Mon Sep 17 00:00:00 2001 From: aringenbach Date: Mon, 21 Nov 2022 18:43:53 +0100 Subject: [PATCH 01/19] Update Rich text editor library version --- Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- changelog.d/6930.bugfix | 1 + changelog.d/6983.bugfix | 1 + changelog.d/7042.bugfix | 1 + changelog.d/7086.bugfix | 1 + project.yml | 2 +- 6 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 changelog.d/6930.bugfix create mode 100644 changelog.d/6983.bugfix create mode 100644 changelog.d/7042.bugfix create mode 100644 changelog.d/7086.bugfix diff --git a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved index 816ccb018..a3f2f6fc9 100644 --- a/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Riot.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,7 +23,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/matrix-org/matrix-wysiwyg-composer-swift", "state" : { - "revision" : "2469f27b7e1e51aaa135e09f9005eb10fda686e6" + "revision" : "1fbffd0321eb47abcd664ad19c6c943b60abf399" } }, { diff --git a/changelog.d/6930.bugfix b/changelog.d/6930.bugfix new file mode 100644 index 000000000..46609ff52 --- /dev/null +++ b/changelog.d/6930.bugfix @@ -0,0 +1 @@ +Labs: Rich text editor: Fix smart punctuation (e.g. double space transforms into dot) diff --git a/changelog.d/6983.bugfix b/changelog.d/6983.bugfix new file mode 100644 index 000000000..382c55062 --- /dev/null +++ b/changelog.d/6983.bugfix @@ -0,0 +1 @@ +Labs: Rich text editor: Fix input for keyboards that use symbols composition and replacement (e.g. Japanese Romaji, Korean) diff --git a/changelog.d/7042.bugfix b/changelog.d/7042.bugfix new file mode 100644 index 000000000..1f909296b --- /dev/null +++ b/changelog.d/7042.bugfix @@ -0,0 +1 @@ +Labs: Rich text editor: Fix keyboard suggestions for non-latin keyboards (e.g. Chinese Pinyin) diff --git a/changelog.d/7086.bugfix b/changelog.d/7086.bugfix new file mode 100644 index 000000000..ef0f9b382 --- /dev/null +++ b/changelog.d/7086.bugfix @@ -0,0 +1 @@ +Labs: Rich text editor: Fix broken backspace around some type of whitespaces diff --git a/project.yml b/project.yml index 0fe95db19..6a08562d4 100644 --- a/project.yml +++ b/project.yml @@ -53,7 +53,7 @@ packages: branch: main WysiwygComposer: url: https://github.com/matrix-org/matrix-wysiwyg-composer-swift - revision: 2469f27b7e1e51aaa135e09f9005eb10fda686e6 + revision: 1fbffd0321eb47abcd664ad19c6c943b60abf399 DeviceKit: url: https://github.com/devicekit/DeviceKit majorVersion: 4.7.0 From 8015f297aebd9aa6c43f452281fd0023605b0a84 Mon Sep 17 00:00:00 2001 From: Doug Date: Fri, 18 Nov 2022 16:58:29 +0000 Subject: [PATCH 02/19] Fix a crash when a voice message finishes. --- Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift | 3 ++- changelog.d/7074.bugfix | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 changelog.d/7074.bugfix diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift index 231773f2b..4a242a91c 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageAudioPlayer.swift @@ -61,7 +61,8 @@ class VoiceMessageAudioPlayer: NSObject { } var currentTime: TimeInterval { - return abs(CMTimeGetSeconds(audioPlayer?.currentTime() ?? .zero)) + let currentTime = abs(CMTimeGetSeconds(audioPlayer?.currentTime() ?? .zero)) + return currentTime.isFinite ? currentTime : .zero } var playerItems: [AVPlayerItem] { diff --git a/changelog.d/7074.bugfix b/changelog.d/7074.bugfix new file mode 100644 index 000000000..39d6b2e5d --- /dev/null +++ b/changelog.d/7074.bugfix @@ -0,0 +1 @@ +Voice Messages: Fix crash when voice message finishes playing. From 54e5753a21e97778a3c0a47d38b4fcec51565e52 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 22 Nov 2022 18:21:59 +0100 Subject: [PATCH 03/19] hide button and revert state when rotating on an iPhone, also fixed the positioning of the strikethrough and underline, and the maxCompressed size in landscape mode is always adapted to be visible. --- .../WysiwygInputToolbarView.swift | 7 ++++++- .../Room/Composer/Model/ComposerModels.swift | 2 +- .../Modules/Room/Composer/View/Composer.swift | 21 ++++++++++++++----- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift index 45c10004f..1ab81876a 100644 --- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift +++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift @@ -286,9 +286,14 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp private func updateTextViewHeight() { let height = UIScreen.main.bounds.height let barOffset: CGFloat = 68 - let toolbarHeight: CGFloat = 82 + let toolbarHeight: CGFloat = 96 let finalHeight = height - keyboardHeight - toolbarHeight - barOffset wysiwygViewModel.maxExpandedHeight = finalHeight + if finalHeight < 200 { + wysiwygViewModel.maxCompressedHeight = finalHeight > wysiwygViewModel.minHeight ? finalHeight : wysiwygViewModel.minHeight + } else { + wysiwygViewModel.maxCompressedHeight = 200 + } } // MARK: - HtmlRoomInputToolbarViewProtocol diff --git a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift index badcd2b20..84b5f8f95 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift @@ -34,8 +34,8 @@ struct FormatItem { enum FormatType { case bold case italic - case strikethrough case underline + case strikethrough } extension FormatType: CaseIterable, Identifiable { diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index c21bd5ed5..e34b090d3 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -14,7 +14,6 @@ // limitations under the License. // -import DSBottomSheet import SwiftUI import WysiwygComposer @@ -22,7 +21,6 @@ struct Composer: View { // MARK: - Properties // MARK: Private - @ObservedObject private var viewModel: ComposerViewModelType.Context @ObservedObject private var wysiwygViewModel: WysiwygComposerViewModel private let resizeAnimationDuration: Double @@ -33,12 +31,17 @@ struct Composer: View { @Environment(\.theme) private var theme: ThemeSwiftUI @State private var isActionButtonShowing = false + @State private var isToggleButtonHidden = false + + private var isLandscapeIphone: Bool { + let device = UIDevice.current + return device.isPhone && device.orientation.isLandscape + } private let horizontalPadding: CGFloat = 12 private let borderHeight: CGFloat = 40 - private let minTextViewHeight: CGFloat = 22 private var verticalPadding: CGFloat { - (borderHeight - minTextViewHeight) / 2 + (borderHeight - wysiwygViewModel.minHeight) / 2 } private var topPadding: CGFloat { @@ -46,7 +49,7 @@ struct Composer: View { } private var cornerRadius: CGFloat { - if viewModel.viewState.shouldDisplayContext || wysiwygViewModel.idealHeight > minTextViewHeight { + if viewModel.viewState.shouldDisplayContext || wysiwygViewModel.idealHeight > wysiwygViewModel.minHeight { return 14 } else { return borderHeight / 2 @@ -129,6 +132,7 @@ struct Composer: View { .frame(width: 16, height: 16) } .accessibilityIdentifier(toggleButtonAcccessibilityIdentifier) + .isHidden(isToggleButtonHidden) .padding(.leading, 12) .padding(.trailing, 4) } @@ -200,6 +204,7 @@ struct Composer: View { self.resizeAnimationDuration = resizeAnimationDuration self.sendMessageAction = sendMessageAction self.showSendMediaActions = showSendMediaActions + self._isToggleButtonHidden = State(initialValue: isLandscapeIphone) } var body: some View { @@ -233,6 +238,12 @@ struct Composer: View { } .padding(.horizontal, horizontalPadding) .padding(.bottom, 4) + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + if wysiwygViewModel.maximised, isLandscapeIphone { + wysiwygViewModel.maximised = false + } + isToggleButtonHidden = isLandscapeIphone + } } } From 09235b902bd4109bee6e3a9f767ce3c00dd7319a Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 22 Nov 2022 18:28:24 +0100 Subject: [PATCH 04/19] changelog --- changelog.d/7096.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/7096.change diff --git a/changelog.d/7096.change b/changelog.d/7096.change new file mode 100644 index 000000000..8fbb5fc35 --- /dev/null +++ b/changelog.d/7096.change @@ -0,0 +1 @@ +Rich Text Editor: on iPhones when in landscape mode the fullscreen mode is disabled. \ No newline at end of file From aa8231cde1841219376b3a7cd297031e2ebe2ddc Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 22 Nov 2022 18:44:17 +0100 Subject: [PATCH 05/19] fix to make the function run on RiotSwiftUI --- RiotSwiftUI/Modules/Room/Composer/View/Composer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index e34b090d3..48e8aa030 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -35,7 +35,7 @@ struct Composer: View { private var isLandscapeIphone: Bool { let device = UIDevice.current - return device.isPhone && device.orientation.isLandscape + return device.userInterfaceIdiom == .phone && device.orientation.isLandscape } private let horizontalPadding: CGFloat = 12 From bbf43514eb57fb030e2d0cda1650d676b8eb5a87 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Tue, 22 Nov 2022 19:47:44 +0100 Subject: [PATCH 06/19] removed a reference to a constraint that was not needed but only created a constraint issue when restored during the animation for dismissal --- Riot/Modules/Room/RoomViewController.xib | 1 - 1 file changed, 1 deletion(-) diff --git a/Riot/Modules/Room/RoomViewController.xib b/Riot/Modules/Room/RoomViewController.xib index f9be745c3..f33d661bd 100644 --- a/Riot/Modules/Room/RoomViewController.xib +++ b/Riot/Modules/Room/RoomViewController.xib @@ -36,7 +36,6 @@ - From 341b9bd6b84bf374646fe6354b8abae4f9a76d4d Mon Sep 17 00:00:00 2001 From: Philippe Loriaux Date: Wed, 23 Nov 2022 08:43:22 +0100 Subject: [PATCH 07/19] Add Voice Broadcast left time countdown --- Config/BuildSettings.swift | 2 +- .../Contents.json | 12 ++++++ .../voice_broadcast_time_left.svg | 3 ++ Riot/Assets/en.lproj/Vector.strings | 1 + Riot/Generated/Images.swift | 1 + Riot/Generated/Strings.swift | 4 ++ .../VoiceBroadcastRecorderService.swift | 40 ++++++++++++++++++- ...oiceBroadcastRecorderServiceProtocol.swift | 1 + .../View/VoiceBroadcastRecorderView.swift | 10 +++++ .../VoiceBroadcastRecorderModels.swift | 6 +++ .../VoiceBroadcastRecorderScreenState.swift | 3 +- .../VoiceBroadcastRecorderViewModel.swift | 15 +++++++ 12 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/Contents.json create mode 100644 Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/voice_broadcast_time_left.svg diff --git a/Config/BuildSettings.swift b/Config/BuildSettings.swift index d6b022948..2f85f3c13 100644 --- a/Config/BuildSettings.swift +++ b/Config/BuildSettings.swift @@ -409,7 +409,7 @@ final class BuildSettings: NSObject { // MARK: - Voice Broadcast static let voiceBroadcastChunkLength: Int = 120 - static let voiceBroadcastMaxLength: UInt64 = 144000 + static let voiceBroadcastMaxLength: UInt = 14400 // 240min. // MARK: - MXKAppSettings static let enableBotCreation: Bool = false diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/Contents.json b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/Contents.json new file mode 100644 index 000000000..6dbed5648 --- /dev/null +++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "voice_broadcast_time_left.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/voice_broadcast_time_left.svg b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/voice_broadcast_time_left.svg new file mode 100644 index 000000000..eece236c8 --- /dev/null +++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/voice_broadcast_time_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index fa68764c3..a39163b86 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -2196,6 +2196,7 @@ Tap the + to start adding people."; "voice_broadcast_playback_loading_error" = "Unable to play this voice broadcast."; "voice_broadcast_live" = "Live"; "voice_broadcast_tile" = "Voice broadcast"; +"voice_broadcast_time_left" = "%@ left"; // Mark: - Version check diff --git a/Riot/Generated/Images.swift b/Riot/Generated/Images.swift index 94352b0be..dcf78a2e1 100644 --- a/Riot/Generated/Images.swift +++ b/Riot/Generated/Images.swift @@ -347,6 +347,7 @@ internal class Asset: NSObject { internal static let voiceBroadcastStop = ImageAsset(name: "voice_broadcast_stop") internal static let voiceBroadcastTileLive = ImageAsset(name: "voice_broadcast_tile_live") internal static let voiceBroadcastTileMic = ImageAsset(name: "voice_broadcast_tile_mic") + internal static let voiceBroadcastTimeLeft = ImageAsset(name: "voice_broadcast_time_left") internal static let launchScreenLogo = ImageAsset(name: "launch_screen_logo") } @objcMembers diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index 36091cc12..78729dbf8 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -9139,6 +9139,10 @@ public class VectorL10n: NSObject { public static var voiceBroadcastTile: String { return VectorL10n.tr("Vector", "voice_broadcast_tile") } + /// %@ left + public static func voiceBroadcastTimeLeft(_ p1: String) -> String { + return VectorL10n.tr("Vector", "voice_broadcast_time_left", p1) + } /// Can't start a new voice broadcast public static var voiceBroadcastUnauthorizedTitle: String { return VectorL10n.tr("Vector", "voice_broadcast_unauthorized_title") diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/MatrixSDK/VoiceBroadcastRecorderService.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/MatrixSDK/VoiceBroadcastRecorderService.swift index f2e28e5da..10538095d 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/MatrixSDK/VoiceBroadcastRecorderService.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/MatrixSDK/VoiceBroadcastRecorderService.swift @@ -34,7 +34,13 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol { private var chunkFile: AVAudioFile! = nil private var chunkFrames: AVAudioFrameCount = 0 private var chunkFileNumber: Int = 0 - + + private var currentElapsedTime: UInt = 0 // Time in seconds. + private var currentRemainingTime: UInt { // Time in seconds. + BuildSettings.voiceBroadcastMaxLength - currentElapsedTime + } + private var elapsedTimeTimer: Timer? + // MARK: Public weak var serviceDelegate: VoiceBroadcastRecorderServiceDelegate? @@ -67,12 +73,14 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol { } try audioEngine.start() + startTimer() // Disable the sleep mode during the recording until we are able to handle it UIApplication.shared.isIdleTimerDisabled = true } catch { MXLog.debug("[VoiceBroadcastRecorderService] startRecordingVoiceBroadcast error", context: error) stopRecordingVoiceBroadcast() + invalidateTimer() } } @@ -81,6 +89,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol { audioEngine.stop() audioEngine.inputNode.removeTap(onBus: audioNodeBus) UIApplication.shared.isIdleTimerDisabled = false + invalidateTimer() voiceBroadcastService?.stopVoiceBroadcast(success: { [weak self] _ in MXLog.debug("[VoiceBroadcastRecorderService] Stopped") @@ -110,6 +119,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol { func pauseRecordingVoiceBroadcast() { audioEngine.pause() UIApplication.shared.isIdleTimerDisabled = false + invalidateTimer() voiceBroadcastService?.pauseVoiceBroadcast(success: { [weak self] _ in guard let self = self else { return } @@ -126,6 +136,7 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol { func resumeRecordingVoiceBroadcast() { try? audioEngine.start() + startTimer() voiceBroadcastService?.resumeVoiceBroadcast(success: { [weak self] _ in guard let self = self else { return } @@ -143,12 +154,14 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol { private func resetValues() { chunkFrames = 0 chunkFileNumber = 0 + currentElapsedTime = 0 } /// Release the service private func tearDownVoiceBroadcastService() { resetValues() session.tearDownVoiceBroadcastService() + invalidateTimer() do { try AVAudioSession.sharedInstance().setActive(false) @@ -157,6 +170,31 @@ class VoiceBroadcastRecorderService: VoiceBroadcastRecorderServiceProtocol { } } + /// Start ElapsedTimeTimer. + private func startTimer() { + elapsedTimeTimer = Timer.scheduledTimer(timeInterval: 1.0, + target: self, + selector: #selector(updateCurrentElapsedTimeValue), + userInfo: nil, + repeats: true) + } + + /// Invalidate ElapsedTimeTimer. + private func invalidateTimer() { + elapsedTimeTimer?.invalidate() + elapsedTimeTimer = nil + } + + /// Update currentElapsedTime value. + @objc private func updateCurrentElapsedTimeValue() { + guard currentRemainingTime > 0 else { + stopRecordingVoiceBroadcast() + return + } + currentElapsedTime += 1 + serviceDelegate?.voiceBroadcastRecorderService(self, didUpdateRemainingTime: self.currentRemainingTime) + } + /// Write audio buffer to chunk file. private func writeBuffer(_ buffer: AVAudioPCMBuffer) { let sampleRate = buffer.format.sampleRate diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/VoiceBroadcastRecorderServiceProtocol.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/VoiceBroadcastRecorderServiceProtocol.swift index 7b97eb83a..e457eb843 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/VoiceBroadcastRecorderServiceProtocol.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/Service/VoiceBroadcastRecorderServiceProtocol.swift @@ -18,6 +18,7 @@ import Foundation protocol VoiceBroadcastRecorderServiceDelegate: AnyObject { func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateState state: VoiceBroadcastRecorderState) + func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateRemainingTime remainingTime: UInt) } protocol VoiceBroadcastRecorderServiceProtocol { diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift index 411ce0333..8a5de33aa 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift @@ -53,6 +53,16 @@ struct VoiceBroadcastRecorderView: View { } icon: { Image(uiImage: Asset.Images.voiceBroadcastTileLive.image) } + + if let remainingTimeLabel = viewModel.viewState.currentRecordingState.remainingTimeLabel { + Label { + Text(remainingTimeLabel) + .foregroundColor(theme.colors.secondaryContent) + .font(theme.fonts.caption1) + } icon: { + Image(uiImage: Asset.Images.voiceBroadcastTimeLeft.image) + } + } }.frame(maxWidth: .infinity, alignment: .leading) Label { diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift index 7a2566aad..ebc1f39c3 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift @@ -35,9 +35,15 @@ struct VoiceBroadcastRecorderDetails { let avatarData: AvatarInputProtocol } +struct VoiceBroadcastRecordingState { + var remainingTime: UInt + var remainingTimeLabel: String? +} + struct VoiceBroadcastRecorderViewState: BindableState { var details: VoiceBroadcastRecorderDetails var recordingState: VoiceBroadcastRecorderState + var currentRecordingState: VoiceBroadcastRecordingState var bindings: VoiceBroadcastRecorderViewStateBindings } diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift index bc915d36a..8962da13d 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift @@ -32,7 +32,8 @@ enum MockVoiceBroadcastRecorderScreenState: MockScreenState, CaseIterable { var screenView: ([Any], AnyView) { let details = VoiceBroadcastRecorderDetails(senderDisplayName: "", avatarData: AvatarInput(mxContentUri: "", matrixItemId: "!fakeroomid:matrix.org", displayName: "The name of the room")) - let viewModel = MockVoiceBroadcastRecorderViewModel(initialViewState: VoiceBroadcastRecorderViewState(details: details, recordingState: .started, bindings: VoiceBroadcastRecorderViewStateBindings())) + let recordingState = VoiceBroadcastRecordingState(remainingTime: BuildSettings.voiceBroadcastMaxLength) + let viewModel = MockVoiceBroadcastRecorderViewModel(initialViewState: VoiceBroadcastRecorderViewState(details: details, recordingState: .started, currentRecordingState: recordingState, bindings: VoiceBroadcastRecorderViewStateBindings())) return ( [false, viewModel], diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderViewModel.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderViewModel.swift index 6e1444162..27bd2f6e8 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderViewModel.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderViewModel.swift @@ -34,8 +34,10 @@ class VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderViewModelType, Voic init(details: VoiceBroadcastRecorderDetails, recorderService: VoiceBroadcastRecorderServiceProtocol) { self.voiceBroadcastRecorderService = recorderService + let currentRecordingState = VoiceBroadcastRecordingState(remainingTime: BuildSettings.voiceBroadcastMaxLength) super.init(initialViewState: VoiceBroadcastRecorderViewState(details: details, recordingState: .stopped, + currentRecordingState: currentRecordingState, bindings: VoiceBroadcastRecorderViewStateBindings())) self.voiceBroadcastRecorderService.serviceDelegate = self @@ -77,10 +79,23 @@ class VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderViewModelType, Voic self.state.recordingState = .resumed voiceBroadcastRecorderService.resumeRecordingVoiceBroadcast() } + + private func updateRemainingTime(_ remainingTime: UInt) { + let time = TimeInterval(Double(remainingTime)) + let formatter = DateComponentsFormatter() + formatter.unitsStyle = .abbreviated + + state.currentRecordingState.remainingTime = remainingTime + state.currentRecordingState.remainingTimeLabel = VectorL10n.voiceBroadcastTimeLeft(formatter.string(from: time) ?? "0s") + } } extension VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderServiceDelegate { func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateState state: VoiceBroadcastRecorderState) { self.state.recordingState = state } + + func voiceBroadcastRecorderService(_ service: VoiceBroadcastRecorderServiceProtocol, didUpdateRemainingTime remainingTime: UInt) { + self.updateRemainingTime(remainingTime) + } } From fc0cf90678ea7048cf34460c82c7dad4da1d7279 Mon Sep 17 00:00:00 2001 From: Andy Uhnak Date: Thu, 10 Nov 2022 14:52:09 +0000 Subject: [PATCH 08/19] Display sync progress on the loading screen --- Riot/Assets/en.lproj/Vector.strings | 6 +++ Riot/Generated/Strings.swift | 12 ++++++ .../Analytics/SentryMonitoringClient.swift | 4 ++ Riot/Modules/Application/LegacyAppDelegate.m | 12 +++++- .../AuthenticationCoordinator.swift | 3 +- .../LegacyAuthenticationCoordinator.swift | 3 +- .../LaunchLoading/LaunchLoadingView.swift | 40 ++++++++++++++++++- .../LaunchLoading/LaunchLoadingView.xib | 31 +++++++++++--- .../LaunchLoadingViewController.swift | 4 +- changelog.d/pr-7101.change | 2 + 10 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 changelog.d/pr-7101.change diff --git a/Riot/Assets/en.lproj/Vector.strings b/Riot/Assets/en.lproj/Vector.strings index c06e9ca29..68a4f873d 100644 --- a/Riot/Assets/en.lproj/Vector.strings +++ b/Riot/Assets/en.lproj/Vector.strings @@ -1968,6 +1968,12 @@ Tap the + to start adding people."; "call_transfer_error_title" = "Error"; "call_transfer_error_message" = "Call transfer failed"; +// MARK: - Launch loading + +"launch_loading_server_syncing" = "Syncing with the server"; +"launch_loading_server_syncing_nth_attempt" = "Syncing with the server\n(%@ attempt)"; +"launch_loading_processing_response" = "Processing data\n%@ %%"; + // MARK: - Home "home_empty_view_title" = "Welcome to %@,\n%@"; diff --git a/Riot/Generated/Strings.swift b/Riot/Generated/Strings.swift index b9d30fc5c..41b878426 100644 --- a/Riot/Generated/Strings.swift +++ b/Riot/Generated/Strings.swift @@ -3179,6 +3179,18 @@ public class VectorL10n: NSObject { public static var later: String { return VectorL10n.tr("Vector", "later") } + /// Processing data\n%@ %% + public static func launchLoadingProcessingResponse(_ p1: String) -> String { + return VectorL10n.tr("Vector", "launch_loading_processing_response", p1) + } + /// Syncing with the server + public static var launchLoadingServerSyncing: String { + return VectorL10n.tr("Vector", "launch_loading_server_syncing") + } + /// Syncing with the server\n(%@ attempt) + public static func launchLoadingServerSyncingNthAttempt(_ p1: String) -> String { + return VectorL10n.tr("Vector", "launch_loading_server_syncing_nth_attempt", p1) + } /// Leave public static var leave: String { return VectorL10n.tr("Vector", "leave") diff --git a/Riot/Modules/Analytics/SentryMonitoringClient.swift b/Riot/Modules/Analytics/SentryMonitoringClient.swift index d15cf8039..54933a7ab 100644 --- a/Riot/Modules/Analytics/SentryMonitoringClient.swift +++ b/Riot/Modules/Analytics/SentryMonitoringClient.swift @@ -42,6 +42,10 @@ struct SentryMonitoringClient { options.enableNetworkTracking = false options.beforeSend = { event in + // Use the actual error message as issue fingerprint + if let message = event.message?.formatted { + event.fingerprint = [message] + } MXLog.debug("[SentryMonitoringClient] Issue detected: \(event)") return event } diff --git a/Riot/Modules/Application/LegacyAppDelegate.m b/Riot/Modules/Application/LegacyAppDelegate.m index 3558a4a4d..7c2975522 100644 --- a/Riot/Modules/Application/LegacyAppDelegate.m +++ b/Riot/Modules/Application/LegacyAppDelegate.m @@ -2394,7 +2394,17 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni { MXLogDebug(@"[AppDelegate] showLaunchAnimation"); - LaunchLoadingView *launchLoadingView = [LaunchLoadingView instantiate]; + LaunchLoadingView *launchLoadingView; + if (MXSDKOptions.sharedInstance.enableSyncProgress) + { + MXSession *mainSession = self.mxSessions.firstObject; + launchLoadingView = [LaunchLoadingView instantiateWithSyncProgress:mainSession.syncProgress]; + } + else + { + launchLoadingView = [LaunchLoadingView instantiateWithSyncProgress:nil]; + } + launchLoadingView.frame = window.bounds; [launchLoadingView updateWithTheme:ThemeService.shared.theme]; launchLoadingView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; diff --git a/Riot/Modules/Authentication/AuthenticationCoordinator.swift b/Riot/Modules/Authentication/AuthenticationCoordinator.swift index 5aa6b3731..8b9898992 100644 --- a/Riot/Modules/Authentication/AuthenticationCoordinator.swift +++ b/Riot/Modules/Authentication/AuthenticationCoordinator.swift @@ -613,7 +613,8 @@ final class AuthenticationCoordinator: NSObject, AuthenticationCoordinatorProtoc /// Replace the contents of the navigation router with a loading animation. private func showLoadingAnimation() { - let loadingViewController = LaunchLoadingViewController() + let syncProgress: MXSessionSyncProgress? = MXSDKOptions.sharedInstance().enableSyncProgress ? session?.syncProgress : nil + let loadingViewController = LaunchLoadingViewController(syncProgress: syncProgress) loadingViewController.modalPresentationStyle = .fullScreen // Replace the navigation stack with the loading animation diff --git a/Riot/Modules/Authentication/Legacy/LegacyAuthenticationCoordinator.swift b/Riot/Modules/Authentication/Legacy/LegacyAuthenticationCoordinator.swift index e8ca770ab..583419075 100644 --- a/Riot/Modules/Authentication/Legacy/LegacyAuthenticationCoordinator.swift +++ b/Riot/Modules/Authentication/Legacy/LegacyAuthenticationCoordinator.swift @@ -106,7 +106,8 @@ final class LegacyAuthenticationCoordinator: NSObject, AuthenticationCoordinator // MARK: - Private private func showLoadingAnimation() { - let loadingViewController = LaunchLoadingViewController() + let syncProgress: MXSessionSyncProgress? = MXSDKOptions.sharedInstance().enableSyncProgress ? session?.syncProgress : nil + let loadingViewController = LaunchLoadingViewController(syncProgress: syncProgress) loadingViewController.modalPresentationStyle = .fullScreen // Replace the navigation stack with the loading animation diff --git a/Riot/Modules/LaunchLoading/LaunchLoadingView.swift b/Riot/Modules/LaunchLoading/LaunchLoadingView.swift index f2843db62..55f3aff05 100644 --- a/Riot/Modules/LaunchLoading/LaunchLoadingView.swift +++ b/Riot/Modules/LaunchLoading/LaunchLoadingView.swift @@ -30,12 +30,20 @@ final class LaunchLoadingView: UIView, NibLoadable, Themable { // MARK: - Properties @IBOutlet private weak var animationView: ElementView! + @IBOutlet private weak var statusLabel: UILabel! + private var animationTimeline: Timeline_1! + private let numberFormatter: NumberFormatter = { + let formatter = NumberFormatter() + formatter.numberStyle = .ordinal + return formatter + }() // MARK: - Setup - static func instantiate() -> LaunchLoadingView { + static func instantiate(syncProgress: MXSessionSyncProgress?) -> LaunchLoadingView { let view = LaunchLoadingView.loadFromNib() + syncProgress?.delegate = view return view } @@ -45,6 +53,8 @@ final class LaunchLoadingView: UIView, NibLoadable, Themable { let animationTimeline = Timeline_1(view: self.animationView, duration: LaunchAnimation.duration, repeatCount: LaunchAnimation.repeatCount) animationTimeline.play() self.animationTimeline = animationTimeline + + self.statusLabel.isHidden = !MXSDKOptions.sharedInstance().enableSyncProgress } // MARK: - Public @@ -54,3 +64,31 @@ final class LaunchLoadingView: UIView, NibLoadable, Themable { self.animationView.backgroundColor = theme.backgroundColor } } + +extension LaunchLoadingView: MXSessionSyncProgressDelegate { + func sessionDidUpdateSyncState(_ state: MXSessionSyncState) { + guard MXSDKOptions.sharedInstance().enableSyncProgress else { + return + } + + // Sync may be doing a lot of heavy work on the main thread and the status text + // does not update reliably enough without explicitly refreshing + CATransaction.begin() + statusLabel.text = statusText(for: state) + CATransaction.commit() + } + + private func statusText(for state: MXSessionSyncState) -> String { + switch state { + case .serverSyncing(let attempts): + if attempts > 1, let nth = numberFormatter.string(from: NSNumber(value: attempts)) { + return VectorL10n.launchLoadingServerSyncingNthAttempt(nth) + } else { + return VectorL10n.launchLoadingServerSyncing + } + case .processingResponse(let progress): + let percent = Int(floor(progress * 100)) + return VectorL10n.launchLoadingProcessingResponse("\(percent)") + } + } +} diff --git a/Riot/Modules/LaunchLoading/LaunchLoadingView.xib b/Riot/Modules/LaunchLoading/LaunchLoadingView.xib index c933d1e00..81a6b64f9 100644 --- a/Riot/Modules/LaunchLoading/LaunchLoadingView.xib +++ b/Riot/Modules/LaunchLoading/LaunchLoadingView.xib @@ -1,32 +1,51 @@ - + - + + - + - + - + + - + + + + + + + + + + + + + diff --git a/Riot/Modules/LaunchLoading/LaunchLoadingViewController.swift b/Riot/Modules/LaunchLoading/LaunchLoadingViewController.swift index bd7dba409..1da229b79 100644 --- a/Riot/Modules/LaunchLoading/LaunchLoadingViewController.swift +++ b/Riot/Modules/LaunchLoading/LaunchLoadingViewController.swift @@ -21,10 +21,10 @@ class LaunchLoadingViewController: UIViewController, Reusable { required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - init() { + init(syncProgress: MXSessionSyncProgress?) { super.init(nibName: "LaunchLoadingViewController", bundle: nil) - let launchLoadingView = LaunchLoadingView.instantiate() + let launchLoadingView = LaunchLoadingView.instantiate(syncProgress: syncProgress) launchLoadingView.update(theme: ThemeService.shared().theme) view.vc_addSubViewMatchingParent(launchLoadingView) diff --git a/changelog.d/pr-7101.change b/changelog.d/pr-7101.change new file mode 100644 index 000000000..fe61afb84 --- /dev/null +++ b/changelog.d/pr-7101.change @@ -0,0 +1,2 @@ +Loading: Display sync progress on the loading screen + From 1b4039ccba27a93c564a07eef12d3cb38458472e Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Wed, 23 Nov 2022 14:05:19 +0200 Subject: [PATCH 09/19] Allow alpha builds to run, if tagged accordingly, when pushing new commits --- .github/workflows/release-alpha.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/release-alpha.yml b/.github/workflows/release-alpha.yml index 2c932c52f..f8d03f08b 100644 --- a/.github/workflows/release-alpha.yml +++ b/.github/workflows/release-alpha.yml @@ -4,7 +4,6 @@ on: # Triggers the workflow on any pull request pull_request: - types: [ labeled, synchronized, opened, reopened ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: From 9dc4b8df8210a6a1d3ff89971ea576678356eda2 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Wed, 23 Nov 2022 13:35:05 +0100 Subject: [PATCH 10/19] suggest PR changes --- .../WysiwygInputToolbarView.swift | 17 +++++++++++------ .../Room/Composer/MockComposerScreenState.swift | 6 +++--- .../Room/Composer/Model/ComposerViewState.swift | 5 +++++ .../Modules/Room/Composer/View/Composer.swift | 15 +++------------ .../Composer/ViewModel/ComposerViewModel.swift | 9 +++++++++ .../ViewModel/ComposerViewModelProtocol.swift | 1 + 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift index 1ab81876a..cee4a9c11 100644 --- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift +++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift @@ -43,9 +43,12 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp private var voiceMessageBottomConstraint: NSLayoutConstraint? private var hostingViewController: VectorHostingController! private var wysiwygViewModel = WysiwygComposerViewModel(textColor: ThemeService.shared().theme.colors.primaryContent) - private var viewModel: ComposerViewModelProtocol = ComposerViewModel( - initialViewState: ComposerViewState(textFormattingEnabled: RiotSettings.shared.enableWysiwygTextFormatting, - bindings: ComposerBindings(focused: false))) + private var viewModel: ComposerViewModelProtocol! + + private var isLandscapePhone: Bool { + let device = UIDevice.current + return device.isPhone && device.orientation.isLandscape + } // MARK: Public @@ -99,6 +102,9 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp override func awakeFromNib() { super.awakeFromNib() + viewModel = ComposerViewModel( + initialViewState: ComposerViewState(textFormattingEnabled: RiotSettings.shared.enableWysiwygTextFormatting, + isLandscapePhone: isLandscapePhone, bindings: ComposerBindings(focused: false))) viewModel.callback = { [weak self] result in self?.handleViewModelResult(result) @@ -153,6 +159,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp }, wysiwygViewModel.$maximised + .dropFirst() .removeDuplicates() .sink { [weak self] value in guard let self = self else { return } @@ -229,6 +236,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp } @objc private func deviceDidRotate(_ notification: Notification) { + viewModel.isLandscapePhone = isLandscapePhone DispatchQueue.main.async { self.updateTextViewHeight() } @@ -342,9 +350,6 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp set { self.viewModel.textFormattingEnabled = newValue self.wysiwygViewModel.plainTextMode = !newValue - if !newValue { - self.wysiwygViewModel.maximised = false - } } } diff --git a/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift b/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift index c0602ab03..35a628d02 100644 --- a/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift +++ b/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift @@ -32,9 +32,9 @@ enum MockComposerScreenState: MockScreenState, CaseIterable { let bindings = ComposerBindings(focused: false) switch self { - case .send: viewModel = ComposerViewModel(initialViewState: ComposerViewState(textFormattingEnabled: true, bindings: bindings)) - case .edit: viewModel = ComposerViewModel(initialViewState: ComposerViewState(sendMode: .edit, textFormattingEnabled: true, bindings: bindings)) - case .reply: viewModel = ComposerViewModel(initialViewState: ComposerViewState(eventSenderDisplayName: "TestUser", sendMode: .reply, textFormattingEnabled: true, bindings: bindings)) + case .send: viewModel = ComposerViewModel(initialViewState: ComposerViewState(textFormattingEnabled: true, isLandscapePhone: false, bindings: bindings)) + case .edit: viewModel = ComposerViewModel(initialViewState: ComposerViewState(sendMode: .edit, textFormattingEnabled: true, isLandscapePhone: false, bindings: bindings)) + case .reply: viewModel = ComposerViewModel(initialViewState: ComposerViewState(eventSenderDisplayName: "TestUser", sendMode: .reply, textFormattingEnabled: true, isLandscapePhone: false, bindings: bindings)) } let wysiwygviewModel = WysiwygComposerViewModel(minHeight: 20, maxCompressedHeight: 360) diff --git a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift index c4293eafc..5b4fa0465 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift @@ -20,6 +20,7 @@ struct ComposerViewState: BindableState { var eventSenderDisplayName: String? var sendMode: ComposerSendMode = .send var textFormattingEnabled: Bool + var isLandscapePhone: Bool var placeholder: String? var bindings: ComposerBindings @@ -47,6 +48,10 @@ extension ComposerViewState { default: return nil } } + + var isForcedMinimised: Bool { + isLandscapePhone || !textFormattingEnabled + } } struct ComposerBindings { diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index 48e8aa030..49125ce6a 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -31,12 +31,6 @@ struct Composer: View { @Environment(\.theme) private var theme: ThemeSwiftUI @State private var isActionButtonShowing = false - @State private var isToggleButtonHidden = false - - private var isLandscapeIphone: Bool { - let device = UIDevice.current - return device.userInterfaceIdiom == .phone && device.orientation.isLandscape - } private let horizontalPadding: CGFloat = 12 private let borderHeight: CGFloat = 40 @@ -122,7 +116,7 @@ struct Composer: View { wysiwygViewModel.setup() } } - if viewModel.viewState.textFormattingEnabled { + if !viewModel.viewState.isForcedMinimised { Button { wysiwygViewModel.maximised.toggle() } label: { @@ -132,7 +126,6 @@ struct Composer: View { .frame(width: 16, height: 16) } .accessibilityIdentifier(toggleButtonAcccessibilityIdentifier) - .isHidden(isToggleButtonHidden) .padding(.leading, 12) .padding(.trailing, 4) } @@ -204,7 +197,6 @@ struct Composer: View { self.resizeAnimationDuration = resizeAnimationDuration self.sendMessageAction = sendMessageAction self.showSendMediaActions = showSendMediaActions - self._isToggleButtonHidden = State(initialValue: isLandscapeIphone) } var body: some View { @@ -238,11 +230,10 @@ struct Composer: View { } .padding(.horizontal, horizontalPadding) .padding(.bottom, 4) - .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in - if wysiwygViewModel.maximised, isLandscapeIphone { + .onChange(of: viewModel.viewState.isForcedMinimised) { newValue in + if wysiwygViewModel.maximised && newValue { wysiwygViewModel.maximised = false } - isToggleButtonHidden = isLandscapeIphone } } } diff --git a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift index 21c09c89c..4e6442303 100644 --- a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift +++ b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift @@ -63,6 +63,15 @@ final class ComposerViewModel: ComposerViewModelType, ComposerViewModelProtocol } } + var isLandscapePhone: Bool { + get { + state.isLandscapePhone + } + set { + state.isLandscapePhone = newValue + } + } + var isFocused: Bool { state.bindings.focused } diff --git a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift index 3a7f79af9..0d23be3cc 100644 --- a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift +++ b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModelProtocol.swift @@ -24,6 +24,7 @@ protocol ComposerViewModelProtocol { var eventSenderDisplayName: String? { get set } var placeholder: String? { get set } var isFocused: Bool { get } + var isLandscapePhone: Bool { get set } func dismissKeyboard() func showKeyboard() From 23ee663dba9700c7aa0cef3df16b2dfe82f2cbd6 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Wed, 23 Nov 2022 13:40:15 +0100 Subject: [PATCH 11/19] better naming --- .../Modules/Room/Composer/Model/ComposerViewState.swift | 2 +- RiotSwiftUI/Modules/Room/Composer/View/Composer.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift index 5b4fa0465..66724cdce 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerViewState.swift @@ -49,7 +49,7 @@ extension ComposerViewState { } } - var isForcedMinimised: Bool { + var isMinimiseForced: Bool { isLandscapePhone || !textFormattingEnabled } } diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index 49125ce6a..87c7cbe7a 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -116,7 +116,7 @@ struct Composer: View { wysiwygViewModel.setup() } } - if !viewModel.viewState.isForcedMinimised { + if !viewModel.viewState.isMinimiseForced { Button { wysiwygViewModel.maximised.toggle() } label: { @@ -230,7 +230,7 @@ struct Composer: View { } .padding(.horizontal, horizontalPadding) .padding(.bottom, 4) - .onChange(of: viewModel.viewState.isForcedMinimised) { newValue in + .onChange(of: viewModel.viewState.isMinimiseForced) { newValue in if wysiwygViewModel.maximised && newValue { wysiwygViewModel.maximised = false } From 61073ed257a55cdd416ae60f84a3385a8b63ccc8 Mon Sep 17 00:00:00 2001 From: Philippe Loriaux Date: Wed, 23 Nov 2022 13:46:30 +0100 Subject: [PATCH 12/19] Update on VoiceBroadcast currentRecordingState creation --- .../View/VoiceBroadcastRecorderView.swift | 14 ++++++-------- .../VoiceBroadcastRecorderModels.swift | 2 +- .../VoiceBroadcastRecorderScreenState.swift | 2 +- .../VoiceBroadcastRecorderViewModel.swift | 12 ++++++++---- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift index 8a5de33aa..6c2c21e3c 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/View/VoiceBroadcastRecorderView.swift @@ -54,14 +54,12 @@ struct VoiceBroadcastRecorderView: View { Image(uiImage: Asset.Images.voiceBroadcastTileLive.image) } - if let remainingTimeLabel = viewModel.viewState.currentRecordingState.remainingTimeLabel { - Label { - Text(remainingTimeLabel) - .foregroundColor(theme.colors.secondaryContent) - .font(theme.fonts.caption1) - } icon: { - Image(uiImage: Asset.Images.voiceBroadcastTimeLeft.image) - } + Label { + Text(viewModel.viewState.currentRecordingState.remainingTimeLabel) + .foregroundColor(theme.colors.secondaryContent) + .font(theme.fonts.caption1) + } icon: { + Image(uiImage: Asset.Images.voiceBroadcastTimeLeft.image) } }.frame(maxWidth: .infinity, alignment: .leading) diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift index ebc1f39c3..cb807a430 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderModels.swift @@ -37,7 +37,7 @@ struct VoiceBroadcastRecorderDetails { struct VoiceBroadcastRecordingState { var remainingTime: UInt - var remainingTimeLabel: String? + var remainingTimeLabel: String } struct VoiceBroadcastRecorderViewState: BindableState { diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift index 8962da13d..c2b57dc5c 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderScreenState.swift @@ -32,7 +32,7 @@ enum MockVoiceBroadcastRecorderScreenState: MockScreenState, CaseIterable { var screenView: ([Any], AnyView) { let details = VoiceBroadcastRecorderDetails(senderDisplayName: "", avatarData: AvatarInput(mxContentUri: "", matrixItemId: "!fakeroomid:matrix.org", displayName: "The name of the room")) - let recordingState = VoiceBroadcastRecordingState(remainingTime: BuildSettings.voiceBroadcastMaxLength) + let recordingState = VoiceBroadcastRecordingState(remainingTime: BuildSettings.voiceBroadcastMaxLength, remainingTimeLabel: "1h 20m 47s left") let viewModel = MockVoiceBroadcastRecorderViewModel(initialViewState: VoiceBroadcastRecorderViewState(details: details, recordingState: .started, currentRecordingState: recordingState, bindings: VoiceBroadcastRecorderViewStateBindings())) return ( diff --git a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderViewModel.swift b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderViewModel.swift index 27bd2f6e8..ba9690bfb 100644 --- a/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderViewModel.swift +++ b/RiotSwiftUI/Modules/Room/VoiceBroadcastRecorder/VoiceBroadcastRecorderViewModel.swift @@ -34,7 +34,7 @@ class VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderViewModelType, Voic init(details: VoiceBroadcastRecorderDetails, recorderService: VoiceBroadcastRecorderServiceProtocol) { self.voiceBroadcastRecorderService = recorderService - let currentRecordingState = VoiceBroadcastRecordingState(remainingTime: BuildSettings.voiceBroadcastMaxLength) + let currentRecordingState = VoiceBroadcastRecorderViewModel.currentRecordingState(from: BuildSettings.voiceBroadcastMaxLength) super.init(initialViewState: VoiceBroadcastRecorderViewState(details: details, recordingState: .stopped, currentRecordingState: currentRecordingState, @@ -81,12 +81,16 @@ class VoiceBroadcastRecorderViewModel: VoiceBroadcastRecorderViewModelType, Voic } private func updateRemainingTime(_ remainingTime: UInt) { + state.currentRecordingState = VoiceBroadcastRecorderViewModel.currentRecordingState(from: remainingTime) + } + + private static func currentRecordingState(from remainingTime: UInt) -> VoiceBroadcastRecordingState { let time = TimeInterval(Double(remainingTime)) let formatter = DateComponentsFormatter() formatter.unitsStyle = .abbreviated - - state.currentRecordingState.remainingTime = remainingTime - state.currentRecordingState.remainingTimeLabel = VectorL10n.voiceBroadcastTimeLeft(formatter.string(from: time) ?? "0s") + + return VoiceBroadcastRecordingState(remainingTime: remainingTime, + remainingTimeLabel: VectorL10n.voiceBroadcastTimeLeft(formatter.string(from: time) ?? "0s")) } } From 91ba86db338b155eb4d35d39a95707e88e19d422 Mon Sep 17 00:00:00 2001 From: Mauro Romito Date: Wed, 23 Nov 2022 13:54:15 +0100 Subject: [PATCH 13/19] fixing tests --- .../Room/Composer/Test/Unit/ComposerViewModelTests.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/RiotSwiftUI/Modules/Room/Composer/Test/Unit/ComposerViewModelTests.swift b/RiotSwiftUI/Modules/Room/Composer/Test/Unit/ComposerViewModelTests.swift index 073c6f357..a49314062 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Test/Unit/ComposerViewModelTests.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Test/Unit/ComposerViewModelTests.swift @@ -23,8 +23,13 @@ final class ComposerViewModelTests: XCTestCase { var context: ComposerViewModel.Context! override func setUpWithError() throws { - viewModel = ComposerViewModel(initialViewState: ComposerViewState(textFormattingEnabled: true, - bindings: ComposerBindings(focused: false))) + viewModel = ComposerViewModel( + initialViewState: ComposerViewState( + textFormattingEnabled: true, + isLandscapePhone: false, + bindings: ComposerBindings(focused: false) + ) + ) context = viewModel.context } From c5ac0cec3cd33ffaa8c2ff35798a40584e68797c Mon Sep 17 00:00:00 2001 From: Philippe Loriaux Date: Wed, 23 Nov 2022 14:48:54 +0100 Subject: [PATCH 14/19] Fix scroll issues with VoiceBroadcastPlayback cells (by fixing SizableBaseRoomCell) --- .../SizableCell/SizableBaseRoomCell.swift | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift b/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift index f33762144..b8ba675b2 100644 --- a/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift +++ b/Riot/Modules/Room/TimelineCells/SizableCell/SizableBaseRoomCell.swift @@ -65,6 +65,12 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType { return self.height(for: roomBubbleCellData, fitting: maxWidth) } + + override func prepareForReuse() { + cleanContentVC() + + super.prepareForReuse() + } // MARK - SizableBaseRoomCellType @@ -173,10 +179,21 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType { } return height - } - + } + + private func cleanContentVC() { + contentVC?.removeFromParent() + contentVC?.view.removeFromSuperview() + contentVC?.didMove(toParent: nil) + contentVC = nil + } + + // MARK: - Public + func addContentViewController(_ controller: UIViewController, on contentView: UIView) { controller.view.invalidateIntrinsicContentSize() + + cleanContentVC() let parent = vc_parentViewController parent?.addChild(controller) @@ -185,13 +202,4 @@ class SizableBaseRoomCell: BaseRoomCell, SizableBaseRoomCellType { contentVC = controller } - - override func prepareForReuse() { - contentVC?.removeFromParent() - contentVC?.view.removeFromSuperview() - contentVC?.didMove(toParent: nil) - contentVC = nil - - super.prepareForReuse() - } } From 802cad1cfffc186e72652cd5f3ee5e7529aa45b8 Mon Sep 17 00:00:00 2001 From: Alfonso Grillo Date: Wed, 23 Nov 2022 16:00:59 +0100 Subject: [PATCH 15/19] Refactor InfoSheet presentation --- .../Coordinator/InfoSheetCoordinator.swift | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/RiotSwiftUI/Modules/Common/InfoSheet/Coordinator/InfoSheetCoordinator.swift b/RiotSwiftUI/Modules/Common/InfoSheet/Coordinator/InfoSheetCoordinator.swift index 6fb7fc110..54a9719b2 100644 --- a/RiotSwiftUI/Modules/Common/InfoSheet/Coordinator/InfoSheetCoordinator.swift +++ b/RiotSwiftUI/Modules/Common/InfoSheet/Coordinator/InfoSheetCoordinator.swift @@ -64,28 +64,22 @@ private extension InfoSheetCoordinator { // The bottom sheet should be presented with the content intrinsic height as for design requirement // We can do it easily just on iOS 16+ func setupPresentation(of viewController: VectorHostingController) { - let cornerRadius: CGFloat = 24 + let detents: [VectorHostingBottomSheetPreferences.Detent] - guard + if #available(iOS 16, *), - let parentSize = parameters.parentSize, - let presentationController = viewController.sheetPresentationController - else { - viewController.bottomSheetPreferences = .init(cornerRadius: cornerRadius) - return + let parentSize = parameters.parentSize { + + let intrisincSize = viewController.view.systemLayoutSizeFitting(.init(width: parentSize.width, height: UIView.layoutFittingCompressedSize.height), + withHorizontalFittingPriority: .defaultHigh, + verticalFittingPriority: .defaultLow) + + detents = [.custom(height: intrisincSize.height), .large] + } else { + detents = [.medium, .large] } - let intrisincSize = viewController.view.systemLayoutSizeFitting(.init(width: parentSize.width, height: 0), - withHorizontalFittingPriority: .defaultHigh, - verticalFittingPriority: .defaultLow) - - presentationController.preferredCornerRadius = cornerRadius - presentationController.prefersGrabberVisible = true - presentationController.detents = [ - .custom { context in - min(context.maximumDetentValue, intrisincSize.height) - }, - .large() - ] + viewController.bottomSheetPreferences = .init(detents: detents, cornerRadius: 24) + viewController.bottomSheetPreferences?.setup(viewController: viewController) } } From af22597850565be7c0df5ebd3e9b8af88809ad9f Mon Sep 17 00:00:00 2001 From: Alfonso Grillo Date: Wed, 23 Nov 2022 16:07:55 +0100 Subject: [PATCH 16/19] Add changelog.d file --- changelog.d/pr-7107.change | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr-7107.change diff --git a/changelog.d/pr-7107.change b/changelog.d/pr-7107.change new file mode 100644 index 000000000..19600b10b --- /dev/null +++ b/changelog.d/pr-7107.change @@ -0,0 +1 @@ +Refactor bottom sheet presentation in the device manager. From e1a0db2444d538fc08e8f0d2ad8523f91aac4988 Mon Sep 17 00:00:00 2001 From: Philippe Loriaux Date: Wed, 23 Nov 2022 17:41:54 +0100 Subject: [PATCH 17/19] Add Towncrier file --- changelog.d/pr-7105.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr-7105.bugfix diff --git a/changelog.d/pr-7105.bugfix b/changelog.d/pr-7105.bugfix new file mode 100644 index 000000000..c1125fcaa --- /dev/null +++ b/changelog.d/pr-7105.bugfix @@ -0,0 +1 @@ +Fix scroll issues with VoiceBroadcast and Poll cells From 78f1475cfd2019cee286deb9a381df2090d3d63a Mon Sep 17 00:00:00 2001 From: Philippe Loriaux Date: Wed, 23 Nov 2022 17:43:46 +0100 Subject: [PATCH 18/19] Add Towncrier file --- changelog.d/pr-7103.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/pr-7103.feature diff --git a/changelog.d/pr-7103.feature b/changelog.d/pr-7103.feature new file mode 100644 index 000000000..4ce05d7cb --- /dev/null +++ b/changelog.d/pr-7103.feature @@ -0,0 +1 @@ +Add the left time in the Voice Broadcast tile recorder. From 24782e2484a575c6a7f992ee60d7d79e71bf849a Mon Sep 17 00:00:00 2001 From: Philippe Loriaux Date: Thu, 24 Nov 2022 09:26:13 +0100 Subject: [PATCH 19/19] Update Voice Broadcast Time left asset --- .../voice_broadcast_time_left.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/voice_broadcast_time_left.svg b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/voice_broadcast_time_left.svg index eece236c8..82b9eb425 100644 --- a/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/voice_broadcast_time_left.svg +++ b/Riot/Assets/Images.xcassets/VoiceBroadcast/voice_broadcast_time_left.imageset/voice_broadcast_time_left.svg @@ -1,3 +1,3 @@ - - + +