Add SwiftLint rule to enforce stack spacing. (#2080)

Xcode 15 changes the default behaviour and given we're always working from designs, we shouldn't rely on it for our layout.
This commit is contained in:
Doug 2023-11-14 12:38:38 +00:00 committed by GitHub
parent 752ad66397
commit 2c1e61a1bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
123 changed files with 289 additions and 293 deletions

View File

@ -49,6 +49,18 @@ nesting:
warning: 5 warning: 5
custom_rules: custom_rules:
vstack_spacing:
regex: "(?-s)VStack((?!spacing:).)*\\s*\\{"
match_kinds: identifier
message: "Please use explicit spacing in VStacks."
severity: warning
hstack_spacing:
regex: "(?-s)HStack((?!spacing:).)*\\s*\\{"
match_kinds: identifier
message: "Please use explicit spacing in HStacks."
severity: warning
print_deprecation: print_deprecation:
regex: "\\b(print)\\b" regex: "\\b(print)\\b"
match_kinds: identifier match_kinds: identifier

View File

@ -263,7 +263,7 @@
{ {
"identity" : "swiftui-introspect", "identity" : "swiftui-introspect",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect.git", "location" : "https://github.com/siteline/SwiftUI-Introspect",
"state" : { "state" : {
"revision" : "b94da693e57eaf79d16464b8b7c90d09cba4e290", "revision" : "b94da693e57eaf79d16464b8b7c90d09cba4e290",
"version" : "0.9.2" "version" : "0.9.2"

View File

@ -86,7 +86,7 @@ struct MapLibreStaticMapView<PinAnnotation: View>: View {
} label: { } label: {
placeholderImage placeholderImage
.overlay { .overlay {
VStack { VStack(spacing: 0) {
Image(systemName: "arrow.clockwise") Image(systemName: "arrow.clockwise")
Text(L10n.actionStaticMapLoad) Text(L10n.actionStaticMapLoad)
} }

View File

@ -87,7 +87,7 @@ struct ShimmerOverlay_Previews: PreviewProvider, TestablePreview {
userIndicatorController: ServiceLocator.shared.userIndicatorController) userIndicatorController: ServiceLocator.shared.userIndicatorController)
static var previews: some View { static var previews: some View {
VStack { VStack(spacing: 0) {
ForEach(0...8, id: \.self) { _ in ForEach(0...8, id: \.self) { _ in
HomeScreenRoomCell(room: .placeholder(), context: viewModel.context, isSelected: false) HomeScreenRoomCell(room: .placeholder(), context: viewModel.context, isSelected: false)
} }

View File

@ -97,7 +97,7 @@ struct FormButtonStyle: PrimitiveButtonStyle {
var accessory: FormRowAccessory? var accessory: FormRowAccessory?
func makeBody(configuration: Configuration) -> some View { func makeBody(configuration: Configuration) -> some View {
HStack { HStack(spacing: 8) {
configuration.label configuration.label
.labelStyle(FormRowLabelStyle(role: configuration.role)) .labelStyle(FormRowLabelStyle(role: configuration.role))
.foregroundColor(.compound.textPrimary) .foregroundColor(.compound.textPrimary)
@ -126,7 +126,7 @@ struct FormActionButtonStyle: ButtonStyle {
let title: String let title: String
func makeBody(configuration: Configuration) -> some View { func makeBody(configuration: Configuration) -> some View {
VStack { VStack(spacing: 8) {
configuration.label configuration.label
.buttonStyle(.plain) .buttonStyle(.plain)
.foregroundColor(.compound.textPrimary) .foregroundColor(.compound.textPrimary)

View File

@ -61,7 +61,7 @@ struct FormRowLabelStyle: LabelStyle {
struct FormRowLabelStyle_Previews: PreviewProvider, TestablePreview { struct FormRowLabelStyle_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 12) {
Label("Person", systemImage: "person") Label("Person", systemImage: "person")
.labelStyle(FormRowLabelStyle()) .labelStyle(FormRowLabelStyle())

View File

@ -50,7 +50,7 @@ struct FullscreenDialog<Content: View, BottomContent: View>: View {
var standardLayout: some View { var standardLayout: some View {
GeometryReader { geometry in GeometryReader { geometry in
ScrollView { ScrollView {
VStack { VStack(spacing: 0) {
Spacer() Spacer()
.frame(height: UIConstants.spacerHeight(in: geometry)) .frame(height: UIConstants.spacerHeight(in: geometry))
@ -62,7 +62,7 @@ struct FullscreenDialog<Content: View, BottomContent: View>: View {
} }
.scrollBounceBehavior(.basedOnSize) .scrollBounceBehavior(.basedOnSize)
.safeAreaInset(edge: .bottom) { .safeAreaInset(edge: .bottom) {
VStack { VStack(spacing: 0) {
bottomContent() bottomContent()
.readableFrame() .readableFrame()
.padding(.horizontal, horizontalPadding) .padding(.horizontal, horizontalPadding)
@ -81,7 +81,7 @@ struct FullscreenDialog<Content: View, BottomContent: View>: View {
var accessibilityLayout: some View { var accessibilityLayout: some View {
GeometryReader { geometry in GeometryReader { geometry in
ScrollView { ScrollView {
VStack { VStack(spacing: 0) {
Spacer() Spacer()
.frame(height: UIConstants.spacerHeight(in: geometry)) .frame(height: UIConstants.spacerHeight(in: geometry))

View File

@ -30,7 +30,7 @@ struct UserIndicatorModalView: View {
ProgressView(value: progressFraction) ProgressView(value: progressFraction)
} }
HStack { HStack(spacing: 8) {
if let iconName = indicator.iconName { if let iconName = indicator.iconName {
Image(systemName: iconName) Image(systemName: iconName)
.font(.compound.bodyLG) .font(.compound.bodyLG)

View File

@ -124,7 +124,7 @@ private struct WaveformShape: Shape {
struct EstimatedWaveformView_Previews: PreviewProvider, TestablePreview { struct EstimatedWaveformView_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
// Wrap the WaveformView in a VStack otherwise the preview test will fail (because of Prefire / GeometryReader) // Wrap the WaveformView in a VStack otherwise the preview test will fail (because of Prefire / GeometryReader)
VStack { VStack(spacing: 0) {
EstimatedWaveformView(waveform: EstimatedWaveform.mockWaveform, progress: 0.5) EstimatedWaveformView(waveform: EstimatedWaveform.mockWaveform, progress: 0.5)
.frame(width: 140, height: 50) .frame(width: 140, height: 50)
} }

View File

@ -110,12 +110,12 @@ extension VoiceMessageButton.State {
struct VoiceMessageButton_Previews: PreviewProvider, TestablePreview { struct VoiceMessageButton_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
VStack { VStack(spacing: 8) {
HStack { HStack(spacing: 8) {
VoiceMessageButton(state: .paused, size: .small, action: { }) VoiceMessageButton(state: .paused, size: .small, action: { })
VoiceMessageButton(state: .paused, size: .medium, action: { }) VoiceMessageButton(state: .paused, size: .medium, action: { })
} }
HStack { HStack(spacing: 8) {
VoiceMessageButton(state: .playing, size: .small, action: { }) VoiceMessageButton(state: .playing, size: .small, action: { })
VoiceMessageButton(state: .playing, size: .medium, action: { }) VoiceMessageButton(state: .playing, size: .medium, action: { })
} }

View File

@ -102,7 +102,7 @@ private struct PINDigitField: View {
struct PINTextField_Previews: PreviewProvider, TestablePreview { struct PINTextField_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
VStack { VStack(spacing: 8) {
PreviewWrapper(pinCode: "", isSecure: false) PreviewWrapper(pinCode: "", isSecure: false)
PreviewWrapper(pinCode: "12", isSecure: false) PreviewWrapper(pinCode: "12", isSecure: false)
PreviewWrapper(pinCode: "1234", isSecure: false) PreviewWrapper(pinCode: "1234", isSecure: false)

View File

@ -123,12 +123,12 @@ struct CompletionSuggestion_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
// Putting them is VStack allows the preview to work properly in tests // Putting them is VStack allows the preview to work properly in tests
VStack { VStack(spacing: 8) {
CompletionSuggestionView(imageProvider: MockMediaProvider(), CompletionSuggestionView(imageProvider: MockMediaProvider(),
items: [.user(item: MentionSuggestionItem(id: "@user_mention_1:matrix.org", displayName: "User 1", avatarURL: nil)), items: [.user(item: MentionSuggestionItem(id: "@user_mention_1:matrix.org", displayName: "User 1", avatarURL: nil)),
.user(item: MentionSuggestionItem(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: URL.documentsDirectory))]) { _ in } .user(item: MentionSuggestionItem(id: "@user_mention_2:matrix.org", displayName: "User 2", avatarURL: URL.documentsDirectory))]) { _ in }
} }
VStack { VStack(spacing: 8) {
CompletionSuggestionView(imageProvider: MockMediaProvider(), CompletionSuggestionView(imageProvider: MockMediaProvider(),
items: multipleItems) { _ in } items: multipleItems) { _ in }
} }

View File

@ -299,7 +299,7 @@ struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
ComposerToolbar.mock(focused: true) ComposerToolbar.mock(focused: true)
// Putting them is VStack allows the completion suggestion preview to work properly in tests // Putting them is VStack allows the completion suggestion preview to work properly in tests
VStack { VStack(spacing: 8) {
// The mock functon can't be used in this context because it does not hold a reference to the view model, losing the combine subscriptions // The mock functon can't be used in this context because it does not hold a reference to the view model, losing the combine subscriptions
ComposerToolbar(context: composerViewModel.context, ComposerToolbar(context: composerViewModel.context,
wysiwygViewModel: wysiwygViewModel, wysiwygViewModel: wysiwygViewModel,
@ -307,7 +307,7 @@ struct ComposerToolbar_Previews: PreviewProvider, TestablePreview {
} }
.previewDisplayName("With Suggestions") .previewDisplayName("With Suggestions")
VStack { VStack(spacing: 8) {
ComposerToolbar.textWithVoiceMessage(focused: false) ComposerToolbar.textWithVoiceMessage(focused: false)
ComposerToolbar.textWithVoiceMessage(focused: true) ComposerToolbar.textWithVoiceMessage(focused: true)
ComposerToolbar.voiceMessageRecordingMock(recording: true) ComposerToolbar.voiceMessageRecordingMock(recording: true)

View File

@ -154,7 +154,7 @@ private struct MessageComposerEditHeader: View {
let action: () -> Void let action: () -> Void
var body: some View { var body: some View {
HStack(alignment: .center) { HStack(alignment: .center, spacing: 8) {
Label(L10n.commonEditing, Label(L10n.commonEditing,
iconAsset: Asset.Images.editing, iconAsset: Asset.Images.editing,
iconSize: .xSmall, iconSize: .xSmall,
@ -227,7 +227,7 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
} }
static var previews: some View { static var previews: some View {
VStack { VStack(spacing: 8) {
messageComposer(sendingDisabled: true) messageComposer(sendingDisabled: true)
messageComposer("Some message", messageComposer("Some message",
@ -240,7 +240,7 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
.padding(.horizontal) .padding(.horizontal)
ScrollView { ScrollView {
VStack { VStack(spacing: 8) {
ForEach(replyTypes, id: \.self) { replyDetails in ForEach(replyTypes, id: \.self) { replyDetails in
messageComposer(mode: .reply(itemID: .random, messageComposer(mode: .reply(itemID: .random,
replyDetails: replyDetails, isThread: false)) replyDetails: replyDetails, isThread: false))
@ -252,7 +252,7 @@ struct MessageComposer_Previews: PreviewProvider, TestablePreview {
.previewDisplayName("Replying") .previewDisplayName("Replying")
ScrollView { ScrollView {
VStack { VStack(spacing: 8) {
ForEach(replyTypes, id: \.self) { replyDetails in ForEach(replyTypes, id: \.self) { replyDetails in
messageComposer(mode: .reply(itemID: .random, messageComposer(mode: .reply(itemID: .random,
replyDetails: replyDetails, isThread: true)) replyDetails: replyDetails, isThread: true))

View File

@ -41,18 +41,16 @@ struct VoiceMessagePreviewComposer: View {
} }
var body: some View { var body: some View {
HStack { HStack(spacing: 8) {
HStack { VoiceMessageButton(state: .init(playerState.playerButtonPlaybackState),
VoiceMessageButton(state: .init(playerState.playerButtonPlaybackState), size: .small,
size: .small, action: onPlayPause)
action: onPlayPause) Text(timeLabelContent)
Text(timeLabelContent) .lineLimit(1)
.lineLimit(1) .font(.compound.bodySMSemibold)
.font(.compound.bodySMSemibold) .foregroundColor(.compound.textSecondary)
.foregroundColor(.compound.textSecondary) .monospacedDigit()
.monospacedDigit() .fixedSize(horizontal: true, vertical: true)
.fixedSize(horizontal: true, vertical: true)
}
waveformView waveformView
.waveformInteraction(isDragging: $isDragging, .waveformInteraction(isDragging: $isDragging,
@ -130,9 +128,7 @@ struct VoiceMessagePreviewComposer_Previews: PreviewProvider, TestablePreview {
static let waveformData: [Float] = Array(repeating: 1.0, count: 1000) static let waveformData: [Float] = Array(repeating: 1.0, count: 1000)
static var previews: some View { static var previews: some View {
VStack { VoiceMessagePreviewComposer(playerState: playerState, waveform: .data(waveformData), onPlay: { }, onPause: { }, onSeek: { _ in }, onScrubbing: { _ in })
VoiceMessagePreviewComposer(playerState: playerState, waveform: .data(waveformData), onPlay: { }, onPause: { }, onSeek: { _ in }, onScrubbing: { _ in }) .fixedSize(horizontal: false, vertical: true)
.fixedSize(horizontal: false, vertical: true)
}
} }
} }

View File

@ -69,7 +69,7 @@ private struct VoiceMessageRecordingButtonStyle: ButtonStyle {
struct VoiceMessageRecordingButton_Previews: PreviewProvider, TestablePreview { struct VoiceMessageRecordingButton_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
HStack { HStack(spacing: 8) {
VoiceMessageRecordingButton(mode: .idle) VoiceMessageRecordingButton(mode: .idle)
VoiceMessageRecordingButton(mode: .recording) VoiceMessageRecordingButton(mode: .recording)

View File

@ -45,9 +45,7 @@ struct VoiceMessageRecordingComposer_Previews: PreviewProvider, TestablePreview
static let recorderState = AudioRecorderState() static let recorderState = AudioRecorderState()
static var previews: some View { static var previews: some View {
VStack { VoiceMessageRecordingComposer(recorderState: recorderState)
VoiceMessageRecordingComposer(recorderState: recorderState) .fixedSize(horizontal: false, vertical: true)
.fixedSize(horizontal: false, vertical: true)
}
} }
} }

View File

@ -42,7 +42,7 @@ struct VoiceMessageRecordingView: View {
} }
var body: some View { var body: some View {
HStack { HStack(spacing: 8) {
VoiceMessageRecordingBadge() VoiceMessageRecordingBadge()
.frame(width: recordingIndicatorSize, height: recordingIndicatorSize) .frame(width: recordingIndicatorSize, height: recordingIndicatorSize)

View File

@ -137,7 +137,7 @@ private struct CreatePollOptionView: View {
let deleteAction: () -> Void let deleteAction: () -> Void
var body: some View { var body: some View {
HStack { HStack(spacing: 8) {
if editMode?.wrappedValue == .active { if editMode?.wrappedValue == .active {
Button(role: .destructive, action: deleteAction) { Button(role: .destructive, action: deleteAction) {
CompoundIcon(\.delete) CompoundIcon(\.delete)

View File

@ -20,19 +20,15 @@ struct EmojiPickerScreenHeaderView: View {
let title: String let title: String
var body: some View { var body: some View {
HStack { Text(title)
Text(title) .font(.compound.bodyMD.bold())
.font(.compound.bodyMD.bold()) .foregroundColor(.compound.textPrimary)
.foregroundColor(.compound.textPrimary) .frame(maxWidth: .infinity, alignment: .leading)
.frame(maxWidth: .infinity, alignment: .leading)
}
} }
} }
struct EmojiPickerScreenHeaderView_Previews: PreviewProvider, TestablePreview { struct EmojiPickerScreenHeaderView_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
Group { EmojiPickerScreenHeaderView(title: "Title")
EmojiPickerScreenHeaderView(title: "Title")
}
} }
} }

View File

@ -103,7 +103,7 @@ struct HomeScreenRoomCell: View {
@ViewBuilder @ViewBuilder
private var footer: some View { private var footer: some View {
HStack(alignment: .firstTextBaseline) { HStack(alignment: .firstTextBaseline, spacing: 0) {
ZStack(alignment: .topLeading) { ZStack(alignment: .topLeading) {
// Hidden text with 2 lines to maintain consistent height, scaling with dynamic text. // Hidden text with 2 lines to maintain consistent height, scaling with dynamic text.
Text(" \n ") Text(" \n ")

View File

@ -70,7 +70,7 @@ struct InvitesScreenCell: View {
@ViewBuilder @ViewBuilder
private var inviterView: some View { private var inviterView: some View {
if let invitedText = attributedInviteText, let name = invite.roomDetails.inviter?.displayName { if let invitedText = attributedInviteText, let name = invite.roomDetails.inviter?.displayName {
HStack(alignment: .firstTextBaseline) { HStack(alignment: .firstTextBaseline, spacing: 8) {
LoadableAvatarImage(url: invite.roomDetails.inviter?.avatarURL, LoadableAvatarImage(url: invite.roomDetails.inviter?.avatarURL,
name: name, name: name,
contentID: name, contentID: name,
@ -85,7 +85,7 @@ struct InvitesScreenCell: View {
@ViewBuilder @ViewBuilder
private var textualContent: some View { private var textualContent: some View {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 0) {
Text(title) Text(title)
.font(.compound.bodyLGSemibold) .font(.compound.bodyLGSemibold)
.foregroundColor(.compound.textPrimary) .foregroundColor(.compound.textPrimary)

View File

@ -24,7 +24,7 @@ struct OnboardingScreen: View {
var body: some View { var body: some View {
GeometryReader { geometry in GeometryReader { geometry in
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 0) {
Spacer() Spacer()
.frame(height: UIConstants.spacerHeight(in: geometry)) .frame(height: UIConstants.spacerHeight(in: geometry))
@ -36,6 +36,7 @@ struct OnboardingScreen: View {
.frame(width: geometry.size.width) .frame(width: geometry.size.width)
.padding(.bottom, UIConstants.actionButtonBottomPadding) .padding(.bottom, UIConstants.actionButtonBottomPadding)
.padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16) .padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16)
.padding(.top, 8)
Spacer() Spacer()
.frame(height: UIConstants.spacerHeight(in: geometry)) .frame(height: UIConstants.spacerHeight(in: geometry))
@ -49,7 +50,7 @@ struct OnboardingScreen: View {
} }
var content: some View { var content: some View {
VStack { VStack(spacing: 0) {
Spacer() Spacer()
if verticalSizeClass == .regular { if verticalSizeClass == .regular {

View File

@ -24,7 +24,7 @@ struct RoomMembersListScreen: View {
var body: some View { var body: some View {
ScrollView { ScrollView {
LazyVStack(alignment: .leading) { LazyVStack(alignment: .leading, spacing: 12) {
membersSection(data: context.viewState.visibleInvitedMembers, sectionTitle: L10n.screenRoomMemberListPendingHeaderTitle) membersSection(data: context.viewState.visibleInvitedMembers, sectionTitle: L10n.screenRoomMemberListPendingHeaderTitle)
membersSection(data: context.viewState.visibleJoinedMembers, sectionTitle: L10n.screenRoomMemberListHeaderTitle(Int(context.viewState.joinedMembersCount))) membersSection(data: context.viewState.visibleJoinedMembers, sectionTitle: L10n.screenRoomMemberListHeaderTitle(Int(context.viewState.joinedMembersCount)))
} }
@ -54,7 +54,7 @@ struct RoomMembersListScreen: View {
Text(sectionTitle) Text(sectionTitle)
.foregroundColor(.compound.textSecondary) .foregroundColor(.compound.textSecondary)
.font(.compound.bodyLG) .font(.compound.bodyLG)
.padding(.vertical, 12) .padding(.top, 12)
} }
} }
.padding(.horizontal) .padding(.horizontal)

View File

@ -24,7 +24,7 @@ struct RoomMembersListScreenMemberCell: View {
Button { Button {
context.send(viewAction: .selectMember(id: member.id)) context.send(viewAction: .selectMember(id: member.id))
} label: { } label: {
HStack { HStack(spacing: 8) {
LoadableAvatarImage(url: member.avatarURL, LoadableAvatarImage(url: member.avatarURL,
name: member.name ?? "", name: member.name ?? "",
contentID: member.id, contentID: member.id,
@ -36,26 +36,24 @@ struct RoomMembersListScreenMemberCell: View {
.font(.compound.bodyMDSemibold) .font(.compound.bodyMDSemibold)
.foregroundColor(.compound.textPrimary) .foregroundColor(.compound.textPrimary)
.lineLimit(1) .lineLimit(1)
Spacer()
} }
.frame(maxWidth: .infinity, alignment: .leading)
.accessibilityElement(children: .combine) .accessibilityElement(children: .combine)
} }
} }
} }
struct RoomMembersListMemberCell_Previews: PreviewProvider, TestablePreview { struct RoomMembersListMemberCell_Previews: PreviewProvider, TestablePreview {
static let members: [RoomMemberProxyMock] = [
.mockAlice,
.mockBob,
.mockCharlie
]
static let viewModel = RoomMembersListScreenViewModel(roomProxy: RoomProxyMock(with: .init(displayName: "Some room", members: members)),
mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController)
static var previews: some View { static var previews: some View {
let members: [RoomMemberProxyMock] = [ VStack(spacing: 12) {
.mockAlice,
.mockBob,
.mockCharlie
]
let viewModel = RoomMembersListScreenViewModel(roomProxy: RoomProxyMock(with: .init(displayName: "Some room", members: members)),
mediaProvider: MockMediaProvider(),
userIndicatorController: ServiceLocator.shared.userIndicatorController)
return VStack {
ForEach(members, id: \.userID) { member in ForEach(members, id: \.userID) { member in
RoomMembersListScreenMemberCell(member: .init(withProxy: member), context: viewModel.context) RoomMembersListScreenMemberCell(member: .init(withProxy: member), context: viewModel.context)
} }

View File

@ -72,7 +72,7 @@ struct LongPressWithFeedback_Previews: PreviewProvider, TestablePreview {
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ScrollView { ScrollView {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 8) {
mockBubble("This is a message from somebody with a couple of lines of text.") mockBubble("This is a message from somebody with a couple of lines of text.")
.longPressWithFeedback { isPresentingSheet = true } .longPressWithFeedback { isPresentingSheet = true }

View File

@ -33,7 +33,7 @@ private struct TimelineItemAccessibilityModifier: ViewModifier {
case let timelineItem as EventBasedTimelineItemProtocol: case let timelineItem as EventBasedTimelineItemProtocol:
content content
.accessibilityRepresentation { .accessibilityRepresentation {
VStack { VStack(spacing: 8) {
Text(timelineItem.sender.displayName ?? timelineItem.sender.id) Text(timelineItem.sender.displayName ?? timelineItem.sender.id)
content content
} }

View File

@ -49,7 +49,7 @@ struct TimelineItemBubbledStylerView<Content: View>: View {
} }
VStack(alignment: alignment, spacing: 0) { VStack(alignment: alignment, spacing: 0) {
HStack { HStack(spacing: 0) {
if timelineItem.isOutgoing { if timelineItem.isOutgoing {
Spacer() Spacer()
} }
@ -520,7 +520,7 @@ struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview
} }
static var replies: some View { static var replies: some View {
VStack { VStack(spacing: 0) {
RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""), RoomTimelineItemView(viewState: .init(item: TextRoomTimelineItem(id: .init(timelineID: ""),
timestamp: "10:42", timestamp: "10:42",
isOutgoing: true, isOutgoing: true,

View File

@ -27,16 +27,12 @@ struct TimelineItemPlainStylerView<Content: View>: View {
@State private var showItemActionMenu = false @State private var showItemActionMenu = false
var body: some View { var body: some View {
VStack(alignment: .trailing) { VStack(alignment: .trailing, spacing: 0) {
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
header header
VStack(alignment: .leading, spacing: 4) { VStack(alignment: .leading, spacing: 4) {
HStack(alignment: .firstTextBaseline) { contentWithReply
contentWithReply
Spacer()
}
supplementaryViews supplementaryViews
} }
} }
@ -47,7 +43,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
@ViewBuilder @ViewBuilder
var contentWithReply: some View { var contentWithReply: some View {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 8) {
if let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol { if let messageTimelineItem = timelineItem as? EventBasedMessageTimelineItemProtocol {
if messageTimelineItem.isThreaded { if messageTimelineItem.isThreaded {
ThreadDecorator() ThreadDecorator()
@ -98,8 +94,8 @@ struct TimelineItemPlainStylerView<Content: View>: View {
@ViewBuilder @ViewBuilder
private var header: some View { private var header: some View {
if shouldShowSenderDetails { if shouldShowSenderDetails {
HStack { HStack(spacing: 8) {
HStack { HStack(spacing: 8) {
TimelineSenderAvatarView(timelineItem: timelineItem) TimelineSenderAvatarView(timelineItem: timelineItem)
Text(timelineItem.sender.displayName ?? timelineItem.sender.id) Text(timelineItem.sender.displayName ?? timelineItem.sender.id)
.font(.subheadline) .font(.subheadline)
@ -123,7 +119,7 @@ struct TimelineItemPlainStylerView<Content: View>: View {
@ViewBuilder @ViewBuilder
private var supplementaryViews: some View { private var supplementaryViews: some View {
VStack { VStack(spacing: 4) {
if timelineItem.properties.isEdited { if timelineItem.properties.isEdited {
Text(L10n.commonEditedSuffix) Text(L10n.commonEditedSuffix)
.font(.compound.bodySM) .font(.compound.bodySM)
@ -251,8 +247,9 @@ struct TimelineItemPlainStylerView_Previews: PreviewProvider, TestablePreview {
} }
} }
.environment(\.timelineStyle, .plain) .environment(\.timelineStyle, .plain)
.previewLayout(.sizeThatFits)
.environmentObject(viewModel.context) .environmentObject(viewModel.context)
.previewLayout(.sizeThatFits)
threads threads
.padding() .padding()
.environment(\.timelineStyle, .plain) .environment(\.timelineStyle, .plain)

View File

@ -145,7 +145,7 @@ struct TimelineItemStyler_Previews: PreviewProvider, TestablePreview {
content: .init(body: "באמת‏! -- house!")) content: .init(body: "באמת‏! -- house!"))
static var testView: some View { static var testView: some View {
VStack { VStack(spacing: 0) {
TextRoomTimelineView(timelineItem: base) TextRoomTimelineView(timelineItem: base)
TextRoomTimelineView(timelineItem: sentNonLast) TextRoomTimelineView(timelineItem: sentNonLast)
TextRoomTimelineView(timelineItem: sentLast) TextRoomTimelineView(timelineItem: sentLast)
@ -156,7 +156,7 @@ struct TimelineItemStyler_Previews: PreviewProvider, TestablePreview {
} }
static var languagesTestView: some View { static var languagesTestView: some View {
VStack { VStack(spacing: 0) {
TextRoomTimelineView(timelineItem: ltrString) TextRoomTimelineView(timelineItem: ltrString)
TextRoomTimelineView(timelineItem: rtlString) TextRoomTimelineView(timelineItem: rtlString)
TextRoomTimelineView(timelineItem: ltrStringThatContainsRtl) TextRoomTimelineView(timelineItem: ltrStringThatContainsRtl)

View File

@ -67,7 +67,7 @@ struct ReactionsSummaryView: View {
TabView(selection: $selectedReactionKey) { TabView(selection: $selectedReactionKey) {
ForEach(reactions, id: \.self) { reaction in ForEach(reactions, id: \.self) { reaction in
ScrollView { ScrollView {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 8) {
ForEach(reaction.senders, id: \.self) { sender in ForEach(reaction.senders, id: \.self) { sender in
ReactionSummarySenderView(sender: sender, member: members[sender.senderID], imageProvider: imageProvider) ReactionSummarySenderView(sender: sender, member: members[sender.senderID], imageProvider: imageProvider)
.padding(.horizontal, 16) .padding(.horizontal, 16)
@ -122,15 +122,15 @@ private struct ReactionSummarySenderView: View {
} }
var body: some View { var body: some View {
HStack { HStack(spacing: 8) {
LoadableAvatarImage(url: member?.avatarURL, LoadableAvatarImage(url: member?.avatarURL,
name: displayName, name: displayName,
contentID: sender.senderID, contentID: sender.senderID,
avatarSize: .user(on: .timeline), avatarSize: .user(on: .timeline),
imageProvider: imageProvider) imageProvider: imageProvider)
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 0) {
HStack { HStack(spacing: 8) {
Text(displayName) Text(displayName)
.font(.compound.bodyMDSemibold) .font(.compound.bodyMDSemibold)
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)

View File

@ -42,7 +42,7 @@ struct TimelineDeliveryStatusView: View {
struct TimelineDeliveryStatusView_Previews: PreviewProvider, TestablePreview { struct TimelineDeliveryStatusView_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
VStack { VStack(spacing: 8) {
TimelineDeliveryStatusView(deliveryStatus: .sending) TimelineDeliveryStatusView(deliveryStatus: .sending)
TimelineDeliveryStatusView(deliveryStatus: .sent) TimelineDeliveryStatusView(deliveryStatus: .sent)
} }

View File

@ -205,7 +205,7 @@ struct TimelineReactionAddMoreButtonLabel: View {
struct TimelineReactionViewPreviewsContainer: View { struct TimelineReactionViewPreviewsContainer: View {
var body: some View { var body: some View {
VStack { VStack(spacing: 8) {
TimelineReactionsView(context: RoomScreenViewModel.mock.context, TimelineReactionsView(context: RoomScreenViewModel.mock.context,
itemID: .init(timelineID: "1"), itemID: .init(timelineID: "1"),
reactions: [AggregatedReaction.mockReactionWithLongText, reactions: [AggregatedReaction.mockReactionWithLongText,

View File

@ -92,7 +92,7 @@ struct TimelineReadReceiptsView_Previews: PreviewProvider, TestablePreview {
} }
static var previews: some View { static var previews: some View {
VStack { VStack(spacing: 8) {
TimelineReadReceiptsView(timelineItem: mockTimelineItem(with: singleReceipt)) TimelineReadReceiptsView(timelineItem: mockTimelineItem(with: singleReceipt))
.environmentObject(viewModel.context) .environmentObject(viewModel.context)
TimelineReadReceiptsView(timelineItem: mockTimelineItem(with: doubleReceipt)) TimelineReadReceiptsView(timelineItem: mockTimelineItem(with: doubleReceipt))

View File

@ -119,7 +119,7 @@ struct SwipeRightAction_Previews: PreviewProvider, TestablePreview {
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ScrollView { ScrollView {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 2) {
mockBubble("This is a message from somebody with a couple of lines of text.") mockBubble("This is a message from somebody with a couple of lines of text.")
.swipeRightAction { .swipeRightAction {
Image(systemName: "flame") Image(systemName: "flame")

View File

@ -22,7 +22,7 @@ struct AudioRoomTimelineView: View {
var body: some View { var body: some View {
TimelineStyler(timelineItem: timelineItem) { TimelineStyler(timelineItem: timelineItem) {
HStack { HStack(spacing: 8) {
Image(systemName: "waveform") Image(systemName: "waveform")
.foregroundColor(.compound.iconPrimary) .foregroundColor(.compound.iconPrimary)
FormattedBodyText(text: timelineItem.content.body) FormattedBodyText(text: timelineItem.content.body)

View File

@ -34,7 +34,7 @@ struct CollapsibleRoomTimelineView: View {
isExpanded.toggle() isExpanded.toggle()
} }
} label: { } label: {
HStack(alignment: .center) { HStack(alignment: .center, spacing: 8) {
Text(L10n.roomTimelineStateChanges(timelineItem.items.count)) Text(L10n.roomTimelineStateChanges(timelineItem.items.count))
Text(Image(systemName: "chevron.forward")) Text(Image(systemName: "chevron.forward"))
.rotationEffect(.degrees(isExpanded ? 90 : 0)) .rotationEffect(.degrees(isExpanded ? 90 : 0))

View File

@ -60,7 +60,7 @@ private struct EncryptedHistoryLabelStyle: LabelStyle {
struct EncryptedHistoryRoomTimelineView_Previews: PreviewProvider, TestablePreview { struct EncryptedHistoryRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
VStack { VStack(spacing: 8) {
EncryptedHistoryRoomTimelineView(timelineItem: .init(id: .random, isSessionVerified: true)) EncryptedHistoryRoomTimelineView(timelineItem: .init(id: .random, isSessionVerified: true))
EncryptedHistoryRoomTimelineView(timelineItem: .init(id: .random, isSessionVerified: false)) EncryptedHistoryRoomTimelineView(timelineItem: .init(id: .random, isSessionVerified: false))
} }

View File

@ -22,7 +22,7 @@ struct FileRoomTimelineView: View {
var body: some View { var body: some View {
TimelineStyler(timelineItem: timelineItem) { TimelineStyler(timelineItem: timelineItem) {
HStack { HStack(spacing: 8) {
Image(systemName: "doc.text.fill") Image(systemName: "doc.text.fill")
.foregroundColor(.compound.iconPrimary) .foregroundColor(.compound.iconPrimary)
FormattedBodyText(text: timelineItem.content.body) FormattedBodyText(text: timelineItem.content.body)

View File

@ -87,7 +87,7 @@ struct LocationRoomTimelineView_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
ScrollView { ScrollView {
VStack { VStack(spacing: 8) {
states states
.padding(.horizontal) .padding(.horizontal)
} }
@ -96,7 +96,7 @@ struct LocationRoomTimelineView_Previews: PreviewProvider, TestablePreview {
.previewDisplayName("Bubbles") .previewDisplayName("Bubbles")
ScrollView { ScrollView {
VStack { VStack(spacing: 0) {
states states
.padding(.horizontal) .padding(.horizontal)
} }

View File

@ -26,7 +26,7 @@ struct PollOptionView: View {
FormRowAccessory(kind: .multipleSelection(isSelected: pollOption.isSelected)) FormRowAccessory(kind: .multipleSelection(isSelected: pollOption.isSelected))
VStack(spacing: 10) { VStack(spacing: 10) {
HStack(alignment: .lastTextBaseline) { HStack(alignment: .lastTextBaseline, spacing: 8) {
Text(pollOption.text) Text(pollOption.text)
.font(isFinalWinningOption ? .compound.bodyLGSemibold : .compound.bodyLG) .font(isFinalWinningOption ? .compound.bodyLGSemibold : .compound.bodyLG)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
@ -82,7 +82,7 @@ private struct PollProgressView: View {
struct PollOptionView_Previews: PreviewProvider, TestablePreview { struct PollOptionView_Previews: PreviewProvider, TestablePreview {
static var previews: some View { static var previews: some View {
VStack { VStack(spacing: 8) {
Group { Group {
PollOptionView(pollOption: .init(id: "1", PollOptionView(pollOption: .init(id: "1",
text: "Italian 🇮🇹", text: "Italian 🇮🇹",

View File

@ -22,7 +22,7 @@ struct UnsupportedRoomTimelineView: View {
var body: some View { var body: some View {
TimelineStyler(timelineItem: timelineItem) { TimelineStyler(timelineItem: timelineItem) {
Label { Label {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 0) {
Text("\(timelineItem.body): \(timelineItem.eventType)") Text("\(timelineItem.body): \(timelineItem.eventType)")
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)

View File

@ -24,7 +24,7 @@ struct TimelineItemDebugView: View {
var body: some View { var body: some View {
NavigationStack { NavigationStack {
ScrollView { ScrollView {
VStack { VStack(spacing: 8) {
TimelineItemInfoDisclosureGroup(title: "Model", text: info.model, isInitiallyExpanded: true) TimelineItemInfoDisclosureGroup(title: "Model", text: info.model, isInitiallyExpanded: true)
if let originalJSONInfo = info.originalJSON { if let originalJSONInfo = info.originalJSON {
@ -83,10 +83,9 @@ struct TimelineItemDebugView: View {
@ViewBuilder @ViewBuilder
var disclosureGroupContent: some View { var disclosureGroupContent: some View {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 0) {
Spacer()
Divider() Divider()
.padding(.vertical, 8)
Text(text) Text(text)
.font(.compound.bodyXS.monospaced()) .font(.compound.bodyXS.monospaced())

View File

@ -142,7 +142,7 @@ public struct TimelineItemMenu: View {
private let feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy) private let feedbackGenerator = UIImpactFeedbackGenerator(style: .heavy)
public var body: some View { public var body: some View {
VStack { VStack(spacing: 8) {
header header
.frame(idealWidth: 300.0) .frame(idealWidth: 300.0)
@ -187,7 +187,7 @@ public struct TimelineItemMenu: View {
Spacer(minLength: 8.0) Spacer(minLength: 8.0)
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 0) {
Text(item.sender.displayName ?? item.sender.id) Text(item.sender.displayName ?? item.sender.id)
.font(.compound.bodySMSemibold) .font(.compound.bodySMSemibold)
.foregroundColor(.compound.textPrimary) .foregroundColor(.compound.textPrimary)
@ -211,7 +211,7 @@ public struct TimelineItemMenu: View {
} }
private var reactionsSection: some View { private var reactionsSection: some View {
HStack(alignment: .center) { HStack(alignment: .center, spacing: 8) {
reactionButton(for: "👍️") reactionButton(for: "👍️")
reactionButton(for: "👎️") reactionButton(for: "👎️")
reactionButton(for: "🔥") reactionButton(for: "🔥")
@ -286,12 +286,10 @@ struct TimelineItemMenu_Previews: PreviewProvider, TestablePreview {
static let viewModel = RoomScreenViewModel.mock static let viewModel = RoomScreenViewModel.mock
static var previews: some View { static var previews: some View {
VStack { if let item = RoomTimelineItemFixtures.singleMessageChunk.first as? EventBasedTimelineItemProtocol,
if let item = RoomTimelineItemFixtures.singleMessageChunk.first as? EventBasedTimelineItemProtocol, let actions = TimelineItemMenuActions(actions: [.copy, .edit, .reply(isThread: false), .redact], debugActions: [.viewSource]) {
let actions = TimelineItemMenuActions(actions: [.copy, .edit, .reply(isThread: false), .redact], debugActions: [.viewSource]) { TimelineItemMenu(item: item, actions: actions)
TimelineItemMenu(item: item, actions: actions) .environmentObject(viewModel.context)
}
} }
.environmentObject(viewModel.context)
} }
} }

View File

@ -52,7 +52,7 @@ struct SecureBackupKeyBackupScreen: View {
.font(.compound.bodyMD) .font(.compound.bodyMD)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 10) {
Label { Label {
Text(L10n.screenKeyBackupDisableDescriptionPoint1) Text(L10n.screenKeyBackupDisableDescriptionPoint1)
.foregroundColor(.compound.textSecondary) .foregroundColor(.compound.textSecondary)

View File

@ -121,19 +121,19 @@ struct SecureBackupRecoveryKeyScreen: View {
} }
private var generateRecoveryKeySection: some View { private var generateRecoveryKeySection: some View {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 8) {
Text(L10n.commonRecoveryKey) Text(L10n.commonRecoveryKey)
.foregroundColor(.compound.textPrimary) .foregroundColor(.compound.textPrimary)
.font(.compound.bodySM) .font(.compound.bodySM)
HStack { Group {
if context.viewState.recoveryKey == nil { if context.viewState.recoveryKey == nil {
Button(generateButtonTitle) { Button(generateButtonTitle) {
context.send(viewAction: .generateKey) context.send(viewAction: .generateKey)
} }
.font(.compound.bodyLGSemibold) .font(.compound.bodyLGSemibold)
} else { } else {
HStack(alignment: .top) { HStack(alignment: .top, spacing: 8) {
Text(context.viewState.recoveryKey ?? "") Text(context.viewState.recoveryKey ?? "")
.foregroundColor(.compound.textPrimary) .foregroundColor(.compound.textPrimary)
.font(.compound.bodyLG) .font(.compound.bodyLG)
@ -155,15 +155,16 @@ struct SecureBackupRecoveryKeyScreen: View {
.background(Color.compound.bgSubtleSecondaryLevel0) .background(Color.compound.bgSubtleSecondaryLevel0)
.clipShape(RoundedRectangle(cornerRadius: 8)) .clipShape(RoundedRectangle(cornerRadius: 8))
HStack(alignment: .top) { Label {
if context.viewState.recoveryKey == nil {
CompoundIcon(\.infoSolid, size: .small, relativeTo: .compound.bodySM)
}
Text(context.viewState.recoveryKeySubtitle) Text(context.viewState.recoveryKeySubtitle)
.foregroundColor(.compound.textSecondary) .foregroundColor(.compound.textSecondary)
.font(.compound.bodySM) .font(.compound.bodySM)
} icon: {
if context.viewState.recoveryKey == nil {
CompoundIcon(\.infoSolid, size: .small, relativeTo: .compound.bodySM)
}
} }
.labelStyle(.custom(spacing: 8, alignment: .top))
} }
} }
@ -173,7 +174,7 @@ struct SecureBackupRecoveryKeyScreen: View {
@ViewBuilder @ViewBuilder
private var confirmRecoveryKeySection: some View { private var confirmRecoveryKeySection: some View {
VStack(alignment: .leading) { VStack(alignment: .leading, spacing: 8) {
Text(L10n.commonRecoveryKey) Text(L10n.commonRecoveryKey)
.foregroundColor(.compound.textPrimary) .foregroundColor(.compound.textPrimary)
.font(.compound.bodySM) .font(.compound.bodySM)

View File

@ -196,7 +196,7 @@ struct SettingsScreen: View {
}) })
.accessibilityIdentifier(A11yIdentifiers.settingsScreen.logout) .accessibilityIdentifier(A11yIdentifiers.settingsScreen.logout)
} footer: { } footer: {
VStack { VStack(spacing: 0) {
versionText versionText
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)

View File

@ -41,18 +41,16 @@ struct VoiceMessageRoomPlaybackView: View {
} }
var body: some View { var body: some View {
HStack { HStack(spacing: 8) {
HStack { VoiceMessageButton(state: .init(playerState.playerButtonPlaybackState),
VoiceMessageButton(state: .init(playerState.playerButtonPlaybackState), size: .medium,
size: .medium, action: onPlayPause)
action: onPlayPause) Text(timeLabelContent)
Text(timeLabelContent) .lineLimit(1)
.lineLimit(1) .font(.compound.bodySMSemibold)
.font(.compound.bodySMSemibold) .foregroundColor(.compound.textSecondary)
.foregroundColor(.compound.textSecondary) .monospacedDigit()
.monospacedDigit() .fixedSize(horizontal: true, vertical: true)
.fixedSize(horizontal: true, vertical: true)
}
waveformView waveformView
.waveformInteraction(isDragging: $isDragging, .waveformInteraction(isDragging: $isDragging,

View File

@ -58,7 +58,8 @@ class CreatePollScreenUITests: XCTestCase {
if app.keyboards.count > 0 { if app.keyboards.count > 0 {
app.typeText("\n") app.typeText("\n")
} }
app.swipeUp() app.swipeUp() // Dismisses the keyboard.
app.swipeUp() // Ensures that the bottom is shown.
XCTAssertFalse(addOption.exists) XCTAssertFalse(addOption.exists)
XCTAssertFalse(createButton.isEnabled) XCTAssertFalse(createButton.isEnabled)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More