mirror of
https://github.com/Dimillian/IceCubesApp.git
synced 2025-02-03 16:07:31 +01:00
Various optimizations to views & images rendering
This commit is contained in:
parent
881816730c
commit
f09781582f
@ -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: {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)")
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user