feat: 头像选择上传
This commit is contained in:
parent
5df804d115
commit
44de40cf83
BIN
wake/Assets/.DS_Store
vendored
Normal file
BIN
wake/Assets/.DS_Store
vendored
Normal file
Binary file not shown.
@ -20,6 +20,7 @@ enum TypographyStyle {
|
||||
case largeTitle // 大标题
|
||||
case headline // 大标题
|
||||
case title // 标题
|
||||
case title2 // 标题
|
||||
case body // 正文
|
||||
case subtitle // 副标题
|
||||
case caption // 说明文字
|
||||
@ -39,13 +40,14 @@ struct Typography {
|
||||
// MARK: - 配置
|
||||
|
||||
/// 默认字体库
|
||||
private static let defaultFontFamily: FontFamily = .quicksand
|
||||
private static let defaultFontFamily: FontFamily = .quicksandRegular
|
||||
|
||||
/// 文本样式配置表
|
||||
private static let styleConfig: [TypographyStyle: TypographyConfig] = [
|
||||
.largeTitle: TypographyConfig(size: 32, weight: .heavy, textStyle: .largeTitle),
|
||||
.headline: TypographyConfig(size: 24, weight: .bold, textStyle: .headline),
|
||||
.title: TypographyConfig(size: 20, weight: .semibold, textStyle: .title2),
|
||||
.title2: TypographyConfig(size: 18, weight: .bold, textStyle: .title2),
|
||||
.body: TypographyConfig(size: 16, weight: .regular, textStyle: .body),
|
||||
.subtitle: TypographyConfig(size: 14, weight: .medium, textStyle: .subheadline),
|
||||
.caption: TypographyConfig(size: 12, weight: .regular, textStyle: .caption1),
|
||||
|
||||
84
wake/View/Owner/UserInfo/AvatarPicker.swift
Normal file
84
wake/View/Owner/UserInfo/AvatarPicker.swift
Normal file
@ -0,0 +1,84 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct AvatarPicker: View {
|
||||
@StateObject private var uploadManager = MediaUploadManager()
|
||||
@State private var showMediaPicker = false
|
||||
@State private var isUploading = false
|
||||
@Binding var selectedImage: UIImage?
|
||||
|
||||
public init(selectedImage: Binding<UIImage?>) {
|
||||
self._selectedImage = selectedImage
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
// Avatar Image
|
||||
Button(action: {
|
||||
showMediaPicker = true
|
||||
}) {
|
||||
ZStack {
|
||||
if let selectedImage = selectedImage {
|
||||
Image(uiImage: selectedImage)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 225, height: 225)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 20))
|
||||
} else {
|
||||
// Default SVG avatar
|
||||
SVGImage(svgName: "Avatar")
|
||||
.frame(width: 225, height: 225)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
|
||||
if isUploading {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle())
|
||||
.scaleEffect(1.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upload button
|
||||
Button(action: {
|
||||
showMediaPicker = true
|
||||
}) {
|
||||
Text("Upload from Gallery")
|
||||
.font(Typography.font(for: .subtitle, family: .inter))
|
||||
.fontWeight(.regular)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.foregroundColor(.black)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color.themePrimaryLight)
|
||||
)
|
||||
}
|
||||
.frame(width: .infinity)
|
||||
}
|
||||
.sheet(isPresented: $showMediaPicker) {
|
||||
MediaPicker(
|
||||
selectedMedia: $uploadManager.selectedMedia,
|
||||
imageSelectionLimit: 1,
|
||||
videoSelectionLimit: 0,
|
||||
allowedMediaTypes: .imagesOnly,
|
||||
selectionMode: .single,
|
||||
onDismiss: {
|
||||
showMediaPicker = false
|
||||
if !uploadManager.selectedMedia.isEmpty {
|
||||
isUploading = true
|
||||
uploadManager.startUpload()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.onChange(of: uploadManager.uploadStatus) { _ in
|
||||
if let firstMedia = uploadManager.selectedMedia.first,
|
||||
case .image(let image) = firstMedia,
|
||||
uploadManager.isAllUploaded {
|
||||
selectedImage = image
|
||||
isUploading = false
|
||||
uploadManager.clearAllMedia()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,9 +13,20 @@ struct UserInfo: View {
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
ReturnButton {
|
||||
print("返回")
|
||||
}
|
||||
Spacer()
|
||||
Text("Complete Your Profile")
|
||||
.font(Typography.font(for: .title2, family: .quicksandBold))
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
HStack(spacing: 20) {
|
||||
Text("Choose a photo as your avatar, and we'll generate a video mystery box for you.")
|
||||
.font(Typography.font(for: .small))
|
||||
.font(Typography.font(for: .caption))
|
||||
.foregroundColor(.black)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.vertical, 10)
|
||||
@ -31,59 +42,32 @@ struct UserInfo: View {
|
||||
)
|
||||
)
|
||||
}
|
||||
.padding(.vertical, 10)
|
||||
.padding(10)
|
||||
Spacer()
|
||||
VStack(spacing: 20) {
|
||||
// Title
|
||||
Text("Add Your Avatar")
|
||||
.font(Typography.font(for: .title))
|
||||
.font(Typography.font(for: .body, family: .quicksandBold))
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
|
||||
// Avatar
|
||||
ZStack {
|
||||
// Show either the SVG or the uploaded image
|
||||
if let avatarImage = avatarImage {
|
||||
Image(uiImage: avatarImage)
|
||||
.resizable()
|
||||
.scaledToFill()
|
||||
.frame(width: 200, height: 200)
|
||||
.clipShape(Circle())
|
||||
} else {
|
||||
SVGImage(svgName: "Avatar")
|
||||
.frame(width: 200, height: 200)
|
||||
}
|
||||
|
||||
// Make sure the AvatarUploader is on top and covers the entire area
|
||||
AvatarUploader(selectedImage: $avatarImage, size: 200)
|
||||
.contentShape(Rectangle()) // This makes the entire area tappable
|
||||
}
|
||||
.frame(width: 200, height: 200)
|
||||
.padding(.vertical, 20)
|
||||
|
||||
// Buttons
|
||||
Button(action: {
|
||||
// Action for first button
|
||||
}) {
|
||||
Text("Upload from Gallery")
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.foregroundColor(.black)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 25)
|
||||
.fill(Color(red: 1.0, green: 0.973, blue: 0.871))
|
||||
)
|
||||
AvatarPicker(selectedImage: $avatarImage)
|
||||
}
|
||||
.padding(.top, 20)
|
||||
|
||||
Button(action: {
|
||||
// Action for second button
|
||||
}) {
|
||||
Text("Take a Photo")
|
||||
.font(Typography.font(for: .subtitle, family: .inter))
|
||||
.fontWeight(.regular)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.foregroundColor(.black)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 25)
|
||||
.fill(Color(red: 1.0, green: 0.973, blue: 0.871))
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color.themePrimaryLight)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -94,30 +78,22 @@ struct UserInfo: View {
|
||||
Button(action: {
|
||||
// Action for next button
|
||||
}) {
|
||||
Text("Next")
|
||||
Text("Continue")
|
||||
.font(Typography.font(for: .body))
|
||||
.fontWeight(.bold)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
.padding(16)
|
||||
.foregroundColor(.black)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 25)
|
||||
.fill(Color(red: 1.0, green: 0.714, blue: 0.271))
|
||||
.fill(Color.themePrimary)
|
||||
)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
.padding()
|
||||
.navigationTitle("Complete Your Profile")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.background(Color(red: 0.98, green: 0.98, blue: 0.98)) // #FAFAFA
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarLeading) {
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}) {
|
||||
Image(systemName: "chevron.left")
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,20 @@ struct SVGImage: UIViewRepresentable {
|
||||
webView.scrollView.isScrollEnabled = false
|
||||
webView.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
|
||||
if let url = Bundle.main.url(forResource: svgName, withExtension: "svg") {
|
||||
// 1. 获取 SVG 文件路径(注意:移除了 inDirectory 参数)
|
||||
guard let path = Bundle.main.path(forResource: svgName, ofType: "svg") else {
|
||||
print("❌ 无法找到 SVG 文件: \(svgName).svg")
|
||||
// 打印所有可用的资源文件,用于调试
|
||||
if let resourcePath = Bundle.main.resourcePath {
|
||||
print("可用的资源文件: \(try? FileManager.default.contentsOfDirectory(atPath: resourcePath))")
|
||||
}
|
||||
return webView
|
||||
}
|
||||
|
||||
// 2. 创建文件 URL
|
||||
let fileURL = URL(fileURLWithPath: path)
|
||||
|
||||
// 3. 创建 HTML 字符串
|
||||
let htmlString = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
@ -24,47 +37,32 @@ struct SVGImage: UIViewRepresentable {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: transparent;
|
||||
}
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="width:100%; height:100%; display:flex; align-items:center; justify-content:center;">
|
||||
<img src="\(url.absoluteString)" style="max-width:100%; max-height:100%;" />
|
||||
<img src="\(fileURL.lastPathComponent)" />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
webView.loadHTMLString(htmlString, baseURL: nil)
|
||||
}
|
||||
|
||||
// 4. 加载 HTML 字符串
|
||||
webView.loadHTMLString(htmlString, baseURL: fileURL.deletingLastPathComponent())
|
||||
return webView
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: WKWebView, context: Context) {}
|
||||
}
|
||||
|
||||
// MARK: - View Modifier for SVG Image
|
||||
struct SVGImageModifier: ViewModifier {
|
||||
let size: CGSize
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.frame(width: size.width, height: size.height)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func svgImageStyle(size: CGSize) -> some View {
|
||||
self.modifier(SVGImageModifier(size: size))
|
||||
}
|
||||
}
|
||||
|
||||
// Usage:
|
||||
// SVGImage(svgName: "Avatar")
|
||||
|
||||
@ -48,7 +48,9 @@ struct WakeApp: App {
|
||||
.environmentObject(authState)
|
||||
} else {
|
||||
// 未登录:显示登录界面
|
||||
ContentView()
|
||||
// ContentView()
|
||||
// .environmentObject(authState)
|
||||
UserInfo()
|
||||
.environmentObject(authState)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user