mirror of
https://github.com/ProtonMail/ios-mail.git
synced 2026-05-15 09:50:39 +00:00
AttachmentsView improvements
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user