mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2024-11-22 16:31:00 +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
|
@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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue