mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 08:15:24 +00:00
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.
This commit is contained in:
@@ -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")
|
||||
],
|
||||
|
||||
@@ -4980,7 +4980,7 @@
|
||||
repositoryURL = "https://github.com/vector-im/compound-ios";
|
||||
requirement = {
|
||||
kind = revision;
|
||||
revision = d59c317362beba940baa43d6aacdd357e208048d;
|
||||
revision = e8b35fdd8c4008079dfce203e63bf7a05582d7b9;
|
||||
};
|
||||
};
|
||||
9A472EE0218FE7DCF5283429 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */ = {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ struct AnalyticsPromptScreen: View {
|
||||
.foregroundColor(.compound.textSecondary)
|
||||
|
||||
Divider()
|
||||
.overlay { Color.compound._borderRowSeparator }
|
||||
.overlay { Color.compound.borderDisabled }
|
||||
.padding(.vertical, 20)
|
||||
|
||||
checkmarkList
|
||||
|
||||
@@ -188,7 +188,7 @@ struct CreateRoomScreen: View {
|
||||
.formSectionHeader()
|
||||
.padding(.top, 40)
|
||||
}
|
||||
.listRowSeparatorTint(.compound._borderRowSeparator)
|
||||
.listRowSeparatorTint(.compound.borderDisabled)
|
||||
.listRowBackground(Color.element.formRowBackground)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -86,6 +86,7 @@ struct RoomScreenViewState: BindableState {
|
||||
var showLoading = false
|
||||
var timelineStyle: TimelineStyle
|
||||
var readReceiptsEnabled: Bool
|
||||
var isEncryptedOneToOneRoom = false
|
||||
|
||||
var bindings: RoomScreenViewStateBindings
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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: ""))
|
||||
|
||||
@@ -65,8 +65,8 @@ struct RoomAttachmentPicker: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
|
||||
.presentationDetents([.height(sheetContentHeight)])
|
||||
.presentationBackground(Color.compound.bgCanvasDefault)
|
||||
.presentationDragIndicator(.visible)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,14 +31,13 @@ struct TimelineItemBubbledStylerView<Content: View>: 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<Content: View>: 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<Content: View>: 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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+25
-22
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
<blockquote>Third blockquote with a <a href=\"https://www.matrix.org/\">link</a> in it</blockquote>
|
||||
""",
|
||||
"""
|
||||
<blockquote>A blockquote that is long and goes onto multiple lines as the first item in the message</blockquote>
|
||||
<p>Then another line of text here to reply to the blockquote, which is also a multiline component.</p>
|
||||
<blockquote>Short line here.</blockquote>
|
||||
<p>And a simple reply here.</p>
|
||||
""",
|
||||
"""
|
||||
<code>Hello world</code>
|
||||
<p>Text</p>
|
||||
<code><b>Hello</b> <i>world</i></code>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user