diff --git a/IceCubesApp.xcodeproj/project.pbxproj b/IceCubesApp.xcodeproj/project.pbxproj index 8fc19370..d7c6aecd 100644 --- a/IceCubesApp.xcodeproj/project.pbxproj +++ b/IceCubesApp.xcodeproj/project.pbxproj @@ -17,6 +17,8 @@ 9F398AA92935FFDB00A889F2 /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AA82935FFDB00A889F2 /* Account */; }; 9F398AAB2935FFDB00A889F2 /* Models in Frameworks */ = {isa = PBXBuildFile; productRef = 9F398AAA2935FFDB00A889F2 /* Models */; }; 9F398AB329360A4C00A889F2 /* TimelineTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F398AB229360A4C00A889F2 /* TimelineTab.swift */; }; + 9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F55C68C2955968700F94077 /* ExploreTab.swift */; }; + 9F55C6902955993C00F94077 /* Explore in Frameworks */ = {isa = PBXBuildFile; productRef = 9F55C68F2955993C00F94077 /* Explore */; }; 9F5E581929545BE700A53960 /* Env in Frameworks */ = {isa = PBXBuildFile; productRef = 9F5E581829545BE700A53960 /* Env */; }; 9FAE4ACB293783B000772766 /* SettingsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FAE4ACA293783B000772766 /* SettingsTab.swift */; }; 9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 9FAE4ACD29379A5A00772766 /* KeychainSwift */; }; @@ -41,6 +43,8 @@ 9F398AA52935FE8A00A889F2 /* AppRouteur.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouteur.swift; sourceTree = ""; }; 9F398AAC2936005300A889F2 /* Account */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Account; path = Packages/Account; sourceTree = ""; }; 9F398AB229360A4C00A889F2 /* TimelineTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTab.swift; sourceTree = ""; }; + 9F55C68C2955968700F94077 /* ExploreTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreTab.swift; sourceTree = ""; }; + 9F55C68E295598F900F94077 /* Explore */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Explore; path = Packages/Explore; sourceTree = ""; }; 9F5E581729545B5500A53960 /* Env */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Env; path = Packages/Env; sourceTree = ""; }; 9FAE4AC8293774FF00772766 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 9FAE4ACA293783B000772766 /* SettingsTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = ""; }; @@ -58,6 +62,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9F55C6902955993C00F94077 /* Explore in Frameworks */, 9FAE4ACE29379A5A00772766 /* KeychainSwift in Frameworks */, 9F398AA92935FFDB00A889F2 /* Account in Frameworks */, 9FBFE64E292A72BD00C250E9 /* Network in Frameworks */, @@ -98,6 +103,7 @@ 9F398AB229360A4C00A889F2 /* TimelineTab.swift */, 9F35DB4629506F6600B3281A /* NotificationTab.swift */, 9F35DB4B2952005C00B3281A /* AccountTab.swift */, + 9F55C68C2955968700F94077 /* ExploreTab.swift */, ); path = Tabs; sourceTree = ""; @@ -119,6 +125,7 @@ 9FBFE64C292A72BD00C250E9 /* Frameworks */, 9F398AAC2936005300A889F2 /* Account */, 9F35DB45294FA04C00B3281A /* DesignSystem */, + 9F55C68E295598F900F94077 /* Explore */, 9F5E581729545B5500A53960 /* Env */, 9F398AA32935F90100A889F2 /* Models */, 9F29553D292B67B600E0E81B /* Network */, @@ -189,6 +196,7 @@ 9F35DB43294F9A7D00B3281A /* Status */, 9F35DB4929506FA100B3281A /* Notifications */, 9F5E581829545BE700A53960 /* Env */, + 9F55C68F2955993C00F94077 /* Explore */, ); productName = IceCubesApp; productReference = 9FBFE639292A715500C250E9 /* IceCubesApp.app */; @@ -256,6 +264,7 @@ 9FBFE63D292A715500C250E9 /* IceCubesApp.swift in Sources */, 9F35DB4729506F6600B3281A /* NotificationTab.swift in Sources */, 9FAE4AD129379AD600772766 /* AppAccount.swift in Sources */, + 9F55C68D2955968700F94077 /* ExploreTab.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -516,6 +525,10 @@ isa = XCSwiftPackageProductDependency; productName = Models; }; + 9F55C68F2955993C00F94077 /* Explore */ = { + isa = XCSwiftPackageProductDependency; + productName = Explore; + }; 9F5E581829545BE700A53960 /* Env */ = { isa = XCSwiftPackageProductDependency; productName = Env; diff --git a/IceCubesApp/App/AppRouteur.swift b/IceCubesApp/App/AppRouteur.swift index c4b02b11..857efa8a 100644 --- a/IceCubesApp/App/AppRouteur.swift +++ b/IceCubesApp/App/AppRouteur.swift @@ -24,8 +24,8 @@ extension View { func withSheetDestinations(sheetDestinations: Binding) -> some View { self.sheet(item: sheetDestinations) { destination in switch destination { - default: - EmptyView() + case .statusEditor: + StatusEditorView() } } } diff --git a/IceCubesApp/App/IceCubesApp.swift b/IceCubesApp/App/IceCubesApp.swift index 3bfc67d4..590745f3 100644 --- a/IceCubesApp/App/IceCubesApp.swift +++ b/IceCubesApp/App/IceCubesApp.swift @@ -11,19 +11,25 @@ struct IceCubesApp: App { @StateObject private var appAccountsManager = AppAccountsManager() @StateObject private var currentAccount = CurrentAccount() @StateObject private var quickLook = QuickLook() - + var body: some Scene { WindowGroup { TabView { TimelineTab() .tabItem { - Label("Home", systemImage: "globe") + Label("Timeline", systemImage: "rectangle.on.rectangle") } if appAccountsManager.currentClient.isAuth { NotificationsTab() .tabItem { Label("Notifications", systemImage: "bell") } + } + ExploreTab() + .tabItem { + Label("Explore", systemImage: "magnifyingglass") + } + if appAccountsManager.currentClient.isAuth { AccountTab() .tabItem { Label("Profile", systemImage: "person.circle") diff --git a/IceCubesApp/App/Tabs/ExploreTab.swift b/IceCubesApp/App/Tabs/ExploreTab.swift new file mode 100644 index 00000000..70604d9a --- /dev/null +++ b/IceCubesApp/App/Tabs/ExploreTab.swift @@ -0,0 +1,18 @@ +import SwiftUI +import Env +import Models +import Shimmer +import Explore + +struct ExploreTab: View { + @StateObject private var routeurPath = RouterPath() + + var body: some View { + NavigationStack(path: $routeurPath.path) { + ExploreView() + .withAppRouteur() + .withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet) + } + .environmentObject(routeurPath) + } +} diff --git a/IceCubesApp/App/Tabs/TimelineTab.swift b/IceCubesApp/App/Tabs/TimelineTab.swift index 2380f3b0..c656b57f 100644 --- a/IceCubesApp/App/Tabs/TimelineTab.swift +++ b/IceCubesApp/App/Tabs/TimelineTab.swift @@ -11,6 +11,16 @@ struct TimelineTab: View { TimelineView() .withAppRouteur() .withSheetDestinations(sheetDestinations: $routeurPath.presentedSheet) + .toolbar { + ToolbarItem(placement: .navigationBarLeading) { + Button { + routeurPath.presentedSheet = .statusEditor(replyToStatus: nil) + } label: { + Image(systemName: "square.and.pencil") + } + + } + } } .environmentObject(routeurPath) } diff --git a/Packages/Account/Sources/Account/AccountDetailHeaderView.swift b/Packages/Account/Sources/Account/AccountDetailHeaderView.swift index 07bc49d1..81d4a3f2 100644 --- a/Packages/Account/Sources/Account/AccountDetailHeaderView.swift +++ b/Packages/Account/Sources/Account/AccountDetailHeaderView.swift @@ -66,24 +66,11 @@ struct AccountDetailHeaderView: View { private var accountAvatarView: some View { HStack { - AsyncImage( - url: account.avatar, - content: { image in - image.resizable() - .aspectRatio(contentMode: .fit) - .cornerRadius(4) - .frame(maxWidth: 80, maxHeight: 80) - .overlay( - RoundedRectangle(cornerRadius: 4) - .stroke(.white, lineWidth: 1) - ) - }, - placeholder: { - ProgressView() - .frame(maxWidth: 80, maxHeight: 80) - } - ) - .contentShape(Rectangle()) + AvatarView(url: account.avatar, size: .account) + .overlay( + RoundedRectangle(cornerRadius: 4) + .stroke(.white, lineWidth: 1) + ) .onTapGesture { Task { await quickLook.prepareFor(urls: [account.avatar], selectedURL: account.avatar) diff --git a/Packages/Account/Sources/Account/AccountDetailView.swift b/Packages/Account/Sources/Account/AccountDetailView.swift index 4c6d99ce..649aef2b 100644 --- a/Packages/Account/Sources/Account/AccountDetailView.swift +++ b/Packages/Account/Sources/Account/AccountDetailView.swift @@ -77,7 +77,7 @@ public struct AccountDetailView: View { } } .edgesIgnoringSafeArea(.top) - .navigationTitle(Text(scrollOffset < -20 ? viewModel.title : "")) + .navigationTitle(Text(scrollOffset < -200 ? viewModel.title : "")) } @ViewBuilder @@ -205,21 +205,9 @@ public struct AccountDetailView: View { private func makeTagsListView(tags: [Tag]) -> some View { Group { ForEach(tags) { tag in - HStack { - VStack(alignment: .leading) { - Text("#\(tag.name)") - .font(.headline) - Text("\(tag.totalUses) posts from \(tag.totalAccounts) participants") - .font(.footnote) - .foregroundColor(.gray) - } - Spacer() - } - .padding(.horizontal, DS.Constants.layoutPadding) - .padding(.vertical, 8) - .onTapGesture { - routeurPath.navigate(to: .hashTag(tag: tag.name, account: nil)) - } + TagRowView(tag: tag) + .padding(.horizontal, DS.Constants.layoutPadding) + .padding(.vertical, 8) } } } diff --git a/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift b/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift index 1b73381d..ad41f5de 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/Views/AvatarView.swift @@ -3,30 +3,41 @@ import Shimmer public struct AvatarView: View { public enum Size { - case profile, badge + case account, status, badge var size: CGSize { switch self { - case .profile: + case .account: + return .init(width: 80, height: 80) + case .status: return .init(width: 40, height: 40) case .badge: return .init(width: 28, height: 28) } } + + var cornerRadius: CGFloat { + switch self { + case .badge: + return size.width / 2 + default: + return 4 + } + } } @Environment(\.redactionReasons) private var reasons public let url: URL public let size: Size - public init(url: URL, size: Size = .profile) { + public init(url: URL, size: Size = .status) { self.url = url self.size = size } public var body: some View { if reasons == .placeholder { - RoundedRectangle(cornerRadius: size == .profile ? 4 : size.size.width / 2) + RoundedRectangle(cornerRadius: size.cornerRadius) .fill(.gray) .frame(maxWidth: size.size.width, maxHeight: size.size.height) } else { @@ -39,7 +50,7 @@ public struct AvatarView: View { .frame(width: size.size.width, height: size.size.height) .shimmering() } else { - RoundedRectangle(cornerRadius: size == .profile ? 4 : size.size.width / 2) + RoundedRectangle(cornerRadius: size.cornerRadius) .fill(.gray) .frame(width: size.size.width, height: size.size.height) .shimmering() @@ -47,7 +58,7 @@ public struct AvatarView: View { case let .success(image): image.resizable() .aspectRatio(contentMode: .fit) - .cornerRadius(size == .profile ? 4 : size.size.width / 2) + .cornerRadius(size.cornerRadius) .frame(maxWidth: size.size.width, maxHeight: size.size.height) case .failure: EmptyView() diff --git a/Packages/DesignSystem/Sources/DesignSystem/Views/TagRowView.swift b/Packages/DesignSystem/Sources/DesignSystem/Views/TagRowView.swift new file mode 100644 index 00000000..97f30353 --- /dev/null +++ b/Packages/DesignSystem/Sources/DesignSystem/Views/TagRowView.swift @@ -0,0 +1,29 @@ +import Models +import SwiftUI +import Env + +public struct TagRowView: View { + @EnvironmentObject private var routeurPath: RouterPath + + let tag: Tag + + public init(tag: Tag) { + self.tag = tag + } + + public var body: some View { + HStack { + VStack(alignment: .leading) { + Text("#\(tag.name)") + .font(.headline) + Text("\(tag.totalUses) posts from \(tag.totalAccounts) participants") + .font(.footnote) + .foregroundColor(.gray) + } + Spacer() + } + .onTapGesture { + routeurPath.navigate(to: .hashTag(tag: tag.name, account: nil)) + } + } +} diff --git a/Packages/Env/Sources/Env/Routeur.swift b/Packages/Env/Sources/Env/Routeur.swift index 9ca7efaf..4848229e 100644 --- a/Packages/Env/Sources/Env/Routeur.swift +++ b/Packages/Env/Sources/Env/Routeur.swift @@ -10,10 +10,12 @@ public enum RouteurDestinations: Hashable { } public enum SheetDestinations: Identifiable { + case statusEditor(replyToStatus: String?) + public var id: String { switch self { - default: - break + case .statusEditor: + return "statusEditor" } } } diff --git a/Packages/Explore/.gitignore b/Packages/Explore/.gitignore new file mode 100644 index 00000000..3b298120 --- /dev/null +++ b/Packages/Explore/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +/.build +/Packages +/*.xcodeproj +xcuserdata/ +DerivedData/ +.swiftpm/config/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/Explore/Package.swift b/Packages/Explore/Package.swift new file mode 100644 index 00000000..e411ce40 --- /dev/null +++ b/Packages/Explore/Package.swift @@ -0,0 +1,35 @@ +// swift-tools-version: 5.7 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Explore", + platforms: [ + .iOS(.v16), + ], + products: [ + .library( + name: "Explore", + targets: ["Explore"]), + ], + dependencies: [ + .package(name: "Network", path: "../Network"), + .package(name: "Models", path: "../Models"), + .package(name: "Env", path: "../Env"), + .package(name: "Status", path: "../Status"), + .package(url: "https://github.com/markiv/SwiftUI-Shimmer", exact: "1.1.0") + ], + targets: [ + .target( + name: "Explore", + dependencies: [ + .product(name: "Network", package: "Network"), + .product(name: "Models", package: "Models"), + .product(name: "Env", package: "Env"), + .product(name: "Status", package: "Status"), + .product(name: "Shimmer", package: "SwiftUI-Shimmer") + ]) + ] +) + diff --git a/Packages/Explore/README.md b/Packages/Explore/README.md new file mode 100644 index 00000000..c3954d47 --- /dev/null +++ b/Packages/Explore/README.md @@ -0,0 +1,3 @@ +# Explore + +A description of this package. diff --git a/Packages/Explore/Sources/Explore/ExploreView.swift b/Packages/Explore/Sources/Explore/ExploreView.swift new file mode 100644 index 00000000..f34cebb1 --- /dev/null +++ b/Packages/Explore/Sources/Explore/ExploreView.swift @@ -0,0 +1,95 @@ +import SwiftUI +import Env +import Network +import DesignSystem +import Models +import Status + +public struct ExploreView: View { + @EnvironmentObject private var client: Client + @EnvironmentObject private var routeurPath: RouterPath + + @StateObject private var viewModel = ExploreViewModel() + @State private var searchQuery: String = "" + + public init() { } + + public var body: some View { + List { + Section("Trending Tags") { + ForEach(viewModel.trendingTags + .prefix(upTo: viewModel.trendingTags.count > 5 ? 5 : viewModel.trendingTags.count)) { tag in + TagRowView(tag: tag) + .padding(.vertical, 4) + } + NavigationLink { + List { + ForEach(viewModel.trendingTags) { tag in + TagRowView(tag: tag) + .padding(.vertical, 4) + } + } + .listStyle(.plain) + .navigationTitle("Trending Tags") + .navigationBarTitleDisplayMode(.inline) + } label: { + Text("See more") + .foregroundColor(.brand) + } + } + + Section("Trending Posts") { + ForEach(viewModel.trendingStatuses + .prefix(upTo: viewModel.trendingStatuses.count > 3 ? 3 : viewModel.trendingStatuses.count)) { status in + StatusRowView(viewModel: .init(status: status, isEmbed: false)) + .padding(.vertical, 8) + } + + NavigationLink { + List { + ForEach(viewModel.trendingStatuses) { status in + StatusRowView(viewModel: .init(status: status, isEmbed: false)) + .padding(.vertical, 8) + } + } + .listStyle(.plain) + .navigationTitle("Trending Posts") + .navigationBarTitleDisplayMode(.inline) + } label: { + Text("See more") + .foregroundColor(.brand) + } + } + + Section("Trending Links") { + ForEach(viewModel.trendingLinks + .prefix(upTo: viewModel.trendingLinks.count > 3 ? 3 : viewModel.trendingLinks.count)) { card in + StatusCardView(card: card) + .padding(.vertical, 8) + } + NavigationLink { + List { + ForEach(viewModel.trendingLinks) { card in + StatusCardView(card: card) + .padding(.vertical, 8) + } + } + .listStyle(.plain) + .navigationTitle("Trending Links") + .navigationBarTitleDisplayMode(.inline) + } label: { + Text("See more") + .foregroundColor(.brand) + } + } + } + .task { + viewModel.client = client + await viewModel.fetchTrending() + } + .listStyle(.grouped) + .navigationTitle("Explore") + .searchable(text: $searchQuery) + } + +} diff --git a/Packages/Explore/Sources/Explore/ExploreViewModel.swift b/Packages/Explore/Sources/Explore/ExploreViewModel.swift new file mode 100644 index 00000000..0552ef7d --- /dev/null +++ b/Packages/Explore/Sources/Explore/ExploreViewModel.swift @@ -0,0 +1,25 @@ +import SwiftUI +import Models +import Network + +@MainActor +class ExploreViewModel: ObservableObject { + var client: Client? + + @Published var trendingTags: [Tag] = [] + @Published var trendingStatuses: [Status] = [] + @Published var trendingLinks: [Card] = [] + + func fetchTrending() async { + guard let client else { return } + do { + async let trendingTags: [Tag] = client.get(endpoint: Trends.tags) + async let trendingStatuses: [Status] = client.get(endpoint: Trends.statuses) + async let trendingLinks: [Card] = client.get(endpoint: Trends.links) + + self.trendingTags = try await trendingTags + self.trendingStatuses = try await trendingStatuses + self.trendingLinks = try await trendingLinks + } catch { } + } +} diff --git a/Packages/Models/Sources/Models/Card.swift b/Packages/Models/Sources/Models/Card.swift index 44d511c2..cf26de38 100644 --- a/Packages/Models/Sources/Models/Card.swift +++ b/Packages/Models/Sources/Models/Card.swift @@ -1,6 +1,10 @@ import Foundation -public struct Card: Codable { +public struct Card: Codable, Identifiable { + public var id: String { + url.absoluteString + } + public let url: URL public let title: String? public let description: String? diff --git a/Packages/Network/Sources/Network/Endpoint/Trends.swift b/Packages/Network/Sources/Network/Endpoint/Trends.swift new file mode 100644 index 00000000..270f32e8 --- /dev/null +++ b/Packages/Network/Sources/Network/Endpoint/Trends.swift @@ -0,0 +1,25 @@ +import Foundation + +public enum Trends: Endpoint { + case tags + case statuses + case links + + public func path() -> String { + switch self { + case .tags: + return "trends/tags" + case .statuses: + return "trends/statuses" + case .links: + return "trends/links" + } + } + + public func queryItems() -> [URLQueryItem]? { + switch self { + default: + return nil + } + } +} diff --git a/Packages/Status/Sources/Status/Editor/StatusEditorView.swift b/Packages/Status/Sources/Status/Editor/StatusEditorView.swift new file mode 100644 index 00000000..5e62f333 --- /dev/null +++ b/Packages/Status/Sources/Status/Editor/StatusEditorView.swift @@ -0,0 +1,36 @@ +import SwiftUI + +public struct StatusEditorView: View { + @Environment(\.dismiss) private var dismiss + + @State private var statusText: String = "" + public init() { + + } + + public var body: some View { + NavigationStack { + Form { + TextEditor(text: $statusText) + } + .navigationTitle("Post a toot") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + dismiss() + } label: { + Text("Post") + } + } + ToolbarItem(placement: .navigationBarLeading) { + Button { + dismiss() + } label: { + Text("Cancel") + } + } + } + } + } +} diff --git a/Packages/Status/Sources/Status/Row/StatusActionsView.swift b/Packages/Status/Sources/Status/Row/StatusActionsView.swift index 940968e9..a3890b97 100644 --- a/Packages/Status/Sources/Status/Row/StatusActionsView.swift +++ b/Packages/Status/Sources/Status/Row/StatusActionsView.swift @@ -4,6 +4,7 @@ import Env import Network struct StatusActionsView: View { + @EnvironmentObject private var routeurPath: RouterPath @ObservedObject var viewModel: StatusRowViewModel @MainActor @@ -62,6 +63,8 @@ struct StatusActionsView: View { private func handleAction(action: Actions) { Task { switch action { + case .respond: + routeurPath.navigate(to: .statusDetail(id: viewModel.status.reblog?.id ?? viewModel.status.id)) case .favourite: if viewModel.isFavourited { await viewModel.unFavourite() diff --git a/Packages/Status/Sources/Status/Row/StatusCardView.swift b/Packages/Status/Sources/Status/Row/StatusCardView.swift index d84553b3..1359bdb1 100644 --- a/Packages/Status/Sources/Status/Row/StatusCardView.swift +++ b/Packages/Status/Sources/Status/Row/StatusCardView.swift @@ -2,12 +2,16 @@ import SwiftUI import Models import Shimmer -struct StatusCardView: View { +public struct StatusCardView: View { @Environment(\.openURL) private var openURL - let status: AnyStatus + let card: Card - var body: some View { - if let card = status.card, let title = card.title { + public init(card: Card) { + self.card = card + } + + public var body: some View { + if let title = card.title { VStack(alignment: .leading) { if let imageURL = card.image { AsyncImage( @@ -59,9 +63,3 @@ struct StatusCardView: View { } } } - -struct StatusCardView_Previews: PreviewProvider { - static var previews: some View { - StatusCardView(status: Status.placeholder()) - } -} diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index 53ed76ce..9b854af2 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -67,14 +67,16 @@ public struct StatusRowView: View { StatusMediaPreviewView(attachements: status.mediaAttachments) .padding(.vertical, 4) } - StatusCardView(status: status) + if let card = status.card { + StatusCardView(card: card) + } } } } @ViewBuilder private func makeAccountView(status: AnyStatus) -> some View { - AvatarView(url: status.account.avatar) + AvatarView(url: status.account.avatar, size: .status) VStack(alignment: .leading, spacing: 0) { status.account.displayNameWithEmojis .font(.subheadline)