mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-12-23 15:40:37 +00:00
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:
parent
7f689bbb9c
commit
586e4f525e
1 changed files with 142 additions and 139 deletions
|
@ -9,11 +9,7 @@ import SwiftUI
|
|||
@MainActor
|
||||
public struct StatusRowMediaPreviewView: View {
|
||||
@Environment(\.openWindow) private var openWindow
|
||||
@Environment(\.extraLeadingInset) private var extraLeadingInset: CGFloat
|
||||
@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(Theme.self) private var theme
|
||||
|
||||
|
@ -35,33 +31,6 @@ public struct StatusRowMediaPreviewView: View {
|
|||
private var scrollBottomPadding: CGFloat? = 0
|
||||
#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 {
|
||||
if isCompact {
|
||||
return 50
|
||||
|
@ -80,11 +49,10 @@ public struct StatusRowMediaPreviewView: View {
|
|||
if attachments.count == 1 {
|
||||
FeaturedImagePreView(
|
||||
attachment: attachments[0],
|
||||
imageMaxHeight: imageMaxHeight,
|
||||
sensitive: sensitive,
|
||||
appLayoutWidth: appLayoutWidth,
|
||||
availableWidth: availableWidth,
|
||||
availableHeight: sceneDelegate.windowHeight
|
||||
maxSize: imageMaxHeight == 300
|
||||
? nil
|
||||
: CGSize(width: imageMaxHeight, height: imageMaxHeight),
|
||||
sensitive: sensitive
|
||||
)
|
||||
.accessibilityElement(children: .ignore)
|
||||
.accessibilityLabel(Self.accessibilityLabel(for: attachments[0]))
|
||||
|
@ -152,11 +120,8 @@ private struct MediaPreview: View {
|
|||
let imageMaxHeight: CGFloat
|
||||
let displayData: DisplayData
|
||||
|
||||
@Environment(UserPreferences.self) private var preferences
|
||||
@Environment(\.isCompact) private var isCompact: Bool
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { _ in
|
||||
Group {
|
||||
switch displayData.type {
|
||||
case .image:
|
||||
LazyResizableImage(url: displayData.previewUrl) { state, _ in
|
||||
|
@ -167,11 +132,11 @@ private struct MediaPreview: View {
|
|||
.frame(width: displayData.isLandscape ? imageMaxHeight * 1.2 : imageMaxHeight / 1.5,
|
||||
height: imageMaxHeight)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(.gray.opacity(0.35), lineWidth: 1)
|
||||
)
|
||||
} else if state.isLoading {
|
||||
RoundedRectangle(cornerRadius: 4)
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(Color.gray)
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +154,7 @@ private struct MediaPreview: View {
|
|||
.frame(width: displayData.isLandscape ? imageMaxHeight * 1.2 : imageMaxHeight / 1.5,
|
||||
height: imageMaxHeight)
|
||||
.clipped()
|
||||
.cornerRadius(4)
|
||||
.cornerRadius(10)
|
||||
// #965: do not create overlapping tappable areas, when multiple images are shown
|
||||
.contentShape(Rectangle())
|
||||
.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
|
||||
struct BlurOverLay: View {
|
||||
let sensitive: Bool
|
||||
|
@ -517,3 +386,137 @@ struct WrapperForPreview: View {
|
|||
private static let attachment = MediaAttachment.imageWith(url: url)
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue