From 8c07b9e4cbd5bc5ecf2f18c9df67d46229273346 Mon Sep 17 00:00:00 2001 From: Xavi Gil Date: Thu, 7 Mar 2024 14:14:06 +0100 Subject: [PATCH] AttachmentsView improvements --- ios/UI/Views/AttachmentsView.swift | 66 ++++++++++++++--------- ios/Utils/SwiftUI/View+removeViewIf.swift | 28 ++++++++++ 2 files changed, 68 insertions(+), 26 deletions(-) diff --git a/ios/UI/Views/AttachmentsView.swift b/ios/UI/Views/AttachmentsView.swift index 9511080dd0..785852aba9 100644 --- a/ios/UI/Views/AttachmentsView.swift +++ b/ios/UI/Views/AttachmentsView.swift @@ -19,8 +19,15 @@ import DesignSystem import SwiftUI struct AttachmentsView: View { + @Environment(\.horizontalSizeClass) var horizontalSizeClass + let uiModel: [AttachmentCapsuleUIModel] let onTapEvent: ((String) -> Void)? + + /// Maximum number of attachment capsules to try to show for each horizontal size class + private var maxNumberOfCapsules: CGFloat { + horizontalSizeClass == .compact ? 3 : 5 + } init(uiModel: [AttachmentCapsuleUIModel], onTapEvent: ((String) -> Void)? = nil) { self.uiModel = uiModel @@ -29,39 +36,40 @@ struct AttachmentsView: View { var body: some View { GeometryReader { geometry in - let maxCapsuleWidth = geometry.size.width/2 + let spaceForCapsules = geometry.size.width + - (maxNumberOfCapsules*Layout.spacingBetweenCapsules) - Layout.extraAttachmentsViewWidth + let capsuleMaxWidth = uiModel.count == 1 ? spaceForCapsules : spaceForCapsules/CGFloat(maxNumberOfCapsules) /** - We tried different approaches to compute the view but SwiftUI does not make it easy to - implement the design specs. We also tried this ViewThatFits solution limiting the total number - of preview attachments to a maximum of 3, but the scrolling was stuttering. - Work on a performant solution that allows to show a dynamic number of attachments + SwiftUI does not make it easy to calculate dynamically to fit the maximum number of capsules. After trying + different approaches to compute the view, we end up with setting a maximum number of potential attachments and + use `ViewThatFits` to decide which one to render. We default to just showing 1 if no other limit works. */ -// ViewThatFits(in: .horizontal) { -// hStackWithAttachments(limit: 3, capsuleMaxWidth: maxCapsuleWidth) -// hStackWithAttachments(limit: 2, capsuleMaxWidth: maxCapsuleWidth) - hStackWithAttachments(limit: 1, capsuleMaxWidth: maxCapsuleWidth) -// } + ViewThatFits(in: .horizontal) { + hStackWithAttachments(limit: Int(maxNumberOfCapsules), capsuleMaxWidth: capsuleMaxWidth) + hStackWithAttachments(limit: Int(maxNumberOfCapsules) - 1, capsuleMaxWidth: capsuleMaxWidth) + hStackWithAttachments(limit: 1, capsuleMaxWidth: spaceForCapsules) + } } .frame(height: 32) } func hStackWithAttachments(limit: Int, capsuleMaxWidth: CGFloat) -> some View { - HStack { - HStack { + HStack(spacing: 0) { + HStack(spacing: Layout.spacingBetweenCapsules) { let items = uiModel.prefix(limit) ForEach(items) { item in AttachmentCapsuleView(uiModel: item, maxWidth: capsuleMaxWidth, onTapEvent: onTapEvent) } } - let extraAttachments = uiModel.count - limit + let extraAttachments = min(99, uiModel.count - limit) Text("+\(extraAttachments)") + .frame(width: Layout.extraAttachmentsViewWidth) .fixedSize() .font(.caption2) .fontWeight(.regular) .foregroundStyle(DS.Color.textWeak) .padding(.trailing, 10) - .layoutPriority(1) .removeViewIf(extraAttachments < 1 ) } } @@ -81,19 +89,20 @@ struct AttachmentCapsuleView: View { let maxWidth: CGFloat let onTapEvent: ((String) -> Void)? - private let iconSide: CGFloat = 14.0 - private let padding = EdgeInsets(top: 8.0, leading: 12.0, bottom: 8.0, trailing: 12.0) + private let padding = EdgeInsets( + top: 8.0, leading: Layout.capsuleHPadding, bottom: 8.0, trailing: Layout.capsuleHPadding + ) var body: some View { Button(action: { onTapEvent?(uiModel.attachmentId) }) { - HStack(spacing: 4) { + HStack(spacing: Layout.capsuleSpacing) { Image(uiImage: uiModel.icon) .resizable() .renderingMode(.original) .aspectRatio(contentMode: .fill) - .frame(width: iconSide, height: iconSide) + .frame(width: Layout.capsuleIconSideSize, height: Layout.capsuleIconSideSize) Text(uiModel.name) .font(.caption2) .fontWeight(.regular) @@ -125,16 +134,23 @@ private struct AttachmentCapsuleStyle: ButtonStyle { } +fileprivate enum Layout { + static let spacingBetweenCapsules = 10.0 + static let extraAttachmentsViewWidth = 22.0 + static let capsuleHPadding = 12.0 + static let capsuleIconSideSize = 14.0 + static let capsuleSpacing = 4.0 +} #Preview { VStack { AttachmentsView( uiModel:[ - .init(attachmentId: "1", icon: DS.Icon.icFileTypeIconPdf, name: "1.pdf") + .init(attachmentId: "1", icon: DS.Icon.icFileTypePages, name: "single_attachment_super_long_title_that_goes_beyond_the_half_width_of_a_big_iphone_in_landscape.pdf") ] ) - .frame(width: 300) .border(.red) + AttachmentsView( uiModel:[ .init(attachmentId: "1", icon: DS.Icon.icFileTypeIconPdf, name: "1.pdf"), @@ -145,19 +161,17 @@ private struct AttachmentCapsuleStyle: ButtonStyle { .init(attachmentId: "6", icon: DS.Icon.icFileTypeIconWord, name: "6.pdf"), .init(attachmentId: "7", icon: DS.Icon.icFileTypeIconCode, name: "7.png"), .init(attachmentId: "8", icon: DS.Icon.icFileTypeIconWord, name: "8.xls"), - .init(attachmentId: "9", icon: DS.Icon.icFileTypeIconCode, name: "9.doc"), - .init(attachmentId: "10", icon: DS.Icon.icFileTypeIconCode, name: "10.bash"), ] ) - .frame(width: 300) .border(.red) + AttachmentsView( uiModel:[ .init(attachmentId: "1", icon: DS.Icon.icFileTypeIconPdf, name: "super_long_title_that_goes_beyond_half.pdf"), .init(attachmentId: "2", icon: DS.Icon.icFileTypeIconImage, name: "quite.png"), - .init(attachmentId: "3", icon: DS.Icon.icFileTypeIconExcel, name: "3.xls"), - .init(attachmentId: "4", icon: DS.Icon.icFileTypeIconWord, name: "4.doc"), - .init(attachmentId: "5", icon: DS.Icon.icFileTypeIconCode, name: "5.bash"), + .init(attachmentId: "3", icon: DS.Icon.icFileTypeIconExcel, name: "numebrs.xls"), + .init(attachmentId: "4", icon: DS.Icon.icFileTypeIconWord, name: "words.doc"), + .init(attachmentId: "5", icon: DS.Icon.icFileTypeIconCode, name: "scripts.bash"), ] ) .frame(width: 300) diff --git a/ios/Utils/SwiftUI/View+removeViewIf.swift b/ios/Utils/SwiftUI/View+removeViewIf.swift index 0103dbbe68..c7ad797a25 100644 --- a/ios/Utils/SwiftUI/View+removeViewIf.swift +++ b/ios/Utils/SwiftUI/View+removeViewIf.swift @@ -39,3 +39,31 @@ extension View { } } } + +extension View { + func debugOverlaySize() -> some View { + modifier(DebugOverlaySize()) + } +} + +struct DebugOverlaySize: ViewModifier { + func body(content: Content) -> some View { + content + .overlay { + GeometryReader { proxy in + Text( + "\(proxy.size.width) x \(proxy.size.height)" + ) + .font(.caption) + .foregroundStyle(.white) + .padding(4) + .background { Color.purple } + .border(.black) + .fixedSize() + .frame(width: proxy.size.width, + height: proxy.size.height) + } + } + } +} +