Fix timeline media size (#1928)

* fix layout for post with 1 media item

* fix corner radius

* Fixes

---------

Co-authored-by: Thomas Ricouard <ricouard77@gmail.com>
This commit is contained in:
Thai D. V 2024-01-28 23:32:16 +07:00 committed by GitHub
parent 7f689bbb9c
commit 586e4f525e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -9,11 +9,7 @@ import SwiftUI
@MainActor @MainActor
public struct StatusRowMediaPreviewView: View { public struct StatusRowMediaPreviewView: View {
@Environment(\.openWindow) private var openWindow @Environment(\.openWindow) private var openWindow
@Environment(\.extraLeadingInset) private var extraLeadingInset: CGFloat
@Environment(\.isMediaCompact) private var isCompact: Bool @Environment(\.isMediaCompact) private var isCompact: Bool
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
@Environment(SceneDelegate.self) private var sceneDelegate
@Environment(UserPreferences.self) private var preferences
@Environment(QuickLook.self) private var quickLook @Environment(QuickLook.self) private var quickLook
@Environment(Theme.self) private var theme @Environment(Theme.self) private var theme
@ -35,33 +31,6 @@ public struct StatusRowMediaPreviewView: View {
private var scrollBottomPadding: CGFloat? = 0 private var scrollBottomPadding: CGFloat? = 0
#endif #endif
var availableWidth: CGFloat {
#if os(visionOS)
return sceneDelegate.windowWidth * 0.96
#else
if UIDevice.current.userInterfaceIdiom == .phone &&
(UIDevice.current.orientation == .landscapeLeft || UIDevice.current.orientation == .landscapeRight) || theme.statusDisplayStyle == .medium
{
return sceneDelegate.windowWidth * 0.80
}
return sceneDelegate.windowWidth
#endif
}
var appLayoutWidth: CGFloat {
let avatarColumnWidth = theme.avatarPosition == .leading ? AvatarView.FrameConfig.status.width + .statusColumnsSpacing : 0
var sidebarWidth: CGFloat = 0
var secondaryColumnWidth: CGFloat = 0
let layoutPading: CGFloat = .layoutPadding * 2
if UIDevice.current.userInterfaceIdiom == .pad && horizontalSizeClass != .compact {
sidebarWidth = .sidebarWidth
if preferences.showiPadSecondaryColumn {
secondaryColumnWidth = .secondaryColumnWidth
}
}
return layoutPading + avatarColumnWidth + sidebarWidth + extraLeadingInset + secondaryColumnWidth
}
private var imageMaxHeight: CGFloat { private var imageMaxHeight: CGFloat {
if isCompact { if isCompact {
return 50 return 50
@ -80,11 +49,10 @@ public struct StatusRowMediaPreviewView: View {
if attachments.count == 1 { if attachments.count == 1 {
FeaturedImagePreView( FeaturedImagePreView(
attachment: attachments[0], attachment: attachments[0],
imageMaxHeight: imageMaxHeight, maxSize: imageMaxHeight == 300
sensitive: sensitive, ? nil
appLayoutWidth: appLayoutWidth, : CGSize(width: imageMaxHeight, height: imageMaxHeight),
availableWidth: availableWidth, sensitive: sensitive
availableHeight: sceneDelegate.windowHeight
) )
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
.accessibilityLabel(Self.accessibilityLabel(for: attachments[0])) .accessibilityLabel(Self.accessibilityLabel(for: attachments[0]))
@ -152,11 +120,8 @@ private struct MediaPreview: View {
let imageMaxHeight: CGFloat let imageMaxHeight: CGFloat
let displayData: DisplayData let displayData: DisplayData
@Environment(UserPreferences.self) private var preferences
@Environment(\.isCompact) private var isCompact: Bool
var body: some View { var body: some View {
GeometryReader { _ in Group {
switch displayData.type { switch displayData.type {
case .image: case .image:
LazyResizableImage(url: displayData.previewUrl) { state, _ in LazyResizableImage(url: displayData.previewUrl) { state, _ in
@ -167,11 +132,11 @@ private struct MediaPreview: View {
.frame(width: displayData.isLandscape ? imageMaxHeight * 1.2 : imageMaxHeight / 1.5, .frame(width: displayData.isLandscape ? imageMaxHeight * 1.2 : imageMaxHeight / 1.5,
height: imageMaxHeight) height: imageMaxHeight)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: 10)
.stroke(.gray.opacity(0.35), lineWidth: 1) .stroke(.gray.opacity(0.35), lineWidth: 1)
) )
} else if state.isLoading { } else if state.isLoading {
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: 10)
.fill(Color.gray) .fill(Color.gray)
} }
} }
@ -189,7 +154,7 @@ private struct MediaPreview: View {
.frame(width: displayData.isLandscape ? imageMaxHeight * 1.2 : imageMaxHeight / 1.5, .frame(width: displayData.isLandscape ? imageMaxHeight * 1.2 : imageMaxHeight / 1.5,
height: imageMaxHeight) height: imageMaxHeight)
.clipped() .clipped()
.cornerRadius(4) .cornerRadius(10)
// #965: do not create overlapping tappable areas, when multiple images are shown // #965: do not create overlapping tappable areas, when multiple images are shown
.contentShape(Rectangle()) .contentShape(Rectangle())
.accessibilityElement(children: .ignore) .accessibilityElement(children: .ignore)
@ -198,102 +163,6 @@ private struct MediaPreview: View {
} }
} }
@MainActor
private struct FeaturedImagePreView: View {
let attachment: MediaAttachment
let imageMaxHeight: CGFloat
let sensitive: Bool
let appLayoutWidth: CGFloat
let availableWidth: CGFloat
let availableHeight: CGFloat
@Environment(\.isSecondaryColumn) private var isSecondaryColumn: Bool
@Environment(Theme.self) private var theme
@Environment(\.isCompact) private var isCompact: Bool
@Environment(\.isModal) private var isModal: Bool
var body: some View {
let size: CGSize = size(for: attachment) ?? .init(width: imageMaxHeight, height: imageMaxHeight)
let newSize = imageSize(from: size)
Group {
switch attachment.supportedType {
case .image:
LazyImage(url: attachment.url) { state in
if let image = state.image {
image
.resizable()
.aspectRatio(contentMode: .fill)
.overlay(
RoundedRectangle(cornerRadius: 4)
.stroke(.gray.opacity(0.35), lineWidth: 1)
)
} else {
RoundedRectangle(cornerRadius: 4).fill(Color.gray)
}
}
.processors([.resize(size: newSize)])
case .gifv, .video, .audio:
if let url = attachment.url {
MediaUIAttachmentVideoView(viewModel: .init(url: url))
}
case .none:
EmptyView()
}
}
.frame(width: newSize.width, height: newSize.height)
.overlay {
BlurOverLay(sensitive: sensitive, font: .scaledFootnote)
}
.overlay {
AltTextButton(
text: attachment.description,
font: theme.statusDisplayStyle == .compact ? .footnote : .body
)
}
.clipped()
.cornerRadius(4)
}
private func size(for media: MediaAttachment) -> CGSize? {
guard let width = media.meta?.original?.width,
let height = media.meta?.original?.height
else { return nil }
guard width != 1 && height != 1 else {
return .init(width: 800, height: 600)
}
return .init(width: CGFloat(width), height: CGFloat(height))
}
private func imageSize(from: CGSize) -> CGSize {
if isCompact || theme.statusDisplayStyle == .compact || isSecondaryColumn {
return .init(width: imageMaxHeight, height: imageMaxHeight)
}
var boxWidth = availableWidth - appLayoutWidth
if isModal &&
(UIDevice.current.userInterfaceIdiom == .pad || UIDevice.current.userInterfaceIdiom == .mac) {
boxWidth = availableWidth * 0.50
}
let boxHeight = availableHeight * 0.8 // use only 80% of window height to leave room for text
if from.width <= boxWidth, from.height <= boxHeight {
// intrinsic size of image fits just fine
return from
}
// shrink image proportionally to fit inside the box
let xRatio = boxWidth / from.width
let yRatio = boxHeight / from.height
if xRatio < yRatio {
return .init(width: boxWidth, height: from.height * xRatio)
} else {
return .init(width: from.width * yRatio, height: boxHeight)
}
}
}
@MainActor @MainActor
struct BlurOverLay: View { struct BlurOverLay: View {
let sensitive: Bool let sensitive: Bool
@ -517,3 +386,137 @@ struct WrapperForPreview: View {
private static let attachment = MediaAttachment.imageWith(url: url) private static let attachment = MediaAttachment.imageWith(url: url)
private static let local = Locale(identifier: "en") private static let local = Locale(identifier: "en")
} }
@MainActor
private struct FeaturedImagePreView: View {
let attachment: MediaAttachment
let maxSize: CGSize?
let sensitive: Bool
@Environment(\.isSecondaryColumn) private var isSecondaryColumn: Bool
@Environment(Theme.self) private var theme
@Environment(\.isCompact) private var isCompact: Bool
@Environment(\.isModal) private var isModal: Bool
private var originalWidth: CGFloat {
CGFloat(attachment.meta?.original?.width ?? 300)
}
private var originalHeight: CGFloat {
CGFloat(attachment.meta?.original?.height ?? 300)
}
var body: some View {
if let url = attachment.url {
_Layout(originalWidth: originalWidth, originalHeight: originalHeight, maxSize: maxSize) {
Group {
RoundedRectangle(cornerRadius: 10).fill(Color.gray)
.overlay {
switch attachment.supportedType {
case .image:
LazyResizableImage(url: attachment.url) { state, _ in
if let image = state.image {
image
.resizable()
.scaledToFill()
} else {
RoundedRectangle(cornerRadius: 10).fill(Color.gray)
}
}
case .gifv, .video, .audio:
MediaUIAttachmentVideoView(viewModel: .init(url: url))
default:
EmptyView()
}
}
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(.gray.opacity(0.35), lineWidth: 1)
)
}
}
.overlay {
BlurOverLay(sensitive: sensitive, font: .scaledFootnote)
}
.overlay {
AltTextButton(
text: attachment.description,
font: theme.statusDisplayStyle == .compact ? .footnote : .body
)
}
.clipped()
.cornerRadius(10)
}
}
private struct _Layout: Layout {
let originalWidth: CGFloat
let originalHeight: CGFloat
let maxSize: CGSize?
init(originalWidth: CGFloat?, originalHeight: CGFloat?, maxSize: CGSize?) {
self.originalWidth = originalWidth ?? 200
self.originalHeight = originalHeight ?? 200
self.maxSize = maxSize
}
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
guard !subviews.isEmpty else { return CGSize.zero }
if let maxSize { return maxSize }
return calculateSize(proposal)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
guard let view = subviews.first else { return }
let size = if let maxSize { maxSize } else { calculateSize(proposal) }
view.place(at: bounds.origin, proposal: ProposedViewSize(size))
}
private func calculateSize(_ proposal: ProposedViewSize) -> CGSize {
var size: CGSize
switch (proposal.width, proposal.height) {
case (0, _), (_, 0):
size = CGSize.zero
case (nil, nil), (nil, .some(.infinity)), (.some(.infinity), .some(.infinity)), (.some(.infinity), nil):
size = CGSize(width: originalWidth, height: originalWidth)
case let (nil, .some(height)), let (.some(.infinity), .some(height)):
let minHeight = min(height, originalWidth)
if originalHeight == 0 {
size = CGSize.zero
} else {
size = CGSize(width: originalWidth * minHeight / originalHeight, height: minHeight)
}
case let (.some(width), .some(.infinity)), let (.some(width), nil):
if originalWidth == 0 {
size = CGSize(width: width, height: width)
} else {
size = CGSize(width: width, height: width / originalWidth * originalHeight)
}
case let (.some(width), .some(height)):
// intrinsic size of image fits just fine
if originalWidth <= width, originalHeight <= height {
size = CGSize(width: originalWidth, height: originalHeight)
}
// shrink image proportionally to fit inside the box
let xRatio = width / originalWidth
let yRatio = height / originalHeight
// use small ratio to fit the image in
if xRatio < yRatio {
size = CGSize(width: width, height: originalHeight * xRatio)
} else {
size = CGSize(width: originalWidth * yRatio, height: height)
}
}
return CGSize(width: max(size.width, 200), height: min(size.height, 450))
}
}
}