diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Contents.json index ead86edbb..04b38da3e 100644 --- a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "action_voice_message.png", + "filename" : "Microphone icon.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "action_voice_message@2x.png", + "filename" : "Microphone icon@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "action_voice_message@3x.png", + "filename" : "Microphone icon@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Microphone icon.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Microphone icon.png new file mode 100644 index 000000000..8a6b3eb14 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Microphone icon.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Microphone icon@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Microphone icon@2x.png new file mode 100644 index 000000000..5b404b74c Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Microphone icon@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Microphone icon@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Microphone icon@3x.png new file mode 100644 index 000000000..520e22e94 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/Microphone icon@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message.png deleted file mode 100644 index b969cb3aa..000000000 Binary files a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@2x.png deleted file mode 100644 index 32c6236a6..000000000 Binary files a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@3x.png deleted file mode 100644 index e8cc54c29..000000000 Binary files a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_default.imageset/action_voice_message@3x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Contents.json b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Contents.json index 900874ca1..bc412b2cf 100644 --- a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Contents.json +++ b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Contents.json @@ -1,17 +1,17 @@ { "images" : [ { - "filename" : "voice_message_record_button_recording.png", + "filename" : "Microphone asset.png", "idiom" : "universal", "scale" : "1x" }, { - "filename" : "voice_message_record_button_recording@2x.png", + "filename" : "Microphone asset@2x.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "voice_message_record_button_recording@3x.png", + "filename" : "Microphone asset@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Microphone asset.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Microphone asset.png new file mode 100644 index 000000000..ffeb00aaf Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Microphone asset.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Microphone asset@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Microphone asset@2x.png new file mode 100644 index 000000000..8582e2d23 Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Microphone asset@2x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Microphone asset@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Microphone asset@3x.png new file mode 100644 index 000000000..e48d9a36b Binary files /dev/null and b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/Microphone asset@3x.png differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording.png deleted file mode 100644 index 5972e1272..000000000 Binary files a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@2x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@2x.png deleted file mode 100644 index 802268ba0..000000000 Binary files a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@2x.png and /dev/null differ diff --git a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@3x.png b/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@3x.png deleted file mode 100644 index b1def35e1..000000000 Binary files a/Riot/Assets/Images.xcassets/Room/VoiceMessages/voice_message_record_button_recording.imageset/voice_message_record_button_recording@3x.png and /dev/null differ diff --git a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift index 49f496dbd..95c5c4a02 100644 --- a/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift +++ b/Riot/Modules/Room/Views/WYSIWYGInputToolbar/WysiwygInputToolbarView.swift @@ -32,6 +32,7 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp // MARK: - Properties // MARK: Private + private var voiceMessageToolbarView: VoiceMessageToolbarView? private var cancellables = Set() private var heightConstraint: NSLayoutConstraint! private var hostingViewController: VectorHostingController! @@ -39,42 +40,6 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp private var viewModel: ComposerViewModelProtocol = ComposerViewModel(initialViewState: ComposerViewState()) // MARK: Public - var isEncryptionEnabled = false { - didSet { - updatePlaceholderText() - } - } - - /// The current html content of the composer - var htmlContent: String { - get { - wysiwygViewModel.content.html - } - set { - wysiwygViewModel.setHtmlContent(newValue) - } - } - - /// The display name to show when in edit/reply - var eventSenderDisplayName: String! { - get { - viewModel.eventSenderDisplayName - } - set { - viewModel.eventSenderDisplayName = newValue - } - } - - /// Whether the composer is in send, reply or edit mode. - var sendMode: RoomInputToolbarViewSendMode { - get { - viewModel.sendMode.legacySendMode - } - set { - viewModel.sendMode = ComposerSendMode(from: newValue) - updatePlaceholderText() - } - } override var placeholder: String! { get { @@ -169,9 +134,22 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp switch result { case .cancel: self.toolbarViewDelegate?.roomInputToolbarViewDidTapCancel(self) + case let .contentDidChange(isEmpty): + setVoiceMessageToolbarIsHidden(!isEmpty) } } + private func setVoiceMessageToolbarIsHidden(_ isHidden: Bool) { + guard let voiceMessageToolbarView = voiceMessageToolbarView else { return } + UIView.transition( + with: voiceMessageToolbarView, duration: 0.15, + options: .transitionCrossDissolve, + animations: { + voiceMessageToolbarView.isHidden = isHidden + } + ) + } + private func registerThemeServiceDidChangeThemeNotification() { NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) } @@ -185,12 +163,64 @@ class WysiwygInputToolbarView: MXKRoomInputToolbarView, NibLoadable, HtmlRoomInp wysiwygViewModel.textColor = theme.colors.primaryContent } - // MARK: - RoomInputToolbarViewProtocol + // MARK: - HtmlRoomInputToolbarViewProtocol + var isEncryptionEnabled = false { + didSet { + updatePlaceholderText() + } + } + + /// The current html content of the composer + var htmlContent: String { + get { + wysiwygViewModel.content.html + } + set { + wysiwygViewModel.setHtmlContent(newValue) + } + } + + /// The display name to show when in edit/reply + var eventSenderDisplayName: String! { + get { + viewModel.eventSenderDisplayName + } + set { + viewModel.eventSenderDisplayName = newValue + } + } + + /// Whether the composer is in send, reply or edit mode. + var sendMode: RoomInputToolbarViewSendMode { + get { + viewModel.sendMode.legacySendMode + } + set { + viewModel.sendMode = ComposerSendMode(from: newValue) + updatePlaceholderText() + } + } /// Add the voice message toolbar to the composer /// - Parameter voiceMessageToolbarView: the voice message toolbar UIView func setVoiceMessageToolbarView(_ voiceMessageToolbarView: UIView!) { - // TODO embed the voice messages UI + if let voiceMessageToolbarView = voiceMessageToolbarView as? VoiceMessageToolbarView { + self.voiceMessageToolbarView = voiceMessageToolbarView + voiceMessageToolbarView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.deactivate(voiceMessageToolbarView.containersTopConstraints) + addSubview(voiceMessageToolbarView) + NSLayoutConstraint.activate( + [ + hostingViewController.view.topAnchor.constraint(equalTo: voiceMessageToolbarView.topAnchor), + hostingViewController.view.leftAnchor.constraint(equalTo: voiceMessageToolbarView.leftAnchor), + hostingViewController.view.bottomAnchor.constraint(equalTo: voiceMessageToolbarView.bottomAnchor, constant: 4), + hostingViewController.view.rightAnchor.constraint(equalTo: voiceMessageToolbarView.rightAnchor) + ] + ) + } else { + self.voiceMessageToolbarView?.removeFromSuperview() + self.voiceMessageToolbarView = nil + } } func toolbarHeight() -> CGFloat { diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift index a5e634dfd..b78d1df76 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.swift @@ -88,6 +88,8 @@ class VoiceMessageToolbarView: PassthroughView, NibLoadable, Themable, UIGesture @IBOutlet private var toastNotificationContainerView: UIView! @IBOutlet private var toastNotificationLabel: UILabel! + @IBOutlet var containersTopConstraints: [NSLayoutConstraint]! + private var playbackView: VoiceMessagePlaybackView! private var cancelLabelToRecordButtonDistance: CGFloat = 0.0 diff --git a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.xib b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.xib index 52dcce870..a5cb9a5b8 100644 --- a/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.xib +++ b/Riot/Modules/Room/VoiceMessages/VoiceMessageToolbarView.xib @@ -1,16 +1,16 @@ - + - + - + @@ -19,7 +19,7 @@ - + @@ -71,7 +71,7 @@ - + @@ -267,12 +267,14 @@ + + - + diff --git a/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift b/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift index 4aa483785..48d7df054 100644 --- a/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift +++ b/RiotSwiftUI/Modules/Room/Composer/MockComposerScreenState.swift @@ -40,11 +40,13 @@ enum MockComposerScreenState: MockScreenState, CaseIterable { viewModel.callback = { [weak viewModel, weak wysiwygviewModel] result in guard let viewModel = viewModel else { return } - if viewModel.sendMode == .edit { - wysiwygviewModel?.setHtmlContent("") - } switch result { - case .cancel: viewModel.sendMode = .send + case .cancel: + if viewModel.sendMode == .edit { + wysiwygviewModel?.setHtmlContent("") + } + viewModel.sendMode = .send + default: break } } diff --git a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift index 00470aa53..badcd2b20 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Model/ComposerModels.swift @@ -127,12 +127,14 @@ enum ComposerSendMode: Equatable { case createDM } -enum ComposerViewAction { +enum ComposerViewAction: Equatable { case cancel + case contentDidChange(isEmpty: Bool) } -enum ComposerViewModelResult { +enum ComposerViewModelResult: Equatable { case cancel + case contentDidChange(isEmpty: Bool) } diff --git a/RiotSwiftUI/Modules/Room/Composer/Test/UI/ComposerUITests.swift b/RiotSwiftUI/Modules/Room/Composer/Test/UI/ComposerUITests.swift index 1a37c020b..08ce71fc0 100644 --- a/RiotSwiftUI/Modules/Room/Composer/Test/UI/ComposerUITests.swift +++ b/RiotSwiftUI/Modules/Room/Composer/Test/UI/ComposerUITests.swift @@ -25,11 +25,10 @@ final class ComposerUITests: MockScreenTestCase { let wysiwygTextView = app.textViews.allElementsBoundByIndex[0] XCTAssertTrue(wysiwygTextView.exists) let sendButton = app.buttons["sendButton"] - XCTAssertTrue(sendButton.exists) - XCTAssertFalse(sendButton.isEnabled) + XCTAssertFalse(sendButton.exists) wysiwygTextView.tap() wysiwygTextView.typeText("test") - XCTAssertTrue(sendButton.isEnabled) + XCTAssertTrue(sendButton.exists) XCTAssertFalse(app.buttons["editButton"].exists) } @@ -39,8 +38,7 @@ final class ComposerUITests: MockScreenTestCase { let wysiwygTextView = app.textViews.allElementsBoundByIndex[0] XCTAssertTrue(wysiwygTextView.exists) let sendButton = app.buttons["sendButton"] - XCTAssertTrue(sendButton.exists) - XCTAssertFalse(sendButton.isEnabled) + XCTAssertFalse(sendButton.exists) let cancelButton = app.buttons["cancelButton"] XCTAssertTrue(cancelButton.exists) @@ -51,7 +49,7 @@ final class ComposerUITests: MockScreenTestCase { wysiwygTextView.tap() wysiwygTextView.typeText("test") - XCTAssertTrue(sendButton.isEnabled) + XCTAssertTrue(sendButton.exists) XCTAssertFalse(app.buttons["editButton"].exists) cancelButton.tap() @@ -66,8 +64,7 @@ final class ComposerUITests: MockScreenTestCase { let wysiwygTextView = app.textViews.allElementsBoundByIndex[0] XCTAssertTrue(wysiwygTextView.exists) let editButton = app.buttons["editButton"] - XCTAssertTrue(editButton.exists) - XCTAssertFalse(editButton.isEnabled) + XCTAssertFalse(editButton.exists) let cancelButton = app.buttons["cancelButton"] XCTAssertTrue(cancelButton.exists) @@ -78,7 +75,7 @@ final class ComposerUITests: MockScreenTestCase { wysiwygTextView.tap() wysiwygTextView.typeText("test") - XCTAssertTrue(editButton.isEnabled) + XCTAssertTrue(editButton.exists) XCTAssertFalse(app.buttons["sendButton"].exists) cancelButton.tap() diff --git a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift index 0bcfd6ffd..be7577e12 100644 --- a/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift +++ b/RiotSwiftUI/Modules/Room/Composer/View/Composer.swift @@ -26,7 +26,7 @@ struct Composer: View { @Environment(\.theme) private var theme: ThemeSwiftUI @State private var focused = false - @State private var isActionButtonEnabled = false + @State private var isActionButtonShowing = false private let horizontalPadding: CGFloat = 12 private let borderHeight: CGFloat = 40 @@ -148,7 +148,7 @@ struct Composer: View { .resizable() .foregroundColor(theme.colors.tertiaryContent) .frame(width: 14, height: 14) - + } .frame(width: 36, height: 36) .background(Circle().fill(theme.colors.system)) @@ -159,16 +159,6 @@ struct Composer: View { } .frame(height: 44) Spacer() - // ZStack { - // TODO: Add support for voice messages - // Button { - // - // } label: { - // Image(Asset.Images.voiceMessageRecordButtonDefault.name) - // .foregroundColor(theme.colors.tertiaryContent) - // } - // .isHidden(showSendButton) - // .isHidden(true) Button { sendMessageAction(wysiwygViewModel.content) wysiwygViewModel.clearContent() @@ -181,18 +171,18 @@ struct Composer: View { } .frame(width: 36, height: 36) .padding(.leading, 8) - .disabled(!isActionButtonEnabled) - .opacity(isActionButtonEnabled ? 1 : 0.3) - .animation(.easeInOut(duration: 0.15), value: isActionButtonEnabled) + .isHidden(!isActionButtonShowing) .accessibilityIdentifier(actionButtonAccessibilityIdentifier) .accessibilityLabel(VectorL10n.send) - .onChange(of: wysiwygViewModel.isContentEmpty) { empty in - isActionButtonEnabled = !empty + .onChange(of: wysiwygViewModel.isContentEmpty) { isEmpty in + viewModel.send(viewAction: .contentDidChange(isEmpty: isEmpty)) + withAnimation(.easeInOut(duration: 0.15)) { + isActionButtonShowing = !isEmpty + } } } .padding(.horizontal, 12) .padding(.bottom, 4) - .animation(.none) } } } diff --git a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift index 65fd747b6..1e44ed049 100644 --- a/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift +++ b/RiotSwiftUI/Modules/Room/Composer/ViewModel/ComposerViewModel.swift @@ -60,6 +60,8 @@ final class ComposerViewModel: ComposerViewModelType, ComposerViewModelProtocol switch viewAction { case .cancel: callback?(.cancel) + case let .contentDidChange(isEmpty): + callback?(.contentDidChange(isEmpty: isEmpty)) } } } diff --git a/changelog.d/6941.feature b/changelog.d/6941.feature new file mode 100644 index 000000000..f9c3d32ea --- /dev/null +++ b/changelog.d/6941.feature @@ -0,0 +1 @@ +Added voice message support to the Rich Text Composer \ No newline at end of file