feat: 视频缩略图
This commit is contained in:
parent
008778d9a6
commit
96e58806d4
@ -199,32 +199,48 @@ public class ImageUploadService {
|
||||
|
||||
self.uploader.uploadImage(
|
||||
compressedThumbnail,
|
||||
progress: { _ in },
|
||||
progress: { uploadProgress in
|
||||
// 缩略图上传进度(占总进度的后10%)
|
||||
let progressInfo = UploadProgress(
|
||||
current: 90 + Int(uploadProgress * 10),
|
||||
total: 100,
|
||||
progress: 0.9 + (uploadProgress * 0.1),
|
||||
isOriginal: false
|
||||
)
|
||||
progress(progressInfo)
|
||||
},
|
||||
completion: { thumbnailResult in
|
||||
switch thumbnailResult {
|
||||
case .success(let thumbnailUploadResult):
|
||||
print("✅ 视频缩略图上传完成, fileId: \(thumbnailUploadResult.fileId)")
|
||||
let result = MediaUploadResult.video(
|
||||
video: videoResult,
|
||||
thumbnail: thumbnailUploadResult
|
||||
|
||||
// 确保返回的视频结果中,preview_file_id 是缩略图的 ID
|
||||
let finalVideoResult = ImageUploaderGetID.UploadResult(
|
||||
fileUrl: videoResult.fileUrl,
|
||||
fileName: videoResult.fileName,
|
||||
fileSize: videoResult.fileSize,
|
||||
fileId: videoResult.fileId,
|
||||
previewFileId: thumbnailUploadResult.fileId // 使用缩略图的ID作为preview_file_id
|
||||
)
|
||||
completion(.success(result))
|
||||
|
||||
completion(.success(.video(video: finalVideoResult, thumbnail: thumbnailUploadResult)))
|
||||
|
||||
case .failure(let error):
|
||||
print("❌ 视频缩略图上传失败: \(error.localizedDescription)")
|
||||
completion(.failure(error))
|
||||
// 即使缩略图上传失败,也返回视频上传成功
|
||||
completion(.success(.video(video: videoResult, thumbnail: nil)))
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
let error = NSError(domain: "ImageUploadService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to compress thumbnail"])
|
||||
print("❌ 视频缩略图压缩失败")
|
||||
completion(.failure(error))
|
||||
// 缩略图压缩失败,只返回视频
|
||||
completion(.success(.video(video: videoResult, thumbnail: nil)))
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
print("❌ 视频缩略图提取失败: \(error.localizedDescription)")
|
||||
completion(.failure(error))
|
||||
// 缩略图提取失败,只返回视频
|
||||
completion(.success(.video(video: videoResult, thumbnail: nil)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -352,7 +368,7 @@ public class ImageUploadService {
|
||||
/// 媒体上传结果
|
||||
public enum MediaUploadResult {
|
||||
case file(ImageUploaderGetID.UploadResult)
|
||||
case video(video: ImageUploaderGetID.UploadResult, thumbnail: ImageUploaderGetID.UploadResult)
|
||||
case video(video: ImageUploaderGetID.UploadResult, thumbnail: ImageUploaderGetID.UploadResult?)
|
||||
|
||||
/// 获取文件ID(对于视频,返回视频文件的ID)
|
||||
public var fileId: String {
|
||||
@ -363,6 +379,36 @@ public class ImageUploadService {
|
||||
return videoResult.fileId
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取预览文件ID(对于视频,返回缩略图的ID)
|
||||
public var previewFileId: String? {
|
||||
switch self {
|
||||
case .file:
|
||||
return nil
|
||||
case .video(_, let thumbnailResult):
|
||||
return thumbnailResult?.fileId
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取文件URL(对于视频,返回视频文件的URL)
|
||||
public var fileUrl: String {
|
||||
switch self {
|
||||
case .file(let result):
|
||||
return result.fileUrl
|
||||
case .video(let videoResult, _):
|
||||
return videoResult.fileUrl
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取缩略图URL(如果有)
|
||||
public var thumbnailUrl: String? {
|
||||
switch self {
|
||||
case .file:
|
||||
return nil
|
||||
case .video(_, let thumbnailResult):
|
||||
return thumbnailResult?.fileUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 上传进度信息
|
||||
|
||||
@ -12,12 +12,14 @@ public class ImageUploaderGetID: ObservableObject {
|
||||
public let fileName: String
|
||||
public let fileSize: Int
|
||||
public let fileId: String
|
||||
public let previewFileId: String?
|
||||
|
||||
public init(fileUrl: String, fileName: String, fileSize: Int, fileId: String) {
|
||||
public init(fileUrl: String, fileName: String, fileSize: Int, fileId: String, previewFileId: String? = nil) {
|
||||
self.fileUrl = fileUrl
|
||||
self.fileName = fileName
|
||||
self.fileSize = fileSize
|
||||
self.fileId = fileId
|
||||
self.previewFileId = previewFileId
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +92,11 @@ public class ImageUploaderGetID: ObservableObject {
|
||||
}
|
||||
|
||||
// 2. 获取上传URL
|
||||
getUploadURL(for: imageData) { [weak self] result in
|
||||
getUploadURL(
|
||||
for: imageData,
|
||||
mimeType: "image/jpeg",
|
||||
originalFilename: "image_\(UUID().uuidString).jpg"
|
||||
) { [weak self] result in
|
||||
switch result {
|
||||
case .success((let fileId, let uploadURL)):
|
||||
print("📤 获取到上传URL,开始上传文件...")
|
||||
@ -110,7 +116,7 @@ public class ImageUploaderGetID: ObservableObject {
|
||||
// 4. 确认上传
|
||||
self?.confirmUpload(
|
||||
fileId: fileId,
|
||||
fileName: "avatar_\(UUID().uuidString).jpg",
|
||||
fileName: "image_\(UUID().uuidString).jpg",
|
||||
fileSize: imageData.count,
|
||||
completion: completion
|
||||
)
|
||||
@ -206,76 +212,6 @@ public class ImageUploaderGetID: ObservableObject {
|
||||
|
||||
// MARK: - 私有方法
|
||||
|
||||
/// 获取上传URL
|
||||
private func getUploadURL(
|
||||
for imageData: Data,
|
||||
completion: @escaping (Result<(fileId: String, uploadURL: URL), Error>) -> Void
|
||||
) {
|
||||
let fileName = "avatar_\(UUID().uuidString).jpg"
|
||||
let parameters: [String: Any] = [
|
||||
"filename": fileName,
|
||||
"content_type": "image/jpeg",
|
||||
"file_size": imageData.count
|
||||
]
|
||||
|
||||
let urlString = "\(apiConfig.baseURL)/file/generate-upload-url"
|
||||
guard let url = URL(string: urlString) else {
|
||||
completion(.failure(UploadError.invalidURL))
|
||||
return
|
||||
}
|
||||
|
||||
var request = URLRequest(url: url)
|
||||
request.httpMethod = "POST"
|
||||
request.allHTTPHeaderFields = apiConfig.authHeaders
|
||||
|
||||
do {
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
|
||||
print("📤 准备上传请求,文件名: \(fileName), 大小: \(Double(imageData.count) / 1024.0) KB")
|
||||
} catch {
|
||||
print("❌ 序列化请求参数失败: \(error.localizedDescription)")
|
||||
completion(.failure(error))
|
||||
return
|
||||
}
|
||||
|
||||
let task = session.dataTask(with: request) { data, response, error in
|
||||
if let error = error {
|
||||
completion(.failure(UploadError.uploadFailed(error)))
|
||||
return
|
||||
}
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
completion(.failure(UploadError.invalidResponse))
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
completion(.failure(UploadError.invalidResponse))
|
||||
return
|
||||
}
|
||||
|
||||
// 打印调试信息
|
||||
if let responseString = String(data: data, encoding: .utf8) {
|
||||
print("📥 获取上传URL响应: \(responseString)")
|
||||
}
|
||||
|
||||
do {
|
||||
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
|
||||
let dataDict = json["data"] as? [String: Any],
|
||||
let fileId = dataDict["file_id"] as? String,
|
||||
let uploadURLString = dataDict["upload_url"] as? String,
|
||||
let uploadURL = URL(string: uploadURLString) else {
|
||||
throw UploadError.invalidResponse
|
||||
}
|
||||
|
||||
completion(.success((fileId: fileId, uploadURL: uploadURL)))
|
||||
} catch {
|
||||
completion(.failure(UploadError.invalidResponse))
|
||||
}
|
||||
}
|
||||
|
||||
task.resume()
|
||||
}
|
||||
|
||||
/// 获取上传URL
|
||||
private func getUploadURL(
|
||||
for fileData: Data,
|
||||
@ -291,7 +227,14 @@ public class ImageUploaderGetID: ObservableObject {
|
||||
]
|
||||
|
||||
let urlString = "\(apiConfig.baseURL)/file/generate-upload-url"
|
||||
print("🌐 准备请求上传URL...")
|
||||
print(" - 目标URL: \(urlString)")
|
||||
print(" - 文件名: \(fileName)")
|
||||
print(" - 文件大小: \(Double(fileData.count) / 1024.0) KB")
|
||||
print(" - MIME类型: \(mimeType)")
|
||||
|
||||
guard let url = URL(string: urlString) else {
|
||||
print("❌ 错误: 无效的URL: \(urlString)")
|
||||
completion(.failure(UploadError.invalidURL))
|
||||
return
|
||||
}
|
||||
@ -302,7 +245,9 @@ public class ImageUploaderGetID: ObservableObject {
|
||||
|
||||
do {
|
||||
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
|
||||
print("📤 准备上传请求,文件名: \(fileName), 大小: \(Double(fileData.count) / 1024.0) KB")
|
||||
if let bodyString = String(data: request.httpBody ?? Data(), encoding: .utf8) {
|
||||
print("📤 请求体: \(bodyString)")
|
||||
}
|
||||
} catch {
|
||||
print("❌ 序列化请求参数失败: \(error.localizedDescription)")
|
||||
completion(.failure(error))
|
||||
@ -311,37 +256,61 @@ public class ImageUploaderGetID: ObservableObject {
|
||||
|
||||
let task = session.dataTask(with: request) { data, response, error in
|
||||
if let error = error {
|
||||
print("❌ 获取上传URL请求失败: \(error.localizedDescription)")
|
||||
completion(.failure(UploadError.uploadFailed(error)))
|
||||
return
|
||||
}
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
print("❌ 无效的服务器响应")
|
||||
completion(.failure(UploadError.invalidResponse))
|
||||
return
|
||||
}
|
||||
|
||||
print("📥 收到上传URL响应")
|
||||
print(" - 状态码: \(httpResponse.statusCode)")
|
||||
|
||||
guard let data = data else {
|
||||
print("❌ 响应数据为空")
|
||||
completion(.failure(UploadError.invalidResponse))
|
||||
return
|
||||
}
|
||||
|
||||
// 打印调试信息
|
||||
// 打印响应头
|
||||
print(" - 响应头: \(httpResponse.allHeaderFields)")
|
||||
|
||||
// 打印响应体
|
||||
if let responseString = String(data: data, encoding: .utf8) {
|
||||
print("📥 上传URL响应: \(responseString)")
|
||||
print(" - 响应体: \(responseString)")
|
||||
}
|
||||
|
||||
do {
|
||||
let json = try JSONSerialization.jsonObject(with: data) as? [String: Any]
|
||||
guard let code = json?["code"] as? Int, code == 0,
|
||||
let dataDict = json?["data"] as? [String: Any],
|
||||
print(" - 解析的JSON: \(String(describing: json))")
|
||||
|
||||
guard let code = json?["code"] as? Int, code == 0 else {
|
||||
let errorMessage = json?["message"] as? String ?? "未知错误"
|
||||
print("❌ 服务器返回错误: \(errorMessage)")
|
||||
completion(.failure(UploadError.serverError(errorMessage)))
|
||||
return
|
||||
}
|
||||
|
||||
guard let dataDict = json?["data"] as? [String: Any],
|
||||
let fileId = dataDict["file_id"] as? String,
|
||||
let uploadURLString = dataDict["upload_url"] as? String,
|
||||
let uploadURL = URL(string: uploadURLString) else {
|
||||
throw UploadError.invalidResponse
|
||||
print("❌ 响应数据格式错误")
|
||||
completion(.failure(UploadError.invalidResponse))
|
||||
return
|
||||
}
|
||||
|
||||
print("✅ 成功获取上传URL")
|
||||
print(" - 文件ID: \(fileId)")
|
||||
print(" - 上传URL: \(uploadURLString)")
|
||||
|
||||
completion(.success((fileId: fileId, uploadURL: uploadURL)))
|
||||
} catch {
|
||||
print("❌ 解析响应数据失败: \(error.localizedDescription)")
|
||||
completion(.failure(UploadError.invalidResponse))
|
||||
}
|
||||
}
|
||||
@ -421,75 +390,61 @@ public class ImageUploaderGetID: ObservableObject {
|
||||
public func uploadFile(
|
||||
fileData: Data,
|
||||
to uploadURL: URL,
|
||||
mimeType: String = "application/octet-stream",
|
||||
mimeType: String,
|
||||
onProgress: @escaping (Double) -> Void,
|
||||
completion: @escaping (Result<Void, Error>) -> Void
|
||||
) -> URLSessionUploadTask {
|
||||
print("📤 开始上传文件...")
|
||||
|
||||
var request = URLRequest(url: uploadURL)
|
||||
request.httpMethod = "PUT"
|
||||
request.setValue(mimeType, forHTTPHeaderField: "Content-Type")
|
||||
|
||||
let task = session.uploadTask(with: request, from: fileData) { _, response, error in
|
||||
let task = session.uploadTask(
|
||||
with: request,
|
||||
from: fileData
|
||||
) { data, response, error in
|
||||
if let error = error {
|
||||
completion(.failure(error))
|
||||
print("❌ 文件上传失败: \(error.localizedDescription)")
|
||||
completion(.failure(UploadError.uploadFailed(error)))
|
||||
return
|
||||
}
|
||||
|
||||
guard let httpResponse = response as? HTTPURLResponse else {
|
||||
print("❌ 无效的响应")
|
||||
completion(.failure(UploadError.invalidResponse))
|
||||
return
|
||||
}
|
||||
|
||||
guard (200...299).contains(httpResponse.statusCode) else {
|
||||
let statusCode = httpResponse.statusCode
|
||||
completion(.failure(UploadError.serverError("上传失败,状态码: \(statusCode)")))
|
||||
print("❌ 服务器返回错误状态码: \(statusCode)")
|
||||
completion(.failure(UploadError.serverError("HTTP \(statusCode)")))
|
||||
return
|
||||
}
|
||||
|
||||
print("✅ 文件上传成功")
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
// 添加进度观察
|
||||
if #available(iOS 11.0, *) {
|
||||
let progressObserver = task.progress.observe(\.fractionCompleted) { progressValue, _ in
|
||||
DispatchQueue.main.async {
|
||||
onProgress(progressValue.fractionCompleted)
|
||||
}
|
||||
let progressObserver = task.progress.observe(\.fractionCompleted) { progress, _ in
|
||||
let percentComplete = progress.fractionCompleted
|
||||
print("📊 文件上传进度: \(Int(percentComplete * 100))%")
|
||||
onProgress(percentComplete)
|
||||
}
|
||||
|
||||
task.addCompletionHandler { [weak task] in
|
||||
progressObserver.invalidate()
|
||||
task?.progress.cancel()
|
||||
}
|
||||
} else {
|
||||
var lastProgress: Double = 0
|
||||
let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
|
||||
let bytesSent = task.countOfBytesSent
|
||||
let totalBytes = task.countOfBytesExpectedToSend
|
||||
let currentProgress = totalBytes > 0 ? Double(bytesSent) / Double(totalBytes) : 0
|
||||
|
||||
// 只有当进度有显著变化时才回调,避免频繁更新UI
|
||||
if abs(currentProgress - lastProgress) > 0.01 || currentProgress >= 1.0 {
|
||||
lastProgress = currentProgress
|
||||
DispatchQueue.main.async {
|
||||
onProgress(min(currentProgress, 1.0))
|
||||
}
|
||||
}
|
||||
|
||||
if currentProgress >= 1.0 {
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
task.addCompletionHandler {
|
||||
timer.invalidate()
|
||||
}
|
||||
}
|
||||
// 存储观察者以避免提前释放
|
||||
objc_setAssociatedObject(task, &AssociatedKeys.progressObserver, progressObserver, .OBJC_ASSOCIATION_RETAIN)
|
||||
|
||||
task.resume()
|
||||
return task
|
||||
}
|
||||
|
||||
private struct AssociatedKeys {
|
||||
static var progressObserver = "progressObserver"
|
||||
}
|
||||
|
||||
// MARK: - 文件上传状态
|
||||
|
||||
/// 文件上传状态
|
||||
|
||||
@ -60,7 +60,7 @@ public class MediaUploadManager: ObservableObject {
|
||||
/// 上传状态
|
||||
@Published public private(set) var uploadStatus: [String: MediaUploadStatus] = [:]
|
||||
/// 上传结果
|
||||
@Published public private(set) var uploadResults: [String: String] = [:] // Store fileId as String
|
||||
@Published public private(set) var uploadResults: [String: UploadResult] = [:]
|
||||
|
||||
private let uploader = ImageUploadService() // Use ImageUploadService
|
||||
private let logger = Logger(subsystem: "com.yourapp.media", category: "MediaUploadManager")
|
||||
@ -125,14 +125,8 @@ public class MediaUploadManager: ObservableObject {
|
||||
}
|
||||
|
||||
/// 获取上传结果
|
||||
public func getUploadResults() -> [String: String] {
|
||||
var results: [String: String] = [:]
|
||||
for (id, status) in uploadStatus {
|
||||
if case .completed(let fileId) = status {
|
||||
results[id] = fileId
|
||||
}
|
||||
}
|
||||
return results
|
||||
public func getUploadResults() -> [String: UploadResult] {
|
||||
return uploadResults
|
||||
}
|
||||
|
||||
/// 检查是否所有上传都已完成
|
||||
@ -146,6 +140,21 @@ public class MediaUploadManager: ObservableObject {
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
/// 上传结果
|
||||
public struct UploadResult: Codable, Equatable {
|
||||
public let fileId: String
|
||||
public let thumbnailId: String?
|
||||
|
||||
public init(fileId: String, thumbnailId: String? = nil) {
|
||||
self.fileId = fileId
|
||||
self.thumbnailId = thumbnailId
|
||||
}
|
||||
|
||||
public static func == (lhs: UploadResult, rhs: UploadResult) -> Bool {
|
||||
return lhs.fileId == rhs.fileId && lhs.thumbnailId == rhs.thumbnailId
|
||||
}
|
||||
}
|
||||
|
||||
private func uploadMedia(_ media: MediaType) {
|
||||
logger.info("🔄 开始处理媒体: \(media.id)")
|
||||
|
||||
@ -175,9 +184,23 @@ public class MediaUploadManager: ObservableObject {
|
||||
Task { @MainActor in
|
||||
switch result {
|
||||
case .success(let uploadResult):
|
||||
let fileId = uploadResult.fileId
|
||||
self.logger.info("✅ 上传成功 (\(media.id)): \(fileId)")
|
||||
self.uploadResults[media.id] = fileId
|
||||
// 处理上传结果
|
||||
let fileId: String
|
||||
let thumbnailId: String?
|
||||
|
||||
switch uploadResult {
|
||||
case .file(let result):
|
||||
fileId = result.fileId
|
||||
thumbnailId = nil
|
||||
case .video(let video, let thumbnail):
|
||||
fileId = video.fileId
|
||||
thumbnailId = thumbnail?.fileId
|
||||
}
|
||||
|
||||
// 保存上传结果
|
||||
let result = UploadResult(fileId: fileId, thumbnailId: thumbnailId)
|
||||
self.uploadResults[media.id] = result
|
||||
self.logger.info("✅ 上传成功 (\(media.id)): \(fileId), 缩略图ID: \(thumbnailId ?? "无")")
|
||||
self.updateStatus(for: media.id, status: .completed(fileId: fileId))
|
||||
|
||||
// 打印上传结果
|
||||
@ -205,8 +228,8 @@ public class MediaUploadManager: ObservableObject {
|
||||
let results = self.selectedMedia.compactMap { media -> [String: String]? in
|
||||
guard let result = self.uploadResults[media.id] else { return nil }
|
||||
return [
|
||||
"file_id": result,
|
||||
"preview_file_id": result
|
||||
"file_id": result.fileId,
|
||||
"preview_file_id": result.thumbnailId ?? result.fileId
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@ -8,23 +8,38 @@ struct MaterialResponse: Decodable {
|
||||
|
||||
struct MaterialData: Decodable {
|
||||
let items: [MemoryItem]
|
||||
let hasMore: Bool
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case items
|
||||
case hasMore = "has_more"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MemoryItem: Identifiable, Decodable {
|
||||
let id: String
|
||||
let name: String
|
||||
let description: String
|
||||
let name: String?
|
||||
let description: String?
|
||||
let fileInfo: FileInfo
|
||||
let previewFileInfo: FileInfo
|
||||
|
||||
var title: String { name }
|
||||
var subtitle: String { description }
|
||||
var mediaType: MemoryMediaType { .image(fileInfo.url) }
|
||||
var aspectRatio: CGFloat { 1.0 } // Default to square, adjust based on actual image dimensions if needed
|
||||
var title: String { name ?? "Untitled" }
|
||||
var subtitle: String { description ?? "" }
|
||||
var mediaType: MemoryMediaType {
|
||||
let url = fileInfo.url.lowercased()
|
||||
if url.hasSuffix(".mp4") || url.hasSuffix(".mov") {
|
||||
return .video(url: fileInfo.url, previewUrl: previewFileInfo.url)
|
||||
} else {
|
||||
return .image(fileInfo.url)
|
||||
}
|
||||
}
|
||||
var aspectRatio: CGFloat { 1.0 }
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, name, description
|
||||
case fileInfo = "file_info"
|
||||
case previewFileInfo = "preview_file_info"
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,7 +57,7 @@ struct FileInfo: Decodable {
|
||||
|
||||
enum MemoryMediaType: Equatable {
|
||||
case image(String)
|
||||
case video(String)
|
||||
case video(url: String, previewUrl: String)
|
||||
}
|
||||
|
||||
struct MemoriesView: View {
|
||||
@ -57,6 +72,9 @@ struct MemoriesView: View {
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
ZStack {
|
||||
Color.themeTextWhiteSecondary.ignoresSafeArea()
|
||||
|
||||
Group {
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
@ -77,8 +95,15 @@ struct MemoriesView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("My Memories")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
fetchMemories()
|
||||
}
|
||||
@ -97,9 +122,9 @@ struct MemoriesView: View {
|
||||
|
||||
switch result {
|
||||
case .success(let response):
|
||||
print("✅ Successfully fetched \(response.data.items.count) memory items")
|
||||
print("✅ Successfully fetched \(response.data.items) memory items")
|
||||
response.data.items.forEach { item in
|
||||
print("📝 Item ID: \(item.id), Title: \(item.name), URL: \(item.fileInfo.url)")
|
||||
print("📝 Item ID: \(item.id), Title: \(item.name ?? "Untitled"), URL: \(item)")
|
||||
}
|
||||
self.memories = response.data.items
|
||||
case .failure(let error):
|
||||
@ -135,33 +160,38 @@ struct MemoryCard: View {
|
||||
}
|
||||
}
|
||||
|
||||
case .video(let urlString):
|
||||
if let url = URL(string: urlString) {
|
||||
VideoPlayer(player: AVPlayer(url: url))
|
||||
.aspectRatio(memory.aspectRatio, contentMode: .fill)
|
||||
.onAppear {
|
||||
// The video will be shown with a play button overlay
|
||||
// and will only play when tapped
|
||||
case .video(let url, let previewUrl):
|
||||
// Use preview image for video
|
||||
if let previewUrl = URL(string: previewUrl) {
|
||||
AsyncImage(url: previewUrl) { phase in
|
||||
if let image = phase.image {
|
||||
image
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
} else if phase.error != nil {
|
||||
Color.gray.opacity(0.3)
|
||||
} else {
|
||||
ProgressView()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Color.gray.opacity(0.3)
|
||||
.aspectRatio(memory.aspectRatio, contentMode: .fill)
|
||||
}
|
||||
}
|
||||
}
|
||||
.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) * (1/memory.aspectRatio))
|
||||
.clipped()
|
||||
.cornerRadius(12)
|
||||
.overlay(
|
||||
Group {
|
||||
|
||||
// Show play button for videos
|
||||
if case .video = memory.mediaType {
|
||||
Image(systemName: "play.circle.fill")
|
||||
.font(.system(size: 40))
|
||||
.foregroundColor(.white.opacity(0.9))
|
||||
.shadow(radius: 3)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Title and Subtitle
|
||||
VStack(alignment: .leading, spacing: 1) {
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import SwiftUI
|
||||
import AVFoundation
|
||||
import PhotosUI
|
||||
import AVKit
|
||||
import CoreTransferable
|
||||
import CoreImage.CIFilterBuiltins
|
||||
|
||||
extension Notification.Name {
|
||||
static let didAddFirstMedia = Notification.Name("didAddFirstMedia")
|
||||
@ -296,33 +299,33 @@ struct MediaUploadView: View {
|
||||
}
|
||||
|
||||
/// 处理上传完成
|
||||
private func handleUploadCompletion(results: [String: String]) {
|
||||
uploadedFileIds = results.map { ["file_id": $0.value, "preview_file_id": $0.value] }
|
||||
uploadComplete = !uploadedFileIds.isEmpty
|
||||
|
||||
// 打印结果到控制台
|
||||
if let jsonData = try? JSONSerialization.data(withJSONObject: uploadedFileIds, options: .prettyPrinted),
|
||||
let jsonString = String(data: jsonData, encoding: .utf8) {
|
||||
print("📦 上传完成,文件ID列表:")
|
||||
print(jsonString)
|
||||
private func handleUploadCompletion(results: [String: MediaUploadManager.UploadResult]) {
|
||||
// 转换为需要的格式
|
||||
let formattedResults = results.map { (_, result) -> [String: String] in
|
||||
return [
|
||||
"file_id": result.fileId,
|
||||
"preview_file_id": result.thumbnailId ?? result.fileId
|
||||
]
|
||||
}
|
||||
|
||||
uploadedFileIds = formattedResults
|
||||
uploadComplete = !uploadedFileIds.isEmpty
|
||||
}
|
||||
|
||||
/// 处理继续按钮点击
|
||||
private func handleContinue() {
|
||||
// 获取所有已上传文件的ID
|
||||
let fileIds = uploadManager.uploadResults.map { $0.value }
|
||||
|
||||
guard !fileIds.isEmpty else {
|
||||
// 获取所有已上传文件的结果
|
||||
let uploadResults = uploadManager.uploadResults
|
||||
guard !uploadResults.isEmpty else {
|
||||
print("⚠️ 没有可用的文件ID")
|
||||
return
|
||||
}
|
||||
|
||||
// 准备请求参数
|
||||
let files = fileIds.map { fileId -> [String: String] in
|
||||
let files = uploadResults.map { (_, result) -> [String: String] in
|
||||
return [
|
||||
"file_id": fileId,
|
||||
"preview_file_id": fileId
|
||||
"file_id": result.fileId,
|
||||
"preview_file_id": result.thumbnailId ?? result.fileId
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user