Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Resell.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@
AD9A41632DCD0B58B830301D /* DailyPicksView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD0A9236FBD60E612257A18B /* DailyPicksView.swift */; };
AD1EEA9694D1ABD1CD4322D1 /* TrendingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADFE5FBCFC55ED36DEB120C4 /* TrendingView.swift */; };
ADA18E8891F5365FCF5D4A36 /* RecentlyViewedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD36843F11D34E925CA18F7B /* RecentlyViewedView.swift */; };
AD352495F4C92AC5AF1AADF6 /* ExploreCollageCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD0062162947A49A54BA0E74 /* ExploreCollageCard.swift */; };
ADC4811657FC86FA058DBC78 /* ExploreSections.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD61E53786777FDFEDFFBC3F /* ExploreSections.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -338,6 +340,8 @@
AD0A9236FBD60E612257A18B /* DailyPicksView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyPicksView.swift; sourceTree = "<group>"; };
ADFE5FBCFC55ED36DEB120C4 /* TrendingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrendingView.swift; sourceTree = "<group>"; };
AD36843F11D34E925CA18F7B /* RecentlyViewedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyViewedView.swift; sourceTree = "<group>"; };
AD0062162947A49A54BA0E74 /* ExploreCollageCard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreCollageCard.swift; sourceTree = "<group>"; };
AD61E53786777FDFEDFFBC3F /* ExploreSections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExploreSections.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -760,6 +764,8 @@
ADE4A10188B9356473A077AA /* Explore */ = {
isa = PBXGroup;
children = (
AD61E53786777FDFEDFFBC3F /* ExploreSections.swift */,
AD0062162947A49A54BA0E74 /* ExploreCollageCard.swift */,
AD36843F11D34E925CA18F7B /* RecentlyViewedView.swift */,
ADFE5FBCFC55ED36DEB120C4 /* TrendingView.swift */,
AD0A9236FBD60E612257A18B /* DailyPicksView.swift */,
Expand Down Expand Up @@ -929,6 +935,8 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
ADC4811657FC86FA058DBC78 /* ExploreSections.swift in Sources */,
AD352495F4C92AC5AF1AADF6 /* ExploreCollageCard.swift in Sources */,
ADA18E8891F5365FCF5D4A36 /* RecentlyViewedView.swift in Sources */,
AD1EEA9694D1ABD1CD4322D1 /* TrendingView.swift in Sources */,
AD9A41632DCD0B58B830301D /* DailyPicksView.swift in Sources */,
Expand Down
96 changes: 96 additions & 0 deletions Resell/Views/Explore/ExploreCollageCard.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
//
// ExploreCollageCard.swift
// Resell
//
// Created by Andrew Gao on 9/3/26.
//

import SwiftUI

/// Collage preview showing either one image or a four-image grid.
struct ExploreCollageCard: View {

let title: String
let subtitle: String?
let posts: [Post]
let action: () -> Void

private let spacing: CGFloat = 2

@State private var loadedStates: [Bool] = Array(repeating: false, count: 4)

private var previewPosts: [Post] {
Array(posts.prefix(posts.count >= 4 ? 4 : 1))
}

var body: some View {
Button(action: action) {
VStack(alignment: .leading, spacing: 8) {
GeometryReader { geo in
collage(size: geo.size)
.frame(width: geo.size.width, height: geo.size.height)
.clipped()
}
// Slightly wider than tall so cards sit a bit shorter than a square.
.aspectRatio(1.12, contentMode: .fit)
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))

titleLabel
}
}
.buttonStyle(.plain)
.onChange(of: previewPosts.map(\.id)) { _ in
loadedStates = Array(repeating: false, count: 4)
}
}

private var titleLabel: some View {
Text(subtitle.map { $0.isEmpty ? title : "\(title) • \($0)" } ?? title)
.font(Constants.Fonts.title2)
.foregroundStyle(Constants.Colors.black)
.lineLimit(1)
}

@ViewBuilder
private func collage(size: CGSize) -> some View {
let urls = previewPosts.map { URL(string: $0.images.first ?? "") }
let count = urls.count
let cellWidth = (size.width - spacing) / 2
let cellHeight = (size.height - spacing) / 2

if count == 0 {
Constants.Colors.wash
} else if count == 1 {
imageCell(url: urls[0], index: 0)
.frame(width: size.width, height: size.height)
.clipped()
} else {
VStack(spacing: spacing) {
HStack(spacing: spacing) {
imageCell(url: urls[0], index: 0)
.frame(width: cellWidth, height: cellHeight)
.clipped()
imageCell(url: urls[1], index: 1)
.frame(width: cellWidth, height: cellHeight)
.clipped()
}
HStack(spacing: spacing) {
imageCell(url: urls[2], index: 2)
.frame(width: cellWidth, height: cellHeight)
.clipped()
imageCell(url: urls[3], index: 3)
.frame(width: cellWidth, height: cellHeight)
.clipped()
}
}
}
}

private func imageCell(url: URL?, index: Int) -> some View {
CachedImageView(
isImageLoaded: $loadedStates[index],
imageURL: url
)
.clipped()
}
}
Loading