feat: uploadmore

This commit is contained in:
jinyaqiu 2025-08-26 14:54:09 +08:00
parent 607405ba58
commit f232c70ef1

View File

@ -1,4 +1,5 @@
import SwiftUI
import AVFoundation
extension Notification.Name {
static let didAddFirstMedia = Notification.Name("didAddFirstMedia")
@ -28,14 +29,14 @@ struct MediaUploadView: View {
//
uploadHintView
Spacer()
.frame(height: uploadManager.selectedMedia.isEmpty ? 80 : 40)
//
MainUploadArea(
uploadManager: uploadManager,
showMediaPicker: $showMediaPicker,
selectedMedia: $selectedMedia
)
.padding()
.id("mainUploadArea\(uploadManager.selectedMedia.count)")
Spacer()
@ -92,7 +93,7 @@ struct MediaUploadView: View {
.font(.caption)
.foregroundColor(.black)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(12)
.padding(3)
.background(
LinearGradient(
gradient: Gradient(colors: [
@ -148,7 +149,7 @@ struct MediaUploadView: View {
//
if selectedIndices.isEmpty {
selectedIndices = [0] //
selectedIndices = [0] //
selectedMedia = newItems.first
}
@ -270,7 +271,9 @@ struct MainUploadArea: View {
// MARK: -
var body: some View {
VStack(spacing: 16) {
VStack() {
Spacer()
.frame(height: 30)
//
Text("Click to upload 20 images and 5 videos to generate your next blind box.")
.font(Typography.font(for: .title2, family: .quicksandBold))
@ -278,6 +281,8 @@ struct MainUploadArea: View {
.foregroundColor(.black)
.multilineTextAlignment(.center)
.padding(.horizontal)
Spacer()
.frame(height: 50)
//
if let mediaToDisplay = selectedMedia ?? uploadManager.selectedMedia.first {
MediaPreview(media: mediaToDisplay, uploadManager: uploadManager)
@ -285,7 +290,7 @@ struct MainUploadArea: View {
.frame(width: 225, height: 225)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(Color.themePrimary, lineWidth: 5) // 使2线
.stroke(Color.themePrimary, lineWidth: 5)
)
.cornerRadius(16)
.shadow(radius: 4)
@ -296,6 +301,8 @@ struct MainUploadArea: View {
}
//
mediaPreviewSection
Spacer()
.frame(height: 10)
}
.onAppear {
print("MainUploadArea appeared")
@ -312,9 +319,8 @@ struct MainUploadArea: View {
}
}
.background(Color.white)
.cornerRadius(16)
.shadow(radius: 2)
.animation(.default, value: selectedMedia?.id) // selectedMedia id
.cornerRadius(18)
.animation(.default, value: selectedMedia?.id)
}
// MARK: -
@ -323,7 +329,7 @@ struct MainUploadArea: View {
private var mediaPreviewSection: some View {
Group {
if !uploadManager.selectedMedia.isEmpty {
VStack(spacing: 8) {
VStack(spacing: 4) {
//
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack(spacing: 10) {
@ -337,9 +343,9 @@ struct MainUploadArea: View {
}
.padding(.horizontal)
}
.frame(height: 140)
.frame(height: 70)
}
.padding(.vertical, 8)
.padding(.top, 10)
}
}
}
@ -354,51 +360,85 @@ struct MainUploadArea: View {
VStack(spacing: 4) {
//
MediaPreview(media: media, uploadManager: uploadManager)
.frame(width: 80, height: 80)
.frame(width: 58, height: 58)
.cornerRadius(8)
.shadow(radius: 1)
.overlay(
//
ZStack {
Path { path in
let radius: CGFloat = 4
let width: CGFloat = 28
let height: CGFloat = 18
ZStack(alignment: .topLeading) {
//
ZStack(alignment: .topLeading) {
Path { path in
let radius: CGFloat = 4
let width: CGFloat = 14
let height: CGFloat = 10
//
path.move(to: CGPoint(x: 0, y: radius))
path.addQuadCurve(to: CGPoint(x: radius, y: 0),
control: CGPoint(x: 0, y: 0))
//
path.move(to: CGPoint(x: 0, y: radius))
path.addQuadCurve(to: CGPoint(x: radius, y: 0),
control: CGPoint(x: 0, y: 0))
//
path.addLine(to: CGPoint(x: width, y: 0))
//
path.addLine(to: CGPoint(x: width, y: 0))
//
path.addLine(to: CGPoint(x: width, y: height - radius))
//
path.addLine(to: CGPoint(x: width, y: height - radius))
//
path.addQuadCurve(to: CGPoint(x: width - radius, y: height),
control: CGPoint(x: width, y: height))
//
path.addQuadCurve(to: CGPoint(x: width - radius, y: height),
control: CGPoint(x: width, y: height))
//
path.addLine(to: CGPoint(x: 0, y: height))
//
path.addLine(to: CGPoint(x: 0, y: height))
//
path.closeSubpath()
//
path.closeSubpath()
}
.fill(Color(hex: "BEBEBE").opacity(0.6))
Text("\(index + 1)")
.font(.system(size: 8, weight: .bold))
.foregroundColor(.black)
.frame(width: 14, height: 10)
.offset(y: -1)
}
.fill(Color.white.opacity(0.5))
.frame(width: 14, height: 10, alignment: .topLeading)
.padding([.top, .leading], 2)
// Text
Text("\(index + 1)")
.font(.system(size: 12, weight: .bold))
.foregroundColor(.black)
.frame(width: 28, height: 18)
.offset(x: 0, y: -1)
//
if case .video(let url, _) = media, let videoURL = url as? URL {
VStack {
Spacer()
HStack {
Spacer()
Text(getVideoDuration(url: videoURL))
.font(.system(size: 8, weight: .bold))
.foregroundColor(.black)
.padding(.horizontal, 4)
.frame(height: 10)
.background(Color(hex: "BEBEBE").opacity(0.6))
.cornerRadius(2)
}
.padding([.trailing, .bottom], 0)
}
} else {
VStack {
Spacer()
HStack {
Spacer()
Text("")
.font(.system(size: 8, weight: .bold))
.foregroundColor(.black)
.padding(.horizontal, 4)
.frame(height: 10)
.background(Color(hex: "BEBEBE").opacity(0.6))
.cornerRadius(2)
}
.padding([.trailing, .bottom], 0)
}
.opacity(0)
}
}
.frame(width: 28, height: 18)
.offset(x: 0, y: 0)
.padding([.top, .leading], 0),
alignment: .topLeading
)
// mediaItemView onTapGesture
// .onTapGesture {
@ -429,18 +469,19 @@ struct MainUploadArea: View {
selectedMedia = nil
}
}) {
Image(systemName: "xmark.circle.fill")
.font(.system(size: 20))
.foregroundColor(.white.opacity(0.5))
Image(systemName: "xmark")
.font(.system(size: 10, weight: .bold))
.foregroundColor(.black)
.frame(width: 12, height: 12)
.background(
Circle()
.fill(Color.black.opacity(0.5))
.frame(width: 20, height: 20)
.fill(Color(hex: "BEBEBE").opacity(0.6))
.frame(width: 12, height: 12)
)
}
.offset(x: 6, y: -6) //
}
.padding(4)
.padding(.horizontal,4)
.contentShape(Rectangle())
}
@ -460,7 +501,8 @@ struct MainUploadArea: View {
ProgressView(value: progress, total: 1.0)
.progressViewStyle(LinearProgressViewStyle())
.frame(height: 3)
.tint(Color.themePrimary)
.padding(.horizontal, 4)
.padding(.bottom, 2)
}
.frame(width: 60)
@ -486,15 +528,15 @@ struct MainUploadArea: View {
private var addMoreButton: some View {
Button(action: { showMediaPicker = true }) {
Image(systemName: "plus")
.font(.system(size: 18))
.font(.system(size: 8, weight: .bold))
.foregroundColor(.black)
.frame(width: 80, height: 80)
.frame(width: 58, height: 58)
.background(Color.white)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(style: StrokeStyle(
lineWidth: 3,
lineWidth: 2,
dash: [4, 4]
))
.foregroundColor(Color.themePrimary)
@ -518,14 +560,21 @@ struct UploadPromptView: View {
.frame(width: 225, height: 225)
.contentShape(Rectangle())
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(style: StrokeStyle(
lineWidth: 5,
lineCap: .round,
dash: [12, 8]
))
.foregroundColor(Color.themePrimary)
)
ZStack {
RoundedRectangle(cornerRadius: 20)
.stroke(style: StrokeStyle(
lineWidth: 5,
lineCap: .round,
dash: [12, 8]
))
.foregroundColor(Color.themePrimary)
// Add plus icon in the center
Image(systemName: "plus")
.font(.system(size: 32, weight: .bold))
.foregroundColor(.black)
}
)
}
}
}
@ -637,11 +686,8 @@ struct MediaPreview: View {
ZStack {
// 1.
if let image = image {
loadedImageView(image)
//
if case .video = media {
playButton
ZStack(alignment: .bottomTrailing) {
loadedImageView(image)
}
// 100%
@ -690,14 +736,6 @@ struct MediaPreview: View {
.transition(.opacity.animation(.easeInOut(duration: 0.2)))
}
///
private var playButton: some View {
Image(systemName: "play.circle.fill")
.font(.system(size: 24))
.foregroundColor(.white)
.shadow(radius: 4)
}
///
private func errorView(error: Error) -> some View {
VStack(spacing: 8) {
@ -780,6 +818,15 @@ struct MediaPreview: View {
}
}
private func getVideoDuration(url: URL) -> String {
let asset = AVURLAsset(url: url)
let durationInSeconds = CMTimeGetSeconds(asset.duration)
guard durationInSeconds.isFinite else { return "0:00" }
let minutes = Int(durationInSeconds) / 60
let seconds = Int(durationInSeconds) % 60
return String(format: "%d:%02d", minutes, seconds)
}
// MARK: -
/// MediaType Identifiable
@ -795,6 +842,14 @@ extension MediaType: Identifiable {
}
}
extension TimeInterval {
var formattedDuration: String {
let minutes = Int(self) / 60
let seconds = Int(self) % 60
return String(format: "%d:%02d", minutes, seconds)
}
}
// MARK: -
struct MediaUploadView_Previews: PreviewProvider {