Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show room encryption state in the composer #3841

Merged
merged 3 commits into from
Feb 27, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@
"rich_text_editor_bullet_list" = "Toggle bullet list";
"rich_text_editor_close_formatting_options" = "Close formatting options";
"rich_text_editor_code_block" = "Toggle code block";
"rich_text_editor_composer_encrypted_placeholder" = "Encrypted message…";
"rich_text_editor_composer_placeholder" = "Message…";
"rich_text_editor_create_link" = "Create a link";
"rich_text_editor_edit_link" = "Edit link";
Expand Down Expand Up @@ -479,9 +480,15 @@
"screen_security_and_privacy_room_publishing_section_header" = "Room publishing";
"screen_security_and_privacy_room_visibility_section_footer" = "Room addresses are ways to find and access rooms. This also ensures you can easily share your room with others.\nThe address is also required to make the room visible in %1$@ public room directory.";
"screen_security_and_privacy_title" = "Security & privacy";
"screen_start_chat_join_room_by_address_invalid_address" = "Not a valid address";
"screen_start_chat_join_room_by_address_placeholder" = "Enter...";
"screen_start_chat_join_room_by_address_room_found" = "Matching room found";
"screen_start_chat_join_room_by_address_room_not_found" = "Room not found";
"screen_start_chat_join_room_by_address_supporting_text" = "e.g. #room-name:matrix.org";
"screen_timeline_item_menu_send_failure_changed_identity" = "Message not sent because %1$@’s verified identity has changed.";
"screen_timeline_item_menu_send_failure_unsigned_device" = "Message not sent because %1$@ has not verified all devices.";
"screen_timeline_item_menu_send_failure_you_unsigned_device" = "Message not sent because you have not verified one or more of your devices.";
"screen.start_chat.join_room_by_address_action" = "Join room by address";
"screen_account_provider_form_hint" = "Homeserver address";
"screen_account_provider_form_notice" = "Enter a search term or a domain address.";
"screen_account_provider_form_subtitle" = "Search for a company, community, or private server.";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@
"rich_text_editor_bullet_list" = "Toggle bullet list";
"rich_text_editor_close_formatting_options" = "Close formatting options";
"rich_text_editor_code_block" = "Toggle code block";
"rich_text_editor_composer_encrypted_placeholder" = "Encrypted message…";
"rich_text_editor_composer_placeholder" = "Message…";
"rich_text_editor_create_link" = "Create a link";
"rich_text_editor_edit_link" = "Edit link";
Expand Down Expand Up @@ -479,9 +480,15 @@
"screen_security_and_privacy_room_publishing_section_header" = "Room publishing";
"screen_security_and_privacy_room_visibility_section_footer" = "Room addresses are ways to find and access rooms. This also ensures you can easily share your room with others.\nThe address is also required to make the room visible in %1$@ public room directory.";
"screen_security_and_privacy_title" = "Security & privacy";
"screen_start_chat_join_room_by_address_invalid_address" = "Not a valid address";
"screen_start_chat_join_room_by_address_placeholder" = "Enter...";
"screen_start_chat_join_room_by_address_room_found" = "Matching room found";
"screen_start_chat_join_room_by_address_room_not_found" = "Room not found";
"screen_start_chat_join_room_by_address_supporting_text" = "e.g. #room-name:matrix.org";
"screen_timeline_item_menu_send_failure_changed_identity" = "Message not sent because %1$@’s verified identity has changed.";
"screen_timeline_item_menu_send_failure_unsigned_device" = "Message not sent because %1$@ has not verified all devices.";
"screen_timeline_item_menu_send_failure_you_unsigned_device" = "Message not sent because you have not verified one or more of your devices.";
"screen.start_chat.join_room_by_address_action" = "Join room by address";
"screen_account_provider_form_hint" = "Homeserver address";
"screen_account_provider_form_notice" = "Enter a search term or a domain address.";
"screen_account_provider_form_subtitle" = "Search for a company, community, or private server.";
Expand Down
19 changes: 19 additions & 0 deletions ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,8 @@ internal enum L10n {
internal static var richTextEditorCodeBlock: String { return L10n.tr("Localizable", "rich_text_editor_code_block") }
/// Add a caption
internal static var richTextEditorComposerCaptionPlaceholder: String { return L10n.tr("Localizable", "rich_text_editor_composer_caption_placeholder") }
/// Encrypted message…
internal static var richTextEditorComposerEncryptedPlaceholder: String { return L10n.tr("Localizable", "rich_text_editor_composer_encrypted_placeholder") }
/// Message…
internal static var richTextEditorComposerPlaceholder: String { return L10n.tr("Localizable", "rich_text_editor_composer_placeholder") }
/// Create a link
Expand Down Expand Up @@ -2458,6 +2460,16 @@ internal enum L10n {
internal static var screenSignoutSaveRecoveryKeyTitle: String { return L10n.tr("Localizable", "screen_signout_save_recovery_key_title") }
/// An error occurred when trying to start a chat
internal static var screenStartChatErrorStartingChat: String { return L10n.tr("Localizable", "screen_start_chat_error_starting_chat") }
/// Not a valid address
internal static var screenStartChatJoinRoomByAddressInvalidAddress: String { return L10n.tr("Localizable", "screen_start_chat_join_room_by_address_invalid_address") }
/// Enter...
internal static var screenStartChatJoinRoomByAddressPlaceholder: String { return L10n.tr("Localizable", "screen_start_chat_join_room_by_address_placeholder") }
/// Matching room found
internal static var screenStartChatJoinRoomByAddressRoomFound: String { return L10n.tr("Localizable", "screen_start_chat_join_room_by_address_room_found") }
/// Room not found
internal static var screenStartChatJoinRoomByAddressRoomNotFound: String { return L10n.tr("Localizable", "screen_start_chat_join_room_by_address_room_not_found") }
/// e.g. #room-name:matrix.org
internal static var screenStartChatJoinRoomByAddressSupportingText: String { return L10n.tr("Localizable", "screen_start_chat_join_room_by_address_supporting_text") }
/// Message not sent because %1$@’s verified identity has changed.
internal static func screenTimelineItemMenuSendFailureChangedIdentity(_ p1: Any) -> String {
return L10n.tr("Localizable", "screen_timeline_item_menu_send_failure_changed_identity", String(describing: p1))
Expand Down Expand Up @@ -2862,6 +2874,13 @@ internal enum L10n {
/// You
internal static var you: String { return L10n.tr("Localizable", "common.you") }
}

internal enum Screen {
internal enum StartChat {
/// Join room by address
internal static var joinRoomByAddressAction: String { return L10n.tr("Localizable", "screen.start_chat.join_room_by_address_action") }
}
}
}
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct MediaUploadPreviewScreenViewState: BindableState {
let url: URL
let title: String?
let shouldShowCaptionWarning: Bool
let isRoomEncrypted: Bool
var shouldDisableInteraction = false

var bindings = MediaUploadPreviewScreenBindings()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ class MediaUploadPreviewScreenViewModel: MediaUploadPreviewScreenViewModelType,
// Start processing the media whilst the user is reviewing it/adding a caption.
processingTask = Task { await mediaUploadingPreprocessor.processMedia(at: url) }

super.init(initialViewState: MediaUploadPreviewScreenViewState(url: url, title: title, shouldShowCaptionWarning: shouldShowCaptionWarning))
super.init(initialViewState: MediaUploadPreviewScreenViewState(url: url,
title: title,
shouldShowCaptionWarning: shouldShowCaptionWarning,
isRoomEncrypted: roomProxy.isEncrypted))
}

override func process(viewAction: MediaUploadPreviewScreenViewAction) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ struct MediaUploadPreviewScreen: View {
captionWarningButton
}
}
.messageComposerStyle()
.messageComposerStyle(isEncrypted: context.viewState.isRoomEncrypted)

SendButton {
context.send(viewAction: .send)
Expand Down Expand Up @@ -228,7 +228,7 @@ struct MediaUploadPreviewScreen_Previews: PreviewProvider, TestablePreview {
static let testURL = Bundle.main.url(forResource: "AppIcon60x60@2x", withExtension: "png")

static let viewModel = MediaUploadPreviewScreenViewModel(userIndicatorController: UserIndicatorControllerMock.default,
roomProxy: JoinedRoomProxyMock(),
roomProxy: JoinedRoomProxyMock(.init()),
mediaUploadingPreprocessor: MediaUploadingPreprocessor(appSettings: ServiceLocator.shared.settings),
title: "App Icon.png",
url: snapshotURL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ struct ComposerToolbarViewState: BindableState {
var audioPlayerState: AudioPlayerState
var audioRecorderState: AudioRecorderState

let isRoomEncrypted: Bool

var bindings: ComposerToolbarViewStateBindings

var isUploading: Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ final class ComposerToolbarViewModel: ComposerToolbarViewModelType, ComposerTool
title: L10n.commonVoiceMessage,
duration: 0),
audioRecorderState: .init(),
isRoomEncrypted: roomProxy.isEncrypted,
bindings: .init()),
mediaProvider: mediaProvider)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,11 @@ struct ComposerToolbar: View {
selectedRange: $context.selectedRange,
composerView: composerView,
mode: context.viewState.composerMode,
placeholder: placeholder,
composerFormattingEnabled: context.composerFormattingEnabled,
showResizeGrabber: context.composerFormattingEnabled,
isExpanded: $context.composerExpanded) {
isExpanded: $context.composerExpanded,
isEncrypted: context.viewState.isRoomEncrypted) {
sendMessage()
} editAction: {
context.send(viewAction: .editLastMessage)
Expand Down Expand Up @@ -222,8 +224,16 @@ struct ComposerToolbar: View {
private var placeholder: String {
switch context.viewState.composerMode {
case .reply(_, _, let isThread):
return isThread ? L10n.actionReplyInThread : L10n.richTextEditorComposerPlaceholder
return isThread ? L10n.actionReplyInThread : composerPlaceholder
default:
return composerPlaceholder
}
}

private var composerPlaceholder: String {
if context.viewState.isRoomEncrypted {
return L10n.richTextEditorComposerEncryptedPlaceholder
} else {
return L10n.richTextEditorComposerPlaceholder
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@ struct MessageComposer: View {
@Binding var plainComposerText: NSAttributedString
@Binding var presendCallback: (() -> Void)?
@Binding var selectedRange: NSRange

let composerView: WysiwygComposerView
let mode: ComposerMode
let placeholder: String

let composerFormattingEnabled: Bool
let showResizeGrabber: Bool
@Binding var isExpanded: Bool
let isEncrypted: Bool

let sendAction: () -> Void
let editAction: () -> Void
let pasteAction: PasteHandler
Expand All @@ -37,7 +42,7 @@ struct MessageComposer: View {
}

composerTextField
.messageComposerStyle(header: header)
.messageComposerStyle(header: header, isEncrypted: isEncrypted)
// Explicitly disable all animations to fix weirdness with the header immediately
// appearing whilst the text field and keyboard are still animating up to it.
.animation(.noAnimation, value: mode)
Expand All @@ -64,7 +69,7 @@ struct MessageComposer: View {
onAppearAction()
}
} else {
MessageComposerTextField(placeholder: L10n.richTextEditorComposerPlaceholder,
MessageComposerTextField(placeholder: placeholder,
text: $plainComposerText,
presendCallback: $presendCallback,
selectedRange: $selectedRange,
Expand Down Expand Up @@ -195,23 +200,29 @@ private struct MessageComposerHeaderLabelStyle: LabelStyle {
// MARK: - Style

extension View {
func messageComposerStyle(header: some View = EmptyView()) -> some View {
modifier(MessageComposerStyleModifier(header: header))
func messageComposerStyle(header: some View = EmptyView(), isEncrypted: Bool) -> some View {
modifier(MessageComposerStyleModifier(header: header, isEncrypted: isEncrypted))
}
}

private struct MessageComposerStyleModifier<Header: View>: ViewModifier {
private let composerShape = RoundedRectangle(cornerRadius: 21, style: .circular)

let header: Header
let isEncrypted: Bool

func body(content: Content) -> some View {
VStack(alignment: .leading, spacing: -6) {
header

content
.tint(.compound.iconAccentTertiary)
.padding(.vertical, 10)
HStack(alignment: .top, spacing: 6) {
icon
.scaledOffset(y: 2)

content
.tint(.compound.iconAccentTertiary)
}
.padding(.vertical, 10)
}
.padding(.horizontal, 12.0)
.clipShape(composerShape)
Expand All @@ -224,6 +235,17 @@ private struct MessageComposerStyleModifier<Header: View>: ViewModifier {
}
}
}

@ViewBuilder
private var icon: some View {
if isEncrypted {
CompoundIcon(\.lockSolid, size: .xSmall, relativeTo: .compound.bodyMD)
.foregroundStyle(.compound.iconSuccessPrimary)
} else {
CompoundIcon(\.lockOff, size: .xSmall, relativeTo: .compound.bodyMD)
.foregroundStyle(.compound.iconTertiary)
}
}
}

// MARK: - Previews
Expand Down Expand Up @@ -274,7 +296,8 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
]

static func messageComposer(_ content: NSAttributedString = .init(string: ""),
mode: ComposerMode = .default) -> MessageComposer {
mode: ComposerMode = .default,
placeholder: String = L10n.richTextEditorComposerEncryptedPlaceholder) -> MessageComposer {
let viewModel = WysiwygComposerViewModel(minHeight: 22,
maxExpandedHeight: 250)
viewModel.setMarkdownContent(content.string)
Expand All @@ -290,9 +313,11 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
selectedRange: .constant(NSRange(location: 0, length: 0)),
composerView: composerView,
mode: mode,
placeholder: placeholder,
composerFormattingEnabled: false,
showResizeGrabber: false,
isExpanded: .constant(false),
isEncrypted: false,
sendAction: { },
editAction: { },
pasteAction: { _ in },
Expand All @@ -307,14 +332,16 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
messageComposer(.init(string: "Some message"),
mode: .edit(originalEventOrTransactionID: .eventID(UUID().uuidString), type: .default))

let longMessage = "Short loin ground round tongue hamburger, fatback salami shoulder. Beef turkey sausage kielbasa strip steak. Alcatra capicola pig tail pancetta chislic."
messageComposer(.init(string: longMessage),
mode: .edit(originalEventOrTransactionID: .eventID(UUID().uuidString), type: .default))

messageComposer(mode: .reply(eventID: UUID().uuidString,
replyDetails: .loaded(sender: .init(id: "Kirk"),
eventID: "123",
eventContent: .message(.text(.init(body: "Text: Where the wild things are")))),
isThread: false))

Color.clear.frame(height: 20)

messageComposer(.init(string: "Some new caption"),
mode: .edit(originalEventOrTransactionID: .eventID(UUID().uuidString), type: .addCaption))
messageComposer(.init(string: "Some updated caption"),
Expand All @@ -338,7 +365,8 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
VStack(spacing: 8) {
ForEach(replyTypes, id: \.self) { replyDetails in
messageComposer(mode: .reply(eventID: UUID().uuidString,
replyDetails: replyDetails, isThread: true))
replyDetails: replyDetails, isThread: true),
placeholder: L10n.actionReplyInThread)
}
}
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading