feat: 暂提
This commit is contained in:
parent
6229ddcd6c
commit
86ac98a465
@ -2,18 +2,27 @@ import SwiftUI
|
|||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
/// 媒体上传状态
|
/// 媒体上传状态
|
||||||
public enum MediaUploadStatus: Equatable {
|
public enum MediaUploadStatus: Equatable, Identifiable {
|
||||||
case pending
|
case pending
|
||||||
case uploading(progress: Double)
|
case uploading(progress: Double)
|
||||||
case completed(fileId: String)
|
case completed(fileId: String)
|
||||||
case failed(Error)
|
case failed(Error)
|
||||||
|
|
||||||
|
public var id: String {
|
||||||
|
switch self {
|
||||||
|
case .pending: return "pending"
|
||||||
|
case .uploading: return "uploading"
|
||||||
|
case .completed(let fileId): return "completed_\(fileId)"
|
||||||
|
case .failed: return "failed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static func == (lhs: MediaUploadStatus, rhs: MediaUploadStatus) -> Bool {
|
public static func == (lhs: MediaUploadStatus, rhs: MediaUploadStatus) -> Bool {
|
||||||
switch (lhs, rhs) {
|
switch (lhs, rhs) {
|
||||||
case (.pending, .pending):
|
case (.pending, .pending):
|
||||||
return true
|
return true
|
||||||
case (.uploading(let lhsProgress), .uploading(let rhsProgress)):
|
case (.uploading(let lhsProgress), .uploading(let rhsProgress)):
|
||||||
return lhsProgress == rhsProgress
|
return abs(lhsProgress - rhsProgress) < 0.01
|
||||||
case (.completed(let lhsId), .completed(let rhsId)):
|
case (.completed(let lhsId), .completed(let rhsId)):
|
||||||
return lhsId == rhsId
|
return lhsId == rhsId
|
||||||
case (.failed, .failed):
|
case (.failed, .failed):
|
||||||
@ -27,95 +36,96 @@ public enum MediaUploadStatus: Equatable {
|
|||||||
switch self {
|
switch self {
|
||||||
case .pending: return "等待上传"
|
case .pending: return "等待上传"
|
||||||
case .uploading(let progress): return "上传中 \(Int(progress * 100))%"
|
case .uploading(let progress): return "上传中 \(Int(progress * 100))%"
|
||||||
case .completed(let fileId): return "上传完成 (ID: \(fileId.prefix(8))...)"
|
case .completed(let fileId): return "上传完成 (ID: \(fileId.prefix(8)))..."
|
||||||
case .failed(let error): return "上传失败: \(error.localizedDescription)"
|
case .failed(let error): return "上传失败: \(error.localizedDescription)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public var isCompleted: Bool {
|
||||||
|
if case .completed = self { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public var isUploading: Bool {
|
||||||
|
if case .uploading = self { return true }
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 媒体上传管理器
|
/// 媒体上传管理器
|
||||||
|
@MainActor
|
||||||
public class MediaUploadManager: ObservableObject {
|
public class MediaUploadManager: ObservableObject {
|
||||||
/// 已选媒体文件
|
/// 已选媒体文件
|
||||||
@Published public var selectedMedia: [MediaType] = []
|
@Published public private(set) var selectedMedia: [MediaType] = []
|
||||||
/// 上传状态
|
/// 上传状态
|
||||||
@Published public var uploadStatus: [String: MediaUploadStatus] = [:]
|
@Published public private(set) var uploadStatus: [String: MediaUploadStatus] = [:]
|
||||||
|
|
||||||
private let uploader = ImageUploadService()
|
private let uploader = ImageUploadService()
|
||||||
|
private let logger = Logger(subsystem: "com.yourapp.media", category: "MediaUploadManager")
|
||||||
|
|
||||||
public init() {}
|
public init() {}
|
||||||
|
|
||||||
/// 添加上传媒体
|
/// 添加上传媒体
|
||||||
public func addMedia(_ media: [MediaType]) {
|
public func addMedia(_ media: [MediaType]) {
|
||||||
print("Adding \(media.count) media items")
|
|
||||||
let newMedia = media.filter { newItem in
|
let newMedia = media.filter { newItem in
|
||||||
!selectedMedia.contains { $0.id == newItem.id }
|
!self.selectedMedia.contains { $0.id == newItem.id }
|
||||||
}
|
|
||||||
print("After filtering duplicates: \(newMedia.count) new items")
|
|
||||||
|
|
||||||
let isFirstMedia = selectedMedia.isEmpty
|
|
||||||
selectedMedia.append(contentsOf: newMedia)
|
|
||||||
|
|
||||||
for item in newMedia {
|
|
||||||
uploadStatus[item.id] = .pending
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是第一次添加媒体,发送通知
|
// 使用任务组处理多个媒体项的添加
|
||||||
if isFirstMedia, let firstMedia = newMedia.first {
|
Task { [weak self] in
|
||||||
NotificationCenter.default.post(
|
guard let self = self else { return }
|
||||||
name: .didAddFirstMedia,
|
var updatedMedia = self.selectedMedia
|
||||||
object: nil,
|
for item in newMedia {
|
||||||
userInfo: ["media": firstMedia]
|
updatedMedia.append(item)
|
||||||
)
|
self.uploadStatus[item.id] = .pending
|
||||||
|
}
|
||||||
|
self.selectedMedia = updatedMedia
|
||||||
|
|
||||||
|
// 如果是第一次添加媒体,发送通知
|
||||||
|
if !newMedia.isEmpty, let firstMedia = newMedia.first, self.selectedMedia.count == newMedia.count {
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: .didAddFirstMedia,
|
||||||
|
object: nil,
|
||||||
|
userInfo: ["media": firstMedia]
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 移除指定索引的媒体
|
/// 移除指定ID的媒体
|
||||||
public func removeMedia(at index: Int) {
|
public func removeMedia(id: String) {
|
||||||
guard index < selectedMedia.count else { return }
|
Task { @MainActor in
|
||||||
selectedMedia.remove(at: index)
|
self.selectedMedia.removeAll { $0.id == id }
|
||||||
// 更新状态字典
|
self.uploadStatus.removeValue(forKey: id)
|
||||||
var newStatus: [String: MediaUploadStatus] = [:]
|
|
||||||
uploadStatus.forEach { key, value in
|
|
||||||
if let keyInt = Int(key), keyInt < index {
|
|
||||||
newStatus[key] = value
|
|
||||||
} else if let keyInt = Int(key), keyInt > index {
|
|
||||||
newStatus["\(keyInt - 1)"] = value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
uploadStatus = newStatus
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 清空所有媒体
|
/// 清空所有媒体
|
||||||
public func clearAllMedia() {
|
public func clearAllMedia() {
|
||||||
selectedMedia.removeAll()
|
Task { @MainActor in
|
||||||
uploadStatus.removeAll()
|
self.selectedMedia.removeAll()
|
||||||
|
self.uploadStatus.removeAll()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 开始上传所有选中的媒体
|
/// 开始上传所有选中的媒体
|
||||||
public func startUpload() {
|
public func startUpload() {
|
||||||
print("🔄 开始批量上传 \(selectedMedia.count) 个文件")
|
logger.info("🔄 开始批量上传 \(self.selectedMedia.count) 个文件")
|
||||||
// 重置上传状态
|
|
||||||
uploadStatus.removeAll()
|
|
||||||
|
|
||||||
for (index, media) in selectedMedia.enumerated() {
|
// 只处理状态为pending或failed的媒体
|
||||||
let id = "\(index)"
|
let mediaToUpload = self.selectedMedia.filter { media in
|
||||||
uploadStatus[id] = .pending
|
guard let status = self.uploadStatus[media.id] else { return true }
|
||||||
|
return !status.isCompleted && !status.isUploading
|
||||||
// Convert MediaType to ImageUploadService.MediaType
|
}
|
||||||
let uploadMediaType: ImageUploadService.MediaType
|
|
||||||
switch media {
|
for media in mediaToUpload {
|
||||||
case .image(let image):
|
self.uploadMedia(media)
|
||||||
uploadMediaType = .image(image)
|
|
||||||
case .video(let url, let thumbnail):
|
|
||||||
uploadMediaType = .video(url, thumbnail)
|
|
||||||
}
|
|
||||||
uploadMedia(uploadMediaType, id: id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 获取上传结果
|
/// 获取上传结果
|
||||||
public func getUploadResults() -> [String: String] {
|
public func getUploadResults() -> [String: String] {
|
||||||
var results: [String: String] = [:]
|
var results: [String: String] = [:]
|
||||||
for (id, status) in uploadStatus {
|
for (id, status) in uploadStatus {
|
||||||
if case .completed(let fileId) = status {
|
if case .completed(let fileId) = status {
|
||||||
results[id] = fileId
|
results[id] = fileId
|
||||||
@ -126,42 +136,59 @@ public class MediaUploadManager: ObservableObject {
|
|||||||
|
|
||||||
/// 检查是否所有上传都已完成
|
/// 检查是否所有上传都已完成
|
||||||
public var isAllUploaded: Bool {
|
public var isAllUploaded: Bool {
|
||||||
guard !selectedMedia.isEmpty else { return false }
|
guard !self.selectedMedia.isEmpty else { return false }
|
||||||
return uploadStatus.allSatisfy { _, status in
|
return self.selectedMedia.allSatisfy { media in
|
||||||
if case .completed = status { return true }
|
if case .completed = self.uploadStatus[media.id] { return true }
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private Methods
|
// MARK: - Private Methods
|
||||||
|
|
||||||
private func uploadMedia(_ media: ImageUploadService.MediaType, id: String) {
|
private func uploadMedia(_ media: MediaType) {
|
||||||
print("🔄 开始处理媒体: \(id)")
|
logger.info("🔄 开始处理媒体: \(media.id)")
|
||||||
uploadStatus[id] = .uploading(progress: 0)
|
|
||||||
|
|
||||||
|
// 更新状态为上传中
|
||||||
|
updateStatus(for: media.id, status: .uploading(progress: 0))
|
||||||
|
|
||||||
|
// 转换媒体类型
|
||||||
|
let uploadMediaType: ImageUploadService.MediaType
|
||||||
|
switch media {
|
||||||
|
case .image(let image):
|
||||||
|
uploadMediaType = .image(image)
|
||||||
|
case .video(let url, let thumbnail):
|
||||||
|
uploadMediaType = .video(url, thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 开始上传
|
||||||
uploader.uploadMedia(
|
uploader.uploadMedia(
|
||||||
media,
|
uploadMediaType,
|
||||||
progress: { progress in
|
progress: { [weak self] progress in
|
||||||
print("📊 上传进度 (\(id)): \(progress.current)%")
|
guard let self = self else { return }
|
||||||
DispatchQueue.main.async {
|
Task { @MainActor in
|
||||||
self.uploadStatus[id] = .uploading(progress: progress.progress)
|
self.updateStatus(for: media.id, status: .uploading(progress: progress.progress))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
completion: { [weak self] result in
|
completion: { [weak self] result in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
DispatchQueue.main.async {
|
Task { @MainActor in
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let uploadResult):
|
case .success(let uploadResult):
|
||||||
print("✅ 上传成功 (\(id)): \(uploadResult.fileId)")
|
self.logger.info("✅ 上传成功 (\(media.id)): \(uploadResult.fileId)")
|
||||||
self.uploadStatus[id] = .completed(fileId: uploadResult.fileId)
|
self.updateStatus(for: media.id, status: .completed(fileId: uploadResult.fileId))
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
print("❌ 上传失败 (\(id)): \(error.localizedDescription)")
|
self.logger.error("❌ 上传失败 (\(media.id)): \(error.localizedDescription)")
|
||||||
self.uploadStatus[id] = .failed(error)
|
self.updateStatus(for: media.id, status: .failed(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
private func updateStatus(for mediaId: String, status: MediaUploadStatus) {
|
||||||
|
uploadStatus[mediaId] = status
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Preview Helper
|
// MARK: - Preview Helper
|
||||||
@ -171,7 +198,6 @@ struct MediaUploadExample: View {
|
|||||||
@StateObject private var uploadManager = MediaUploadManager()
|
@StateObject private var uploadManager = MediaUploadManager()
|
||||||
@State private var showMediaPicker = false
|
@State private var showMediaPicker = false
|
||||||
|
|
||||||
// 添加图片和视频选择限制参数
|
|
||||||
let imageSelectionLimit: Int
|
let imageSelectionLimit: Int
|
||||||
let videoSelectionLimit: Int
|
let videoSelectionLimit: Int
|
||||||
|
|
||||||
@ -231,7 +257,15 @@ struct MediaUploadExample: View {
|
|||||||
.navigationTitle("媒体上传")
|
.navigationTitle("媒体上传")
|
||||||
.sheet(isPresented: $showMediaPicker) {
|
.sheet(isPresented: $showMediaPicker) {
|
||||||
MediaPicker(
|
MediaPicker(
|
||||||
selectedMedia: $uploadManager.selectedMedia,
|
selectedMedia: Binding(
|
||||||
|
get: { self.uploadManager.selectedMedia },
|
||||||
|
set: { newMedia in
|
||||||
|
Task { @MainActor in
|
||||||
|
self.uploadManager.clearAllMedia()
|
||||||
|
self.uploadManager.addMedia(newMedia)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
imageSelectionLimit: imageSelectionLimit,
|
imageSelectionLimit: imageSelectionLimit,
|
||||||
videoSelectionLimit: videoSelectionLimit,
|
videoSelectionLimit: videoSelectionLimit,
|
||||||
onDismiss: { showMediaPicker = false }
|
onDismiss: { showMediaPicker = false }
|
||||||
@ -253,15 +287,15 @@ struct MediaSelectionView: View {
|
|||||||
|
|
||||||
// 显示媒体缩略图和上传状态
|
// 显示媒体缩略图和上传状态
|
||||||
List {
|
List {
|
||||||
ForEach(0..<uploadManager.selectedMedia.count, id: \.self) { index in
|
ForEach(uploadManager.selectedMedia, id: \.id) { media in
|
||||||
let media = uploadManager.selectedMedia[index]
|
let status = uploadManager.uploadStatus[media.id] ?? .pending
|
||||||
let mediaId = "\(index)"
|
|
||||||
let status = uploadManager.uploadStatus[mediaId] ?? .pending
|
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
// 缩略图
|
// 缩略图
|
||||||
MediaThumbnailView(media: media, onDelete: nil)
|
MediaThumbnailView(media: media, onDelete: {
|
||||||
.frame(width: 60, height: 60)
|
uploadManager.removeMedia(id: media.id)
|
||||||
|
})
|
||||||
|
.frame(width: 60, height: 60)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(media.isVideo ? "视频" : "图片")
|
Text(media.isVideo ? "视频" : "图片")
|
||||||
@ -283,11 +317,6 @@ struct MediaSelectionView: View {
|
|||||||
}
|
}
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, 8)
|
||||||
}
|
}
|
||||||
.onDelete { indexSet in
|
|
||||||
indexSet.forEach { index in
|
|
||||||
uploadManager.removeMedia(at: index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.frame(height: 300)
|
.frame(height: 300)
|
||||||
}
|
}
|
||||||
@ -320,18 +349,35 @@ private struct MediaThumbnailView: View {
|
|||||||
Image(uiImage: thumbnail)
|
Image(uiImage: thumbnail)
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFill()
|
.scaledToFill()
|
||||||
.frame(width: 80, height: 80)
|
.frame(width: 60, height: 60)
|
||||||
.cornerRadius(8)
|
.cornerRadius(8)
|
||||||
.clipped()
|
.clipped()
|
||||||
|
|
||||||
if media.isVideo {
|
if media.isVideo {
|
||||||
Image(systemName: "video.fill")
|
Image(systemName: "video.fill")
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
|
.font(.system(size: 12))
|
||||||
.padding(4)
|
.padding(4)
|
||||||
.background(Color.black.opacity(0.6))
|
.background(Color.black.opacity(0.6))
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.padding(4)
|
.padding(4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除按钮
|
||||||
|
if let onDelete = onDelete {
|
||||||
|
Button(action: onDelete) {
|
||||||
|
Image(systemName: "xmark.circle.fill")
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.font(.system(size: 16))
|
||||||
|
.background(Color.black.opacity(0.8))
|
||||||
|
.clipShape(Circle())
|
||||||
|
}
|
||||||
|
.padding(4)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Color.gray.opacity(0.3)
|
||||||
|
.frame(width: 60, height: 60)
|
||||||
|
.cornerRadius(8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,17 +24,21 @@ struct MediaUploadDemo: View {
|
|||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.sheet(isPresented: $showMediaPicker) {
|
.sheet(isPresented: $showMediaPicker) {
|
||||||
MediaPicker(
|
MediaPicker(
|
||||||
selectedMedia: $uploadManager.selectedMedia,
|
selectedMedia: Binding(
|
||||||
|
get: { uploadManager.selectedMedia },
|
||||||
|
set: { newMedia in
|
||||||
|
uploadManager.clearAllMedia()
|
||||||
|
uploadManager.addMedia(newMedia)
|
||||||
|
}
|
||||||
|
),
|
||||||
imageSelectionLimit: 1,
|
imageSelectionLimit: 1,
|
||||||
videoSelectionLimit: 0,
|
videoSelectionLimit: 0,
|
||||||
allowedMediaTypes: .imagesOnly, // This needs to come before selectionMode
|
allowedMediaTypes: .imagesOnly,
|
||||||
selectionMode: .single, // This was moved after allowedMediaTypes
|
selectionMode: .single,
|
||||||
onDismiss: {
|
onDismiss: {
|
||||||
showMediaPicker = false
|
showMediaPicker = false
|
||||||
// 当媒体选择器关闭时,如果有选中的媒体,开始上传
|
|
||||||
if !uploadManager.selectedMedia.isEmpty {
|
if !uploadManager.selectedMedia.isEmpty {
|
||||||
isUploading = true
|
// Start upload logic here
|
||||||
uploadManager.startUpload()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -113,7 +113,13 @@ public struct AvatarPicker: View {
|
|||||||
}
|
}
|
||||||
.sheet(isPresented: $showMediaPicker) {
|
.sheet(isPresented: $showMediaPicker) {
|
||||||
MediaPicker(
|
MediaPicker(
|
||||||
selectedMedia: $uploadManager.selectedMedia,
|
selectedMedia: Binding(
|
||||||
|
get: { uploadManager.selectedMedia },
|
||||||
|
set: { newMedia in
|
||||||
|
uploadManager.clearAllMedia()
|
||||||
|
uploadManager.addMedia(newMedia)
|
||||||
|
}
|
||||||
|
),
|
||||||
imageSelectionLimit: 1,
|
imageSelectionLimit: 1,
|
||||||
videoSelectionLimit: 0,
|
videoSelectionLimit: 0,
|
||||||
allowedMediaTypes: .imagesOnly,
|
allowedMediaTypes: .imagesOnly,
|
||||||
@ -165,7 +171,8 @@ public struct AvatarPicker: View {
|
|||||||
.sheet(isPresented: $showImageCapture) {
|
.sheet(isPresented: $showImageCapture) {
|
||||||
CustomCameraView(isPresented: $showImageCapture) { image in
|
CustomCameraView(isPresented: $showImageCapture) { image in
|
||||||
selectedImage = image
|
selectedImage = image
|
||||||
uploadManager.selectedMedia = [.image(image)]
|
uploadManager.clearAllMedia()
|
||||||
|
uploadManager.addMedia([.image(image)])
|
||||||
withAnimation {
|
withAnimation {
|
||||||
isUploading = true
|
isUploading = true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -143,7 +143,8 @@ struct MediaUploadView: View {
|
|||||||
if !newItems.isEmpty {
|
if !newItems.isEmpty {
|
||||||
// 将新项添加到现有选择的开头
|
// 将新项添加到现有选择的开头
|
||||||
let newMedia = newItems + uploadManager.selectedMedia
|
let newMedia = newItems + uploadManager.selectedMedia
|
||||||
uploadManager.selectedMedia = newMedia
|
uploadManager.clearAllMedia()
|
||||||
|
uploadManager.addMedia(newMedia)
|
||||||
|
|
||||||
// 如果没有选中的媒体,则选中第一个新增的
|
// 如果没有选中的媒体,则选中第一个新增的
|
||||||
if selectedIndices.isEmpty {
|
if selectedIndices.isEmpty {
|
||||||
@ -225,7 +226,8 @@ struct MediaUploadView: View {
|
|||||||
updatedMedia.append(contentsOf: newItems)
|
updatedMedia.append(contentsOf: newItems)
|
||||||
|
|
||||||
// 更新选中的媒体
|
// 更新选中的媒体
|
||||||
uploadManager.selectedMedia = updatedMedia
|
uploadManager.clearAllMedia()
|
||||||
|
uploadManager.addMedia(updatedMedia)
|
||||||
|
|
||||||
// 如果当前没有选中的媒体,则选中第一个新增的媒体
|
// 如果当前没有选中的媒体,则选中第一个新增的媒体
|
||||||
if selectedIndices.isEmpty && !newItems.isEmpty {
|
if selectedIndices.isEmpty && !newItems.isEmpty {
|
||||||
@ -419,8 +421,9 @@ struct MainUploadArea: View {
|
|||||||
|
|
||||||
// 右上角关闭按钮
|
// 右上角关闭按钮
|
||||||
Button(action: {
|
Button(action: {
|
||||||
// 移除选中的媒体项
|
// 使用公共API移除媒体
|
||||||
uploadManager.selectedMedia.remove(at: index)
|
uploadManager.removeMedia(id: media.id)
|
||||||
|
|
||||||
// 重置选中的媒体
|
// 重置选中的媒体
|
||||||
if selectedMedia == media {
|
if selectedMedia == media {
|
||||||
selectedMedia = nil
|
selectedMedia = nil
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user