feat: 瀑布流
This commit is contained in:
parent
68582b82cb
commit
4836c1f4ae
@ -9,6 +9,7 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
AB6693CA2E65C94400BCAAC1 /* SVGKit in Frameworks */ = {isa = PBXBuildFile; productRef = AB6693C92E65C94400BCAAC1 /* SVGKit */; };
|
||||
AB6693CC2E65C94400BCAAC1 /* SVGKitSwift in Frameworks */ = {isa = PBXBuildFile; productRef = AB6693CB2E65C94400BCAAC1 /* SVGKitSwift */; };
|
||||
AB6695272E67015600BCAAC1 /* WaterfallGrid in Frameworks */ = {isa = PBXBuildFile; productRef = AB6695262E67015600BCAAC1 /* WaterfallGrid */; };
|
||||
AB8773632E4E04400071CB53 /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = AB8773622E4E040E0071CB53 /* .gitignore */; };
|
||||
ABC150C12E5DB39A00A1F970 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = ABC150C02E5DB39A00A1F970 /* Lottie */; };
|
||||
ABE8998E2E533A7100CD7BA6 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = ABE8998D2E533A7100CD7BA6 /* Alamofire */; };
|
||||
@ -62,6 +63,7 @@
|
||||
AB6693CC2E65C94400BCAAC1 /* SVGKitSwift in Frameworks */,
|
||||
AB6693CA2E65C94400BCAAC1 /* SVGKit in Frameworks */,
|
||||
ABE8998E2E533A7100CD7BA6 /* Alamofire in Frameworks */,
|
||||
AB6695272E67015600BCAAC1 /* WaterfallGrid in Frameworks */,
|
||||
ABC150C12E5DB39A00A1F970 /* Lottie in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -120,6 +122,7 @@
|
||||
ABC150C02E5DB39A00A1F970 /* Lottie */,
|
||||
AB6693C92E65C94400BCAAC1 /* SVGKit */,
|
||||
AB6693CB2E65C94400BCAAC1 /* SVGKitSwift */,
|
||||
AB6695262E67015600BCAAC1 /* WaterfallGrid */,
|
||||
);
|
||||
productName = wake;
|
||||
productReference = ABB4E2082E4B75D900660198 /* wake.app */;
|
||||
@ -153,6 +156,7 @@
|
||||
ABE8998C2E533A7100CD7BA6 /* XCRemoteSwiftPackageReference "Alamofire" */,
|
||||
ABC150BF2E5DB39A00A1F970 /* XCRemoteSwiftPackageReference "lottie-spm" */,
|
||||
AB6693C82E65C94400BCAAC1 /* XCRemoteSwiftPackageReference "SVGKit" */,
|
||||
AB6695252E67015600BCAAC1 /* XCRemoteSwiftPackageReference "WaterfallGrid" */,
|
||||
);
|
||||
preferredProjectObjectVersion = 77;
|
||||
productRefGroup = ABB4E2092E4B75D900660198 /* Products */;
|
||||
@ -409,6 +413,14 @@
|
||||
minimumVersion = 3.0.0;
|
||||
};
|
||||
};
|
||||
AB6695252E67015600BCAAC1 /* XCRemoteSwiftPackageReference "WaterfallGrid" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/paololeonardi/WaterfallGrid.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 1.1.0;
|
||||
};
|
||||
};
|
||||
ABC150BF2E5DB39A00A1F970 /* XCRemoteSwiftPackageReference "lottie-spm" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/airbnb/lottie-spm.git";
|
||||
@ -438,6 +450,11 @@
|
||||
package = AB6693C82E65C94400BCAAC1 /* XCRemoteSwiftPackageReference "SVGKit" */;
|
||||
productName = SVGKitSwift;
|
||||
};
|
||||
AB6695262E67015600BCAAC1 /* WaterfallGrid */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = AB6695252E67015600BCAAC1 /* XCRemoteSwiftPackageReference "WaterfallGrid" */;
|
||||
productName = WaterfallGrid;
|
||||
};
|
||||
ABC150C02E5DB39A00A1F970 /* Lottie */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = ABC150BF2E5DB39A00A1F970 /* XCRemoteSwiftPackageReference "lottie-spm" */;
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"originHash" : "d4b9379b4bd658fe79a6ae528c96d3386427dfe9d23635a65dad6edf12af85ff",
|
||||
"originHash" : "7ea295cc5e3eb8ef644b89ce2b47a7600994b67c8582ee354b643cd63250740d",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "alamofire",
|
||||
@ -45,6 +45,15 @@
|
||||
"revision" : "ce592ae52f982c847a4efc0dd881cc9eb32d29f2",
|
||||
"version" : "1.6.4"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "waterfallgrid",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/paololeonardi/WaterfallGrid.git",
|
||||
"state" : {
|
||||
"revision" : "c7c08652c3540adf8e48409c351879b4caea7e89",
|
||||
"version" : "1.1.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 3
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import SwiftUI
|
||||
import AVKit
|
||||
import WaterfallGrid
|
||||
|
||||
// MARK: - API Response Models
|
||||
struct MaterialResponse: Decodable {
|
||||
@ -98,30 +99,43 @@ struct MemoriesView: View {
|
||||
ZStack {
|
||||
Color.themeTextWhiteSecondary.ignoresSafeArea()
|
||||
|
||||
Group {
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
.scaleEffect(1.5)
|
||||
} else if let error = errorMessage {
|
||||
Text("Error: \(error)")
|
||||
.foregroundColor(.red)
|
||||
} else {
|
||||
// Group {
|
||||
// if isLoading {
|
||||
// ProgressView()
|
||||
// .scaleEffect(1.5)
|
||||
// } else if let error = errorMessage {
|
||||
// Text("Error: \(error)")
|
||||
// .foregroundColor(.red)
|
||||
// } else {
|
||||
// ScrollView {
|
||||
// LazyVGrid(columns: columns, spacing: 4) {
|
||||
// ForEach(memories) { memory in
|
||||
// MemoryCard(memory: memory)
|
||||
// .padding(.horizontal, 2)
|
||||
// .onTapGesture {
|
||||
// withAnimation(.spring()) {
|
||||
// selectedMemory = memory
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// .padding(.top, 4)
|
||||
// .padding(.horizontal, 4)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Replace the WaterfallGrid line with this:
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns, spacing: 4) {
|
||||
ForEach(memories) { memory in
|
||||
WaterfallGrid(memories) { memory in
|
||||
MemoryCard(memory: memory)
|
||||
.padding(.horizontal, 2)
|
||||
.onTapGesture {
|
||||
withAnimation(.spring()) {
|
||||
selectedMemory = memory
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 4)
|
||||
.padding(.horizontal, 4)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -372,20 +386,45 @@ struct VideoPlayer: UIViewRepresentable {
|
||||
|
||||
struct MemoryCard: View {
|
||||
let memory: MemoryItem
|
||||
@State private var aspectRatio: CGFloat = 1.0
|
||||
@State private var isLoading = true
|
||||
|
||||
private func loadAspectRatio(from url: URL) {
|
||||
guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil),
|
||||
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [CFString: Any],
|
||||
let width = imageProperties[kCGImagePropertyPixelWidth] as? CGFloat,
|
||||
let height = imageProperties[kCGImagePropertyPixelHeight] as? CGFloat,
|
||||
height > 0 else {
|
||||
aspectRatio = 16/9 // Default to 16:9 if we can't determine the aspect ratio
|
||||
isLoading = false
|
||||
return
|
||||
}
|
||||
|
||||
aspectRatio = width / height
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
ZStack {
|
||||
// Media content
|
||||
Group {
|
||||
switch memory.mediaType {
|
||||
case .image(let url):
|
||||
if let url = URL(string: url) {
|
||||
AsyncImage(url: url) { phase in
|
||||
Group {
|
||||
if let image = phase.image {
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.onAppear {
|
||||
// Get image dimensions
|
||||
if let uiImage = image.asUIImage() {
|
||||
let size = uiImage.size
|
||||
aspectRatio = size.width / size.height
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
} else if phase.error != nil {
|
||||
Color.gray.opacity(0.3)
|
||||
} else {
|
||||
@ -393,34 +432,38 @@ struct MemoryCard: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .video(_, let previewUrl):
|
||||
if let previewUrl = URL(string: previewUrl) {
|
||||
AsyncImage(url: previewUrl) { phase in
|
||||
Group {
|
||||
if let image = phase.image {
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.onAppear {
|
||||
loadAspectRatio(from: previewUrl)
|
||||
}
|
||||
} else if phase.error != nil {
|
||||
Color.gray.opacity(0.3)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Color.gray.opacity(0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: (UIScreen.main.bounds.width / 2) - 24,
|
||||
height: (UIScreen.main.bounds.width / 2 - 24) * (1/memory.aspectRatio))
|
||||
.frame(
|
||||
width: (UIScreen.main.bounds.width / 2) - 24,
|
||||
height: (UIScreen.main.bounds.width / 2 - 24) / (isLoading ? 1 : aspectRatio)
|
||||
)
|
||||
.clipped()
|
||||
.cornerRadius(12)
|
||||
.onAppear {
|
||||
print("ℹ️ℹ️ℹ️ℹ️ℹ️ℹ️ℹ️ℹ️ Memory Card Appeared - Media Type: \(memory.mediaType)")
|
||||
}
|
||||
|
||||
// Show play button for videos
|
||||
if case .video = memory.mediaType {
|
||||
Image(systemName: "play.circle.fill")
|
||||
.font(.system(size: 40))
|
||||
@ -429,15 +472,11 @@ struct MemoryCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Title and Subtitle
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(memory.title)
|
||||
.font(Typography.font(for: .body, family: .quicksandBold))
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
.lineLimit(1)
|
||||
.onTapGesture {
|
||||
print("🐰🐰🐰🐰🐰🐰🐰🐰🐰🐰🐰 \(memory)")
|
||||
}
|
||||
|
||||
Text(memory.subtitle)
|
||||
.font(.system(size: 14))
|
||||
@ -450,6 +489,23 @@ struct MemoryCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Add this extension to get UIImage from Image
|
||||
extension View {
|
||||
func asUIImage() -> UIImage? {
|
||||
let controller = UIHostingController(rootView: self)
|
||||
let view = controller.view
|
||||
|
||||
let targetSize = controller.view.intrinsicContentSize
|
||||
view?.bounds = CGRect(origin: .zero, size: targetSize)
|
||||
view?.backgroundColor = .clear
|
||||
|
||||
let renderer = UIGraphicsImageRenderer(size: targetSize)
|
||||
return renderer.image { _ in
|
||||
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
MemoriesView()
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user