From 8052441bd709fa5024f5f04a383f8d01c18682be Mon Sep 17 00:00:00 2001 From: Doug <6060466+pixlwave@users.noreply.github.com> Date: Tue, 20 Jun 2023 18:31:40 +0100 Subject: [PATCH] App polish part 2 (#1118) - Fix sheet backgrounds in dark mode. - Fix background colours for reaction buttons. - Reply view layout. - Update separator colours. - Don't show sender name/avatar in DMs. - Fix inconsistent line heights with formatted body text. - Use plain text for reply - Increase tappable size of collapsed state changes button. - Blockquote layout and text colour. - Tap to expand the topic in room details. - Change the topic and security font size in room details. - Add cancel button when inviting someone to an existing room. - Reword Add Friends to Add People in start chat screen. - Update compound. --- DesignKit/Package.swift | 2 +- ElementX.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +-- .../en.lproj/Localizable.strings | 1 + ElementX/Sources/Generated/Strings.swift | 4 ++ .../UIFont+AttributedStringBuilder.m | 12 +++-- ElementX/Sources/Other/SwiftUI/Search.swift | 7 +-- .../View/AnalyticsPromptScreen.swift | 2 +- .../CreateRoom/View/CreateRoomScreen.swift | 2 +- .../HomeScreen/View/HomeScreenRoomCell.swift | 2 +- .../InviteUsersScreenCoordinator.swift | 9 ++-- .../InviteUsersScreenModels.swift | 2 + .../InviteUsersScreenViewModel.swift | 2 + .../View/InviteUsersScreen.swift | 25 ++++++---- .../View/InvitesScreenCell.swift | 2 +- .../View/RoomDetailsEditScreen.swift | 14 +++--- .../RoomDetailsScreenCoordinator.swift | 4 +- .../View/RoomDetailsScreen.swift | 11 +++-- .../Screens/RoomScreen/RoomScreenModels.swift | 1 + .../RoomScreen/RoomScreenViewModel.swift | 1 + .../View/Replies/TimelineReplyView.swift | 30 ++++++++---- .../View/RoomAttachmentPicker.swift | 2 +- .../Style/TimelineItemBubbledStylerView.swift | 19 ++++---- .../Supplementary/TimelineReactionsView.swift | 2 +- .../CollapsibleRoomTimelineView.swift | 47 ++++++++++--------- .../View/Timeline/FormattedBodyText.swift | 15 ++++-- .../RoomScreen/View/TimelineItemMenu.swift | 4 +- .../StartChatScreenCoordinator.swift | 2 + .../View/StartChatScreen.swift | 2 +- project.yml | 2 +- 30 files changed, 141 insertions(+), 95 deletions(-) diff --git a/DesignKit/Package.swift b/DesignKit/Package.swift index 5cad987fb..a25a5d5ae 100644 --- a/DesignKit/Package.swift +++ b/DesignKit/Package.swift @@ -12,7 +12,7 @@ let package = Package( .library(name: "DesignKit", targets: ["DesignKit"]) ], dependencies: [ - .package(url: "https://github.com/vector-im/compound-ios.git", revision: "d59c317362beba940baa43d6aacdd357e208048d"), + .package(url: "https://github.com/vector-im/compound-ios.git", revision: "e8b35fdd8c4008079dfce203e63bf7a05582d7b9"), .package(url: "https://github.com/vector-im/element-design-tokens.git", exact: "0.0.3"), .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", from: "0.1.4") ], diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 5d5274361..33d13a9a6 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -4980,7 +4980,7 @@ repositoryURL = "https://github.com/vector-im/compound-ios"; requirement = { kind = revision; - revision = d59c317362beba940baa43d6aacdd357e208048d; + revision = e8b35fdd8c4008079dfce203e63bf7a05582d7b9; }; }; 9A472EE0218FE7DCF5283429 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 390e5f933..319b25cee 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -11,9 +11,9 @@ { "identity" : "compound-ios", "kind" : "remoteSourceControl", - "location" : "https://github.com/vector-im/compound-ios", + "location" : "https://github.com/vector-im/compound-ios.git", "state" : { - "revision" : "d59c317362beba940baa43d6aacdd357e208048d" + "revision" : "e8b35fdd8c4008079dfce203e63bf7a05582d7b9" } }, { @@ -181,7 +181,7 @@ { "identity" : "swift-snapshot-testing", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-snapshot-testing.git", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", "state" : { "revision" : "cef5b3f6f11781dd4591bdd1dd0a3d22bd609334", "version" : "1.11.0" diff --git a/ElementX/Resources/Localizations/en.lproj/Localizable.strings b/ElementX/Resources/Localizations/en.lproj/Localizable.strings index 3b19ec95a..4ae595e2d 100644 --- a/ElementX/Resources/Localizations/en.lproj/Localizable.strings +++ b/ElementX/Resources/Localizations/en.lproj/Localizable.strings @@ -27,6 +27,7 @@ "action_invite" = "Invite"; "action_invite_friends" = "Invite friends"; "action_invite_friends_to_app" = "Invite friends to %1$@"; +"action_invite_people_to_app" = "Invite people to %1$@"; "action_invites_list" = "Invites"; "action_learn_more" = "Learn more"; "action_leave" = "Leave"; diff --git a/ElementX/Sources/Generated/Strings.swift b/ElementX/Sources/Generated/Strings.swift index dce40302d..66b11949e 100644 --- a/ElementX/Sources/Generated/Strings.swift +++ b/ElementX/Sources/Generated/Strings.swift @@ -68,6 +68,10 @@ public enum L10n { public static func actionInviteFriendsToApp(_ p1: Any) -> String { return L10n.tr("Localizable", "action_invite_friends_to_app", String(describing: p1)) } + /// Invite people to %1$@ + public static func actionInvitePeopleToApp(_ p1: Any) -> String { + return L10n.tr("Localizable", "action_invite_people_to_app", String(describing: p1)) + } /// Invites public static var actionInvitesList: String { return L10n.tr("Localizable", "action_invites_list") } /// Learn more diff --git a/ElementX/Sources/Other/HTMLParsing/UIFont+AttributedStringBuilder.m b/ElementX/Sources/Other/HTMLParsing/UIFont+AttributedStringBuilder.m index cdf69570f..9c24400ac 100644 --- a/ElementX/Sources/Other/HTMLParsing/UIFont+AttributedStringBuilder.m +++ b/ElementX/Sources/Other/HTMLParsing/UIFont+AttributedStringBuilder.m @@ -38,15 +38,17 @@ // On iOS 13+ "TimesNewRomanPSMT" will be used instead of "SFUI" // In case of "Times New Roman" fallback, use system font and reuse UIFontDescriptorSymbolicTraits. - if ([font.familyName.lowercaseString containsString:@"times"]) - { + if ([font.familyName.lowercaseString containsString:@"times"]) { UIFontDescriptorSymbolicTraits symbolicTraits = (UIFontDescriptorSymbolicTraits)CTFontGetSymbolicTraits(ctFont); - UIFontDescriptor *systemFontDescriptor = [UIFont systemFontOfSize:fontSize].fontDescriptor; + // Start with the body text style and update it to keep consistent line spacing with plain text messages. + UIFontDescriptor *fontDescriptor = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleBody]; + fontDescriptor = [fontDescriptor fontDescriptorWithSize:fontSize]; + fontDescriptor = [fontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits]; - UIFontDescriptor *finalFontDescriptor = [systemFontDescriptor fontDescriptorWithSymbolicTraits:symbolicTraits]; - font = [UIFont fontWithDescriptor:finalFontDescriptor size:fontSize]; + font = [UIFont fontWithDescriptor:fontDescriptor size:fontSize]; } + return font; } diff --git a/ElementX/Sources/Other/SwiftUI/Search.swift b/ElementX/Sources/Other/SwiftUI/Search.swift index b1f239c4f..1d2b865ba 100644 --- a/ElementX/Sources/Other/SwiftUI/Search.swift +++ b/ElementX/Sources/Other/SwiftUI/Search.swift @@ -34,11 +34,8 @@ private struct InteractiveDismissSearchModifier: ViewModifier { @Environment(\.isSearching) private var isSearching func body(content: Content) -> some View { - if isSearching { - content.interactiveDismissDisabled() - } else { - content - } + content + .interactiveDismissDisabled(isSearching) } } diff --git a/ElementX/Sources/Screens/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift b/ElementX/Sources/Screens/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift index 019040fab..8b4d5167f 100644 --- a/ElementX/Sources/Screens/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift +++ b/ElementX/Sources/Screens/AnalyticsPromptScreen/View/AnalyticsPromptScreen.swift @@ -51,7 +51,7 @@ struct AnalyticsPromptScreen: View { .foregroundColor(.compound.textSecondary) Divider() - .overlay { Color.compound._borderRowSeparator } + .overlay { Color.compound.borderDisabled } .padding(.vertical, 20) checkmarkList diff --git a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift index 2964737ca..f1c7a9a84 100644 --- a/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift +++ b/ElementX/Sources/Screens/CreateRoom/View/CreateRoomScreen.swift @@ -188,7 +188,7 @@ struct CreateRoomScreen: View { .formSectionHeader() .padding(.top, 40) } - .listRowSeparatorTint(.compound._borderRowSeparator) + .listRowSeparatorTint(.compound.borderDisabled) .listRowBackground(Color.element.formRowBackground) } diff --git a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift index 811058e87..d5dc749e7 100644 --- a/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift +++ b/ElementX/Sources/Screens/HomeScreen/View/HomeScreenRoomCell.swift @@ -41,7 +41,7 @@ struct HomeScreenRoomCell: View { .padding(.vertical, verticalInsets) .overlay(alignment: .bottom) { Rectangle() - .fill(Color.compound._borderRowSeparator) + .fill(Color.compound.borderDisabled) .frame(height: 1 / UIScreen.main.scale) .padding(.trailing, -horizontalInsets) } diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift index c3d3a413f..12c6c5c83 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenCoordinator.swift @@ -26,6 +26,7 @@ struct InviteUsersScreenCoordinatorParameters { } enum InviteUsersScreenCoordinatorAction { + case cancel case proceed case invite(users: [String]) case toggleUser(UserProfileProxy) @@ -55,12 +56,14 @@ final class InviteUsersScreenCoordinator: CoordinatorProtocol { viewModel.actions.sink { [weak self] action in guard let self else { return } switch action { + case .cancel: + actionsSubject.send(.cancel) case .proceed: - self.actionsSubject.send(.proceed) + actionsSubject.send(.proceed) case .invite(let users): - self.actionsSubject.send(.invite(users: users)) + actionsSubject.send(.invite(users: users)) case .toggleUser(let user): - self.actionsSubject.send(.toggleUser(user)) + actionsSubject.send(.toggleUser(user)) } } .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift index bb63d332b..9ebaa1372 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenModels.swift @@ -22,6 +22,7 @@ enum InviteUsersScreenErrorType: Error { } enum InviteUsersScreenViewModelAction { + case cancel case proceed case invite(users: [String]) case toggleUser(UserProfileProxy) @@ -86,6 +87,7 @@ struct InviteUsersScreenViewStateBindings { } enum InviteUsersScreenViewAction { + case cancel case proceed case toggleUser(UserProfileProxy) } diff --git a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift index 0eef0ea6e..62ae7f3ed 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/InviteUsersScreenViewModel.swift @@ -51,6 +51,8 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr override func process(viewAction: InviteUsersScreenViewAction) { switch viewAction { + case .cancel: + actionsSubject.send(.cancel) case .proceed: switch roomType { case .draft: diff --git a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift index d38f378e0..3b28cf006 100644 --- a/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift +++ b/ElementX/Sources/Screens/InviteUsersScreen/View/InviteUsersScreen.swift @@ -26,11 +26,7 @@ struct InviteUsersScreen: View { .scrollDismissesKeyboard(.immediately) .navigationTitle(L10n.screenCreateRoomAddPeopleTitle) .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .confirmationAction) { - nextButton - } - } + .toolbar { toolbar } .disableInteractiveDismissOnSearch() .dismissSearchOnDisappear() .searchable(text: $context.searchQuery, placement: .navigationBarDrawer(displayMode: .always), prompt: L10n.commonSearchForSomeone) @@ -123,11 +119,22 @@ struct InviteUsersScreen: View { .frame(width: frame.width) } - private var nextButton: some View { - Button { context.send(viewAction: .proceed) } label: { - Text(context.viewState.actionText) + @ToolbarContentBuilder + private var toolbar: some ToolbarContent { + if !context.viewState.isCreatingRoom { + ToolbarItem(placement: .cancellationAction) { + Button(L10n.actionCancel) { + context.send(viewAction: .cancel) + } + } + } + + ToolbarItem(placement: .confirmationAction) { + Button(context.viewState.actionText) { + context.send(viewAction: .proceed) + } + .disabled(context.viewState.isActionDisabled) } - .disabled(context.viewState.isActionDisabled) } private func deselect(_ user: UserProfileProxy) { diff --git a/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreenCell.swift b/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreenCell.swift index aaafab55f..f61f2340e 100644 --- a/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreenCell.swift +++ b/ElementX/Sources/Screens/InvitesScreen/View/InvitesScreenCell.swift @@ -116,7 +116,7 @@ struct InvitesScreenCell: View { private var separator: some View { Rectangle() - .fill(Color.compound._borderRowSeparator) + .fill(Color.compound.borderDisabled) .frame(height: 1 / UIScreen.main.scale) } diff --git a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift index 347ce3801..03e85c086 100644 --- a/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsEditScreen/View/RoomDetailsEditScreen.swift @@ -91,10 +91,9 @@ struct RoomDetailsEditScreen: View { prompt: canEditName ? Text(L10n.commonRoomNamePlaceholder) : nil, axis: .horizontal) .focused($focus, equals: .name) - .font(.compound.bodyLG) - .foregroundColor(.compound.textPrimary) + .textFieldStyle(.compoundForm) .disabled(!canEditName) - .listRowBackground(canEditName ? nil : Color.clear) + .listRowBackground(canEditName ? Color.element.formRowBackground : .clear) .clipShape(RoundedRectangle(cornerRadius: 8)) } header: { Text(L10n.commonRoomName) @@ -109,14 +108,13 @@ struct RoomDetailsEditScreen: View { TextField(L10n.commonTopic, text: $context.topic, - prompt: canEditTopic ? Text(L10n.commonTopicPlaceholder) : nil, + prompt: canEditTopic ? Text(L10n.commonTopicPlaceholder).foregroundColor(.compound.textPlaceholder) : nil, axis: .vertical) .focused($focus, equals: .topic) - .font(.compound.bodyLG) - .foregroundColor(.compound.textPrimary) + .textFieldStyle(.compoundForm) .disabled(!canEditTopic) - .listRowBackground(canEditTopic ? nil : Color.clear) - .lineLimit(3, reservesSpace: true) + .listRowBackground(canEditTopic ? Color.element.formRowBackground : .clear) + .lineLimit(3...) } header: { Text(L10n.commonTopic) .formSectionHeader() diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift index 8fad0ae57..c49fc981b 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/RoomDetailsScreenCoordinator.swift @@ -104,6 +104,8 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol { guard let self else { return } switch result { + case .cancel: + navigationStackCoordinator?.setSheetCoordinator(nil) case .proceed: break case .invite(let users): @@ -114,7 +116,7 @@ final class RoomDetailsScreenCoordinator: CoordinatorProtocol { } .store(in: &cancellables) - parameters.navigationStackCoordinator?.setSheetCoordinator(userIndicatorController) { [weak self] in + navigationStackCoordinator?.setSheetCoordinator(userIndicatorController) { [weak self] in self?.selectedUsers.value = [] } } diff --git a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift index b9f4b83e4..8c9fc63fc 100644 --- a/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift +++ b/ElementX/Sources/Screens/RoomDetailsScreen/View/RoomDetailsScreen.swift @@ -19,6 +19,8 @@ import SwiftUI struct RoomDetailsScreen: View { @ObservedObject var context: RoomDetailsScreenViewModel.Context + @State private var isTopicExpanded = false + var body: some View { Form { if let recipient = context.viewState.dmRecipient { @@ -111,8 +113,9 @@ struct RoomDetailsScreen: View { if let topic = context.viewState.topic, !topic.isEmpty { Text(topic) .foregroundColor(.compound.textSecondary) - .font(.compound.bodySM) - .lineLimit(3) + .font(.compound.bodyMD) + .lineLimit(isTopicExpanded ? nil : 3) + .onTapGesture { isTopicExpanded.toggle() } } else { Button { context.send(viewAction: .processTapAddTopic) @@ -155,7 +158,7 @@ struct RoomDetailsScreen: View { .accessibilityIdentifier(A11yIdentifiers.roomDetailsScreen.invite) } } - .listRowSeparatorTint(.compound._borderRowSeparator) + .listRowSeparatorTint(.compound.borderDisabled) .buttonStyle(FormButtonStyle(accessory: .navigationLink)) .formSectionStyle() .foregroundColor(.compound.textPrimary) @@ -247,7 +250,7 @@ struct RoomDetailsScreen_Previews: PreviewProvider { .mockCharlie ] let roomProxy = RoomProxyMock(with: .init(displayName: "Room A", - topic: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + topic: "Bacon ipsum dolor amet short ribs buffalo pork loin cupim frankfurter. Burgdoggen pig shankle biltong flank ham jowl sirloin bacon cow. T-bone alcatra boudin beef spare ribs pig fatback jerky swine short ribs shankle chislic frankfurter pork loin. Chicken tri-tip bresaola t-bone pastrami brisket.", // swiftlint:disable:this line_length isDirect: false, isEncrypted: true, canonicalAlias: "#alias:domain.com", diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift index a7cccc9d3..2d8541c84 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenModels.swift @@ -86,6 +86,7 @@ struct RoomScreenViewState: BindableState { var showLoading = false var timelineStyle: TimelineStyle var readReceiptsEnabled: Bool + var isEncryptedOneToOneRoom = false var bindings: RoomScreenViewStateBindings diff --git a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift index a0ac414e7..e60feba32 100644 --- a/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/RoomScreen/RoomScreenViewModel.swift @@ -45,6 +45,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol roomAvatarURL: roomProxy.avatarURL, timelineStyle: ServiceLocator.shared.settings.timelineStyle, readReceiptsEnabled: ServiceLocator.shared.settings.readReceiptsEnabled, + isEncryptedOneToOneRoom: roomProxy.isEncryptedOneToOneRoom, bindings: .init(composerText: "", composerFocused: false)), imageProvider: mediaProvider) diff --git a/ElementX/Sources/Screens/RoomScreen/View/Replies/TimelineReplyView.swift b/ElementX/Sources/Screens/RoomScreen/View/Replies/TimelineReplyView.swift index 96e3a5d48..30e99a16f 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Replies/TimelineReplyView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Replies/TimelineReplyView.swift @@ -101,29 +101,41 @@ struct TimelineReplyView: View { var icon: Icon? + var isTextOnly: Bool { + icon?.mediaSource == nil && icon?.systemIconName == nil + } + + /// The string shown as the message preview. + /// + /// This converts the formatted body to a plain string to remove formatting + /// and render with a consistent font size. This conversion is done to avoid + /// showing markdown characters in the preview for messages with formatting. + var messagePreview: String { + guard let formattedBody else { return plainBody } + return String(formattedBody.characters) + } + var body: some View { - HStack { + HStack(spacing: 8) { iconView .frame(width: imageContainerSize, height: imageContainerSize) .foregroundColor(.compound.iconPrimary) .background(Color.compound.bgSubtlePrimary) .cornerRadius(icon?.cornerRadii ?? 0.0, corners: .allCorners) - if icon?.mediaSource == nil, icon?.systemIconName == nil { - Spacer().frame(width: 4.0) - } - - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: 2) { Text(sender.displayName ?? sender.id) .font(.compound.bodySMSemibold) .foregroundColor(.compound.textPrimary) - Text(formattedBody ?? AttributedString(plainBody)) + Text(messagePreview) .font(.compound.bodyMD) - .foregroundColor(.compound.textPlaceholder) + .foregroundColor(.compound.textSecondary) .tint(.compound.textLinkExternal) .lineLimit(2) } + .padding(.leading, isTextOnly ? 8 : 0) + .padding(.trailing, 8) } } @@ -154,7 +166,7 @@ struct TimelineReplyView_Previews: PreviewProvider { static let viewModel = RoomScreenViewModel.mock static var previews: some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 20) { TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .notLoaded(eventID: "")) TimelineReplyView(placement: .timeline, timelineItemReplyDetails: .loading(eventID: "")) diff --git a/ElementX/Sources/Screens/RoomScreen/View/RoomAttachmentPicker.swift b/ElementX/Sources/Screens/RoomScreen/View/RoomAttachmentPicker.swift index 0f695437e..ae93a7046 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/RoomAttachmentPicker.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/RoomAttachmentPicker.swift @@ -65,8 +65,8 @@ struct RoomAttachmentPicker: View { } } } - .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) .presentationDetents([.height(sheetContentHeight)]) + .presentationBackground(Color.compound.bgCanvasDefault) .presentationDragIndicator(.visible) } } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift index 6d677df20..8fe8264c0 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Style/TimelineItemBubbledStylerView.swift @@ -31,14 +31,13 @@ struct TimelineItemBubbledStylerView: View { @State private var showItemActionMenu = false - private var isTextItem: Bool { - timelineItem is TextBasedRoomTimelineItem - } + private var isTextItem: Bool { timelineItem is TextBasedRoomTimelineItem } + private var isEncryptedOneToOneRoom: Bool { context.viewState.isEncryptedOneToOneRoom } var body: some View { ZStack(alignment: .trailingFirstTextBaseline) { VStack(alignment: alignment, spacing: -12) { - if !timelineItem.isOutgoing { + if !timelineItem.isOutgoing, !isEncryptedOneToOneRoom { header .zIndex(1) } @@ -213,7 +212,6 @@ struct TimelineItemBubbledStylerView: View { // The rendered reply bubble with a greedy width. The custom layout prevents // the infinite width from increasing the overall width of the view. TimelineReplyView(placement: .timeline, timelineItemReplyDetails: replyDetails) - .foregroundColor(.compound.textPlaceholder) .fixedSize(horizontal: false, vertical: true) .padding(4.0) .frame(maxWidth: .infinity, alignment: .leading) @@ -235,7 +233,7 @@ struct TimelineItemBubbledStylerView: View { } private var messageBubbleTopPadding: CGFloat { - guard timelineItem.isOutgoing else { return 0 } + guard timelineItem.isOutgoing || isEncryptedOneToOneRoom else { return 0 } return timelineGroupStyle == .single || timelineGroupStyle == .first ? 8 : 0 } @@ -298,13 +296,14 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider { } static var mockTimeline: some View { - VStack(alignment: .leading, spacing: 0) { - ForEach(viewModel.state.items) { item in - item.padding(TimelineStyle.bubbles.rowInsets) // Insets added in the table view cells + ScrollView { + VStack(alignment: .leading, spacing: 0) { + ForEach(viewModel.state.items) { item in + item.padding(TimelineStyle.bubbles.rowInsets) // Insets added in the table view cells + } } } .environment(\.timelineStyle, .bubbles) - .previewLayout(.sizeThatFits) .environmentObject(viewModel.context) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReactionsView.swift b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReactionsView.swift index 1807895cb..77690b348 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReactionsView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Supplementary/TimelineReactionsView.swift @@ -53,7 +53,7 @@ struct TimelineReactionButton: View { .background( backgroundShape .strokeBorder(reaction.isHighlighted ? Color.compound.textSecondary : .compound.bgCanvasDefault, lineWidth: 2) - .background(reaction.isHighlighted ? Color.compound.textPrimary.opacity(0.1) : .compound.bgSubtleSecondary, in: backgroundShape) + .background(reaction.isHighlighted ? Color.compound.textPrimary.opacity(0.1) : .compound._bgReactionButton, in: backgroundShape) ) .accessibilityElement(children: .combine) } diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/CollapsibleRoomTimelineView.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/CollapsibleRoomTimelineView.swift index 2f44a1502..22e661cbe 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/CollapsibleRoomTimelineView.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/CollapsibleRoomTimelineView.swift @@ -42,33 +42,25 @@ struct CollapsibleRoomTimelineView: View { } } -struct CollapsibleRoomTimelineView_Previews: PreviewProvider { - static var previews: some View { - let item = CollapsibleTimelineItem(items: [SeparatorRoomTimelineItem(id: "First separator", text: "This is a separator"), - SeparatorRoomTimelineItem(id: "Second separator", text: "This is another separator")]) - CollapsibleRoomTimelineView(timelineItem: item) - } -} - private struct CollapsibleRoomTimelineItemDisclosureGroupStyle: DisclosureGroupStyle { func makeBody(configuration: Configuration) -> some View { VStack(spacing: 0.0) { - HStack(alignment: .center) { - configuration.label - Text(Image(systemName: "chevron.forward")) - .rotationEffect(.degrees(configuration.isExpanded ? 90 : 0)) - .animation(.elementDefault, value: configuration.isExpanded) + Button { configuration.isExpanded.toggle() } label: { + HStack(alignment: .center) { + configuration.label + Text(Image(systemName: "chevron.forward")) + .rotationEffect(.degrees(configuration.isExpanded ? 90 : 0)) + .animation(.elementDefault, value: configuration.isExpanded) + } + .font(.compound.bodySM) + .foregroundColor(.compound.textSecondary) + .padding(.horizontal, 36.0) + .padding(.vertical, 12.0) + .contentShape(Rectangle()) } + .buttonStyle(.plain) .frame(maxWidth: .infinity) - .font(.compound.bodySM) - .foregroundColor(.compound.textSecondary) - .padding(.horizontal, 36.0) - .padding(.top, 20.0) - .padding(.bottom, 12.0) - - .onTapGesture { - configuration.isExpanded.toggle() - } + .padding(.top, 8.0) if configuration.isExpanded { configuration.content @@ -76,3 +68,14 @@ private struct CollapsibleRoomTimelineItemDisclosureGroupStyle: DisclosureGroupS } } } + +struct CollapsibleRoomTimelineView_Previews: PreviewProvider { + static let item = CollapsibleTimelineItem(items: [ + SeparatorRoomTimelineItem(id: "First separator", text: "This is a separator"), + SeparatorRoomTimelineItem(id: "Second separator", text: "This is another separator") + ]) + + static var previews: some View { + CollapsibleRoomTimelineView(timelineItem: item) + } +} diff --git a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift index 3402bc430..e38251d3f 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/Timeline/FormattedBodyText.swift @@ -57,16 +57,17 @@ struct FormattedBodyText: View { // The rendered blockquote with a greedy width. The custom layout prevents the // infinite width from increasing the overall width of the view. Text(component.attributedString.mergingAttributes(blockquoteAttributes)) - .foregroundColor(.compound.textPlaceholder) + .foregroundColor(.compound.textSecondary) .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity, alignment: .leading) .padding(.leading, 12.0) .overlay(alignment: .leading) { // User an overlay here so that the rectangle's infinite height doesn't take priority - Rectangle() + Capsule() .frame(width: 2.0) - .padding(.leading, 6.0) - .foregroundColor(.compound.textPlaceholder) + .padding(.leading, 5.0) + .foregroundColor(.compound.textSecondary) + .padding(.vertical, 2) } .layoutPriority(TimelineBubbleLayout.Priority.visibleQuote) } else { @@ -152,6 +153,12 @@ struct FormattedBodyText_Previews: PreviewProvider {
Third blockquote with a link in it
""", """ +
A blockquote that is long and goes onto multiple lines as the first item in the message
+

Then another line of text here to reply to the blockquote, which is also a multiline component.

+
Short line here.
+

And a simple reply here.

+ """, + """ Hello world

Text

Hello world diff --git a/ElementX/Sources/Screens/RoomScreen/View/TimelineItemMenu.swift b/ElementX/Sources/Screens/RoomScreen/View/TimelineItemMenu.swift index af94e57ae..a7b6ace5c 100644 --- a/ElementX/Sources/Screens/RoomScreen/View/TimelineItemMenu.swift +++ b/ElementX/Sources/Screens/RoomScreen/View/TimelineItemMenu.swift @@ -86,8 +86,8 @@ public struct TimelineItemMenu: View { } } } - .background(Color.compound.bgCanvasDefault.ignoresSafeArea()) .presentationDetents([.medium, .large]) + .presentationBackground(Color.compound.bgCanvasDefault) .presentationDragIndicator(.visible) } @@ -164,7 +164,7 @@ public struct TimelineItemMenu: View { private func reactionBackgroundColor(for emoji: String) -> Color { if item.properties.reactions.first(where: { $0.key == emoji }) != nil { - return .compound._bgReactionButton + return .compound.bgActionPrimaryRest } else { return .clear } diff --git a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift index 10c9d061d..65fa80b29 100644 --- a/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift +++ b/ElementX/Sources/Screens/StartChatScreen/StartChatScreenCoordinator.swift @@ -97,6 +97,8 @@ final class StartChatScreenCoordinator: CoordinatorProtocol { guard let self else { return } switch result { + case .cancel: + break // Not shown in this flow. case .proceed: openCreateRoomScreen() case .invite: diff --git a/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift b/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift index 9548ed208..d3142376d 100644 --- a/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift +++ b/ElementX/Sources/Screens/StartChatScreen/View/StartChatScreen.swift @@ -79,7 +79,7 @@ struct StartChatScreen: View { private var inviteFriendsSection: some View { Section { MatrixUserShareLink(userID: context.viewState.userID) { - Label(L10n.actionInviteFriendsToApp(InfoPlistReader.main.bundleDisplayName), systemImage: "square.and.arrow.up") + Label(L10n.actionInvitePeopleToApp(InfoPlistReader.main.bundleDisplayName), systemImage: "square.and.arrow.up") } .buttonStyle(FormButtonStyle()) .accessibilityIdentifier(A11yIdentifiers.startChatScreen.inviteFriends) diff --git a/project.yml b/project.yml index 6dca13a0e..7639528d0 100644 --- a/project.yml +++ b/project.yml @@ -53,7 +53,7 @@ packages: minorVersion: 5.13.0 Compound: url: https://github.com/vector-im/compound-ios - revision: d59c317362beba940baa43d6aacdd357e208048d + revision: e8b35fdd8c4008079dfce203e63bf7a05582d7b9 # path: ../compound-ios Algorithms: url: https://github.com/apple/swift-algorithms