Various optimizations to views & images rendering

This commit is contained in:
Thomas Ricouard 2023-02-17 18:17:51 +01:00
parent 881816730c
commit f09781582f
18 changed files with 125 additions and 74 deletions

View File

@ -135,9 +135,7 @@ struct IceCubesApp: App {
userPreferences.showiPadSecondaryColumn userPreferences.showiPadSecondaryColumn
{ {
Divider().edgesIgnoringSafeArea(.all) Divider().edgesIgnoringSafeArea(.all)
NotificationsTab(popToRootTab: $popToRootTab, lockedType: nil) notificationsSecondaryColumn
.environment(\.isSecondaryColumn, true)
.frame(maxWidth: 360)
} }
} }
} }
@ -145,6 +143,13 @@ struct IceCubesApp: App {
sideBarLoadedTabs.removeAll() sideBarLoadedTabs.removeAll()
} }
} }
private var notificationsSecondaryColumn: some View {
NotificationsTab(popToRootTab: $popToRootTab, lockedType: nil)
.environment(\.isSecondaryColumn, true)
.frame(maxWidth: 360)
.id(appAccountsManager.currentAccount.id)
}
private var tabBarView: some View { private var tabBarView: some View {
TabView(selection: .init(get: { TabView(selection: .init(get: {

View File

@ -55,9 +55,10 @@ public struct AvatarView: View {
.frame(width: size.size.width, height: size.size.height) .frame(width: size.size.width, height: size.size.height)
} else { } else {
LazyImage(url: url) { state in LazyImage(url: url) { state in
if let image = state.image { if let image = state.imageContainer?.image {
image SwiftUI.Image(uiImage: image)
.resizingMode(.aspectFit) .resizable()
.aspectRatio(contentMode: .fit)
} else { } else {
placeholderView placeholderView
} }

View File

@ -5,9 +5,27 @@ private struct SecondaryColumnKey: EnvironmentKey {
static let defaultValue = false static let defaultValue = false
} }
private struct ExtraLeadingInset: EnvironmentKey {
static let defaultValue: CGFloat = 0
}
private struct IsCompact: EnvironmentKey {
static let defaultValue: Bool = false
}
public extension EnvironmentValues { public extension EnvironmentValues {
var isSecondaryColumn: Bool { var isSecondaryColumn: Bool {
get { self[SecondaryColumnKey.self] } get { self[SecondaryColumnKey.self] }
set { self[SecondaryColumnKey.self] = newValue } set { self[SecondaryColumnKey.self] = newValue }
} }
var extraLeadingInset: CGFloat {
get { self[ExtraLeadingInset.self] }
set { self[ExtraLeadingInset.self] = newValue }
}
var isCompact: Bool {
get { self[IsCompact.self] }
set { self[IsCompact.self] = newValue }
}
} }

View File

@ -81,7 +81,7 @@ public struct ExploreView: View {
private var loadingView: some View { private var loadingView: some View {
ForEach(Status.placeholders()) { status in ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath, isCompact: false)) StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath))
.padding(.vertical, 8) .padding(.vertical, 8)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
@ -184,7 +184,7 @@ public struct ExploreView: View {
Section("explore.section.trending.posts") { Section("explore.section.trending.posts") {
ForEach(viewModel.trendingStatuses ForEach(viewModel.trendingStatuses
.prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count)) { status in .prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count)) { status in
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath, isCompact: false)) StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath))
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
.padding(.vertical, 8) .padding(.vertical, 8)
} }
@ -192,7 +192,7 @@ public struct ExploreView: View {
NavigationLink { NavigationLink {
List { List {
ForEach(viewModel.trendingStatuses) { status in ForEach(viewModel.trendingStatuses) { status in
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath, isCompact: false)) StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath))
.listRowBackground(theme.primaryBackgroundColor) .listRowBackground(theme.primaryBackgroundColor)
.padding(.vertical, 8) .padding(.vertical, 8)
} }

View File

@ -6,24 +6,31 @@
// //
import Models import Models
import Foundation
extension Array where Element == Notification { extension Array where Element == Models.Notification {
func consolidated(selectedType: Notification.NotificationType?) -> [ConsolidatedNotification] { func consolidated(selectedType: Models.Notification.NotificationType?) async -> [ConsolidatedNotification] {
Dictionary(grouping: self) { $0.consolidationId(selectedType: selectedType) } await withCheckedContinuation({ result in
.values DispatchQueue.global().async {
.compactMap { notifications in let notifications: [ConsolidatedNotification] =
guard let notification = notifications.first, Dictionary(grouping: self) { $0.consolidationId(selectedType: selectedType) }
let supportedType = notification.supportedType .values
else { return nil } .compactMap { notifications in
guard let notification = notifications.first,
let supportedType = notification.supportedType
else { return nil }
return ConsolidatedNotification(notifications: notifications, return ConsolidatedNotification(notifications: notifications,
type: supportedType, type: supportedType,
createdAt: notification.createdAt, createdAt: notification.createdAt,
accounts: notifications.map(\.account), accounts: notifications.map(\.account),
status: notification.status) status: notification.status)
} }
.sorted { .sorted {
$0.createdAt.asDate > $1.createdAt.asDate $0.createdAt.asDate > $1.createdAt.asDate
}
result.resume(returning: notifications)
} }
})
} }
} }

View File

@ -7,13 +7,13 @@ import SwiftUI
import Network import Network
struct NotificationRowView: View { struct NotificationRowView: View {
@EnvironmentObject private var currentAccount: CurrentAccount
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@Environment(\.redactionReasons) private var reasons @Environment(\.redactionReasons) private var reasons
let notification: ConsolidatedNotification let notification: ConsolidatedNotification
let client: Client let client: Client
let routerPath: RouterPath let routerPath: RouterPath
let followRequests: [Account]
var body: some View { var body: some View {
HStack(alignment: .top, spacing: 8) { HStack(alignment: .top, spacing: 8) {
@ -28,8 +28,7 @@ struct NotificationRowView: View {
makeMainLabel(type: notification.type) makeMainLabel(type: notification.type)
makeContent(type: notification.type) makeContent(type: notification.type)
if notification.type == .follow_request, if notification.type == .follow_request,
currentAccount.followRequests.map(\.id).contains(notification.accounts[0].id) followRequests.map(\.id).contains(notification.accounts[0].id) {
{
FollowRequestButtons(account: notification.accounts[0]) FollowRequestButtons(account: notification.accounts[0])
} }
} }
@ -137,19 +136,18 @@ struct NotificationRowView: View {
StatusRowView(viewModel: .init(status: status, StatusRowView(viewModel: .init(status: status,
client: client, client: client,
routerPath: routerPath, routerPath: routerPath,
isCompact: true,
showActions: true)) showActions: true))
} else { } else {
StatusRowView(viewModel: .init(status: status, StatusRowView(viewModel: .init(status: status,
client: client, client: client,
routerPath: routerPath, routerPath: routerPath,
isCompact: true,
showActions: false)) showActions: false))
.lineLimit(4) .lineLimit(4)
.foregroundColor(.gray) .foregroundColor(.gray)
} }
Spacer() Spacer()
} }
.environment(\.isCompact, true)
} else { } else {
Group { Group {
Text("@\(notification.accounts[0].acct)") Text("@\(notification.accounts[0].acct)")

View File

@ -84,7 +84,10 @@ public struct NotificationsListView: View {
switch viewModel.state { switch viewModel.state {
case .loading: case .loading:
ForEach(ConsolidatedNotification.placeholders()) { notification in ForEach(ConsolidatedNotification.placeholders()) { notification in
NotificationRowView(notification: notification, client: client, routerPath: routerPath) NotificationRowView(notification: notification,
client: client,
routerPath: routerPath,
followRequests: account.followRequests)
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
.listRowInsets(.init(top: 12, .listRowInsets(.init(top: 12,
leading: .layoutPadding + 4, leading: .layoutPadding + 4,
@ -103,13 +106,17 @@ public struct NotificationsListView: View {
.listSectionSeparator(.hidden) .listSectionSeparator(.hidden)
} else { } else {
ForEach(notifications) { notification in ForEach(notifications) { notification in
NotificationRowView(notification: notification, client: client, routerPath: routerPath) NotificationRowView(notification: notification,
client: client,
routerPath: routerPath,
followRequests: account.followRequests)
.listRowInsets(.init(top: 12, .listRowInsets(.init(top: 12,
leading: .layoutPadding + 4, leading: .layoutPadding + 4,
bottom: 12, bottom: 12,
trailing: .layoutPadding)) trailing: .layoutPadding))
.listRowBackground(notification.type == .mention && lockedType != .mention ? .listRowBackground(notification.type == .mention && lockedType != .mention ?
theme.secondaryBackgroundColor : theme.primaryBackgroundColor) theme.secondaryBackgroundColor : theme.primaryBackgroundColor)
.id(notification.id)
} }
} }

View File

@ -69,7 +69,7 @@ class NotificationsViewModel: ObservableObject {
maxId: nil, maxId: nil,
types: queryTypes, types: queryTypes,
limit: Constants.notificationLimit)) limit: Constants.notificationLimit))
consolidatedNotifications = notifications.consolidated(selectedType: selectedType) consolidatedNotifications = await notifications.consolidated(selectedType: selectedType)
nextPageState = notifications.count < Constants.notificationLimit ? .none : .hasNextPage nextPageState = notifications.count < Constants.notificationLimit ? .none : .hasNextPage
} else if let firstId = consolidatedNotifications.first?.id { } else if let firstId = consolidatedNotifications.first?.id {
var newNotifications: [Models.Notification] = await fetchNewPages(minId: firstId, maxPages: 10) var newNotifications: [Models.Notification] = await fetchNewPages(minId: firstId, maxPages: 10)
@ -77,13 +77,15 @@ class NotificationsViewModel: ObservableObject {
newNotifications = newNotifications.filter { notification in newNotifications = newNotifications.filter { notification in
!consolidatedNotifications.contains(where: { $0.id == notification.id }) !consolidatedNotifications.contains(where: { $0.id == notification.id })
} }
consolidatedNotifications.insert( await consolidatedNotifications.insert(
contentsOf: newNotifications.consolidated(selectedType: selectedType), contentsOf: newNotifications.consolidated(selectedType: selectedType),
at: 0 at: 0
) )
} }
await currentAccount.fetchFollowerRequests() if consolidatedNotifications.contains(where: { $0.type == .follow_request }) {
await currentAccount.fetchFollowerRequests()
}
withAnimation { withAnimation {
state = .display(notifications: consolidatedNotifications, state = .display(notifications: consolidatedNotifications,
@ -129,8 +131,10 @@ class NotificationsViewModel: ObservableObject {
maxId: lastId, maxId: lastId,
types: queryTypes, types: queryTypes,
limit: Constants.notificationLimit)) limit: Constants.notificationLimit))
consolidatedNotifications.append(contentsOf: newNotifications.consolidated(selectedType: selectedType)) await consolidatedNotifications.append(contentsOf: newNotifications.consolidated(selectedType: selectedType))
await currentAccount?.fetchFollowerRequests() if consolidatedNotifications.contains(where: { $0.type == .follow_request }) {
await currentAccount?.fetchFollowerRequests()
}
state = .display(notifications: consolidatedNotifications, state = .display(notifications: consolidatedNotifications,
nextPageState: newNotifications.count < Constants.notificationLimit ? .none : .hasNextPage) nextPageState: newNotifications.count < Constants.notificationLimit ? .none : .hasNextPage)
} catch { } catch {
@ -159,14 +163,14 @@ class NotificationsViewModel: ObservableObject {
{ {
// If the notification type can be consolidated, try to consolidate with the latest row // If the notification type can be consolidated, try to consolidate with the latest row
let latestConsolidatedNotification = consolidatedNotifications.removeFirst() let latestConsolidatedNotification = consolidatedNotifications.removeFirst()
consolidatedNotifications.insert( await consolidatedNotifications.insert(
contentsOf: ([event.notification] + latestConsolidatedNotification.notifications) contentsOf: ([event.notification] + latestConsolidatedNotification.notifications)
.consolidated(selectedType: selectedType), .consolidated(selectedType: selectedType),
at: 0 at: 0
) )
} else { } else {
// Otherwise, just insert the new notification // Otherwise, just insert the new notification
consolidatedNotifications.insert( await consolidatedNotifications.insert(
contentsOf: [event.notification].consolidated(selectedType: selectedType), contentsOf: [event.notification].consolidated(selectedType: selectedType),
at: 0 at: 0
) )

View File

@ -104,18 +104,20 @@ public struct StatusDetailView: View {
} }
let viewModel: StatusRowViewModel = .init(status: status, let viewModel: StatusRowViewModel = .init(status: status,
client: client, client: client,
routerPath: routerPath, routerPath: routerPath)
isCompact: false) return HStack(spacing: 0) {
return HStack {
if isReplyToPrevious { if isReplyToPrevious {
Rectangle() Rectangle()
.fill(theme.tintColor) .fill(theme.tintColor)
.frame(width: 2) .frame(width: 2)
Spacer(minLength: 8)
} }
if self.viewModel.statusId == status.id { if self.viewModel.statusId == status.id {
makeCurrentStatusView(status: status) makeCurrentStatusView(status: status)
.environment(\.extraLeadingInset, isReplyToPrevious ? 10 : 0)
} else { } else {
StatusRowView(viewModel: viewModel) StatusRowView(viewModel: viewModel)
.environment(\.extraLeadingInset, isReplyToPrevious ? 10 : 0)
} }
} }
.listRowBackground(viewModel.highlightRowColor) .listRowBackground(viewModel.highlightRowColor)
@ -130,7 +132,6 @@ public struct StatusDetailView: View {
StatusRowView(viewModel: .init(status: status, StatusRowView(viewModel: .init(status: status,
client: client, client: client,
routerPath: routerPath, routerPath: routerPath,
isCompact: false,
isFocused: !viewModel.isLoadingContext)) isFocused: !viewModel.isLoadingContext))
.overlay { .overlay {
GeometryReader { reader in GeometryReader { reader in
@ -157,7 +158,7 @@ public struct StatusDetailView: View {
private var loadingDetailView: some View { private var loadingDetailView: some View {
ForEach(Status.placeholders()) { status in ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath, isCompact: false)) StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath))
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
} }
} }

View File

@ -5,7 +5,7 @@ import Models
import NukeUI import NukeUI
import SwiftUI import SwiftUI
struct StatusEditorMediaView: View { struct StatusEditorMediaView: View {
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@EnvironmentObject private var currentInstance: CurrentInstance @EnvironmentObject private var currentInstance: CurrentInstance
@ObservedObject var viewModel: StatusEditorViewModel @ObservedObject var viewModel: StatusEditorViewModel

View File

@ -26,8 +26,8 @@ public struct StatusEmbeddedView: View {
StatusRowView(viewModel: .init(status: status, StatusRowView(viewModel: .init(status: status,
client: client, client: client,
routerPath: routerPath, routerPath: routerPath,
isCompact: true,
showActions: false)) showActions: false))
.environment(\.isCompact, true)
} }
Spacer() Spacer()
} }

View File

@ -24,7 +24,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
switch fetcher.statusesState { switch fetcher.statusesState {
case .loading: case .loading:
ForEach(Status.placeholders()) { status in ForEach(Status.placeholders()) { status in
StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath, isCompact: false)) StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath))
.redacted(reason: .placeholder) .redacted(reason: .placeholder)
} }
case .error: case .error:
@ -40,7 +40,7 @@ public struct StatusesListView<Fetcher>: View where Fetcher: StatusesFetcher {
case let .display(statuses, nextPageState): case let .display(statuses, nextPageState):
ForEach(statuses, id: \.viewId) { status in ForEach(statuses, id: \.viewId) { status in
let viewModel = StatusRowViewModel(status: status, client: client, routerPath: routerPath, isCompact: false, isRemote: isRemote) let viewModel = StatusRowViewModel(status: status, client: client, routerPath: routerPath, isRemote: isRemote)
if viewModel.filter?.filter.filterAction != .hide { if viewModel.filter?.filter.filterAction != .hide {
StatusRowView(viewModel: viewModel) StatusRowView(viewModel: viewModel)
.id(status.id) .id(status.id)

View File

@ -54,6 +54,7 @@ struct VideoPlayerView: View {
}.onAppear { }.onAppear {
viewModel.preparePlayer(autoPlay: preferences.autoPlayVideo) viewModel.preparePlayer(autoPlay: preferences.autoPlayVideo)
} }
.cornerRadius(4)
.onChange(of: scenePhase, perform: { scenePhase in .onChange(of: scenePhase, perform: { scenePhase in
switch scenePhase { switch scenePhase {
case .background, .inactive: case .background, .inactive:

View File

@ -8,6 +8,7 @@ import SwiftUI
public struct StatusRowView: View { public struct StatusRowView: View {
@Environment(\.redactionReasons) private var reasons @Environment(\.redactionReasons) private var reasons
@Environment(\.isCompact) private var isCompact: Bool
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
@ -34,12 +35,12 @@ public struct StatusRowView: View {
} else { } else {
let status: AnyStatus = viewModel.status.reblog ?? viewModel.status let status: AnyStatus = viewModel.status.reblog ?? viewModel.status
VStack(alignment: .leading) { VStack(alignment: .leading) {
if !viewModel.isCompact, theme.avatarPosition == .leading { if !isCompact, theme.avatarPosition == .leading {
StatusRowReblogView(viewModel: viewModel) StatusRowReblogView(viewModel: viewModel)
StatusRowReplyView(viewModel: viewModel) StatusRowReplyView(viewModel: viewModel)
} }
HStack(alignment: .top, spacing: .statusColumnsSpacing) { HStack(alignment: .top, spacing: .statusColumnsSpacing) {
if !viewModel.isCompact, if !isCompact,
theme.avatarPosition == .leading { theme.avatarPosition == .leading {
Button { Button {
viewModel.routerPath.navigate(to: .accountDetailWithAccount(account: status.account)) viewModel.routerPath.navigate(to: .accountDetailWithAccount(account: status.account))
@ -48,13 +49,13 @@ public struct StatusRowView: View {
} }
} }
VStack(alignment: .leading) { VStack(alignment: .leading) {
if !viewModel.isCompact, theme.avatarPosition == .top { if !isCompact, theme.avatarPosition == .top {
StatusRowReblogView(viewModel: viewModel) StatusRowReblogView(viewModel: viewModel)
StatusRowReplyView(viewModel: viewModel) StatusRowReplyView(viewModel: viewModel)
} }
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
let status: AnyStatus = viewModel.status.reblog ?? viewModel.status let status: AnyStatus = viewModel.status.reblog ?? viewModel.status
if !viewModel.isCompact { if !isCompact {
StatusRowHeaderView(status: status, viewModel: viewModel) StatusRowHeaderView(status: status, viewModel: viewModel)
} }
StatusRowContentView(status: status, viewModel: viewModel) StatusRowContentView(status: status, viewModel: viewModel)
@ -82,7 +83,7 @@ public struct StatusRowView: View {
.onAppear { .onAppear {
viewModel.markSeen() viewModel.markSeen()
if reasons.isEmpty { if reasons.isEmpty {
if !viewModel.isCompact, viewModel.embeddedStatus == nil { if !isCompact, viewModel.embeddedStatus == nil {
Task { Task {
await viewModel.loadEmbeddedStatus() await viewModel.loadEmbeddedStatus()
} }
@ -93,12 +94,12 @@ public struct StatusRowView: View {
contextMenu contextMenu
} }
.swipeActions(edge: .trailing) { .swipeActions(edge: .trailing) {
if !viewModel.isCompact { if !isCompact {
StatusRowSwipeView(viewModel: viewModel, mode: .trailing) StatusRowSwipeView(viewModel: viewModel, mode: .trailing)
} }
} }
.swipeActions(edge: .leading) { .swipeActions(edge: .leading) {
if !viewModel.isCompact { if !isCompact {
StatusRowSwipeView(viewModel: viewModel, mode: .leading) StatusRowSwipeView(viewModel: viewModel, mode: .leading)
} }
} }

View File

@ -9,7 +9,6 @@ import DesignSystem
@MainActor @MainActor
public class StatusRowViewModel: ObservableObject { public class StatusRowViewModel: ObservableObject {
let status: Status let status: Status
let isCompact: Bool
let isFocused: Bool let isFocused: Bool
let isRemote: Bool let isRemote: Bool
let showActions: Bool let showActions: Bool
@ -61,7 +60,6 @@ public class StatusRowViewModel: ObservableObject {
public init(status: Status, public init(status: Status,
client: Client, client: Client,
routerPath: RouterPath, routerPath: RouterPath,
isCompact: Bool = false,
isFocused: Bool = false, isFocused: Bool = false,
isRemote: Bool = false, isRemote: Bool = false,
showActions: Bool = true) showActions: Bool = true)
@ -69,7 +67,6 @@ public class StatusRowViewModel: ObservableObject {
self.status = status self.status = status
self.client = client self.client = client
self.routerPath = routerPath self.routerPath = routerPath
self.isCompact = isCompact
self.isFocused = isFocused self.isFocused = isFocused
self.isRemote = isRemote self.isRemote = isRemote
self.showActions = showActions self.showActions = showActions

View File

@ -18,9 +18,12 @@ public struct StatusRowCardView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
if let imageURL = card.image { if let imageURL = card.image {
LazyImage(url: imageURL) { state in LazyImage(url: imageURL) { state in
if let image = state.image { if let image = state.imageContainer?.image {
image SwiftUI.Image(uiImage: image)
.resizingMode(.aspectFill) .resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 200)
.clipped()
} else if state.isLoading { } else if state.isLoading {
Rectangle() Rectangle()
.fill(Color.gray) .fill(Color.gray)

View File

@ -5,6 +5,8 @@ import Env
struct StatusRowContentView: View { struct StatusRowContentView: View {
@Environment(\.redactionReasons) private var reasons @Environment(\.redactionReasons) private var reasons
@Environment(\.isCompact) private var isCompact
@EnvironmentObject private var theme: Theme @EnvironmentObject private var theme: Theme
let status: AnyStatus let status: AnyStatus
@ -23,7 +25,7 @@ struct StatusRowContentView: View {
} }
if !reasons.contains(.placeholder), if !reasons.contains(.placeholder),
!viewModel.isCompact, !isCompact,
(viewModel.isEmbedLoading || viewModel.embeddedStatus != nil) { (viewModel.isEmbedLoading || viewModel.embeddedStatus != nil) {
StatusEmbeddedView(status: viewModel.embeddedStatus ?? Status.placeholder(), StatusEmbeddedView(status: viewModel.embeddedStatus ?? Status.placeholder(),
client: viewModel.client, client: viewModel.client,
@ -31,13 +33,14 @@ struct StatusRowContentView: View {
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.redacted(reason: viewModel.isEmbedLoading ? .placeholder : []) .redacted(reason: viewModel.isEmbedLoading ? .placeholder : [])
.shimmering(active: viewModel.isEmbedLoading) .shimmering(active: viewModel.isEmbedLoading)
.transition(.opacity)
} }
if !status.mediaAttachments.isEmpty { if !status.mediaAttachments.isEmpty {
HStack { HStack {
StatusRowMediaPreviewView(attachments: status.mediaAttachments, StatusRowMediaPreviewView(attachments: status.mediaAttachments,
sensitive: status.sensitive, sensitive: status.sensitive,
isNotifications: viewModel.isCompact) isNotifications: isCompact)
if theme.statusDisplayStyle == .compact { if theme.statusDisplayStyle == .compact {
Spacer() Spacer()
} }
@ -47,7 +50,7 @@ struct StatusRowContentView: View {
if let card = status.card, if let card = status.card,
!viewModel.isEmbedLoading, !viewModel.isEmbedLoading,
!viewModel.isCompact, !isCompact,
theme.statusDisplayStyle == .large, theme.statusDisplayStyle == .large,
status.content.statusesURLs.isEmpty, status.content.statusesURLs.isEmpty,
status.mediaAttachments.isEmpty status.mediaAttachments.isEmpty

View File

@ -8,6 +8,7 @@ import SwiftUI
public struct StatusRowMediaPreviewView: View { public struct StatusRowMediaPreviewView: View {
@Environment(\.openURL) private var openURL @Environment(\.openURL) private var openURL
@Environment(\.isSecondaryColumn) private var isSecondaryColumn @Environment(\.isSecondaryColumn) private var isSecondaryColumn
@Environment(\.extraLeadingInset) private var extraLeadingInset: CGFloat
@EnvironmentObject var sceneDelegate: SceneDelegate @EnvironmentObject var sceneDelegate: SceneDelegate
@EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var preferences: UserPreferences
@ -37,7 +38,7 @@ public struct StatusRowMediaPreviewView: View {
if UIDevice.current.userInterfaceIdiom == .pad && sceneDelegate.windowWidth < (.maxColumnWidth + .sidebarWidth) { if UIDevice.current.userInterfaceIdiom == .pad && sceneDelegate.windowWidth < (.maxColumnWidth + .sidebarWidth) {
sidebarWidth = .sidebarWidth sidebarWidth = .sidebarWidth
} }
return (.layoutPadding * 2) + avatarColumnWidth + sidebarWidth return (.layoutPadding * 2) + avatarColumnWidth + sidebarWidth + extraLeadingInset
} }
private var imageMaxHeight: CGFloat { private var imageMaxHeight: CGFloat {
@ -152,11 +153,13 @@ public struct StatusRowMediaPreviewView: View {
switch attachment.supportedType { switch attachment.supportedType {
case .image: case .image:
LazyImage(url: attachment.url) { state in LazyImage(url: attachment.url) { state in
if let image = state.image { if let image = state.imageContainer?.image {
image SwiftUI.Image(uiImage: image)
.resizingMode(.aspectFill) .resizable()
.cornerRadius(4) .aspectRatio(contentMode: .fill)
.frame(width: newSize.width, height: newSize.height) .frame(width: newSize.width, height: newSize.height)
.clipped()
.cornerRadius(4)
} else { } else {
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: 4)
.fill(Color.gray) .fill(Color.gray)
@ -204,9 +207,13 @@ public struct StatusRowMediaPreviewView: View {
case .image: case .image:
ZStack(alignment: .bottomTrailing) { ZStack(alignment: .bottomTrailing) {
LazyImage(url: attachment.url) { state in LazyImage(url: attachment.url) { state in
if let image = state.image { if let image = state.imageContainer?.image {
image SwiftUI.Image(uiImage: image)
.resizingMode(.aspectFill) .resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: isNotifications ? imageMaxHeight : proxy.frame(in: .local).width)
.frame(maxHeight: imageMaxHeight)
.clipped()
.cornerRadius(4) .cornerRadius(4)
} else if state.isLoading { } else if state.isLoading {
RoundedRectangle(cornerRadius: 4) RoundedRectangle(cornerRadius: 4)
@ -215,8 +222,6 @@ public struct StatusRowMediaPreviewView: View {
.frame(maxWidth: isNotifications ? imageMaxHeight : proxy.frame(in: .local).width) .frame(maxWidth: isNotifications ? imageMaxHeight : proxy.frame(in: .local).width)
} }
} }
.frame(maxWidth: isNotifications ? imageMaxHeight : proxy.frame(in: .local).width)
.frame(height: imageMaxHeight)
if sensitive { if sensitive {
cornerSensitiveButton cornerSensitiveButton
} }