AttachmentsView improvements

This commit is contained in:
Xavi Gil
2024-03-07 14:14:06 +01:00
parent 6589c4eac0
commit 8c07b9e4cb
2 changed files with 68 additions and 26 deletions
+40 -26
View File
@@ -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)
+28
View File
@@ -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)
}
}
}
}