feat: 用户信息页面
This commit is contained in:
parent
4f92f52bb7
commit
119b671838
Binary file not shown.
16
wake/Assets/Svg/Avatar.svg
Normal file
16
wake/Assets/Svg/Avatar.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 184 KiB |
@ -23,6 +23,7 @@ enum TypographyStyle {
|
|||||||
case subtitle // 副标题
|
case subtitle // 副标题
|
||||||
case caption // 说明文字
|
case caption // 说明文字
|
||||||
case footnote // 脚注
|
case footnote // 脚注
|
||||||
|
case small // 小号文字
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 字体配置结构体
|
// MARK: - 字体配置结构体
|
||||||
@ -41,13 +42,14 @@ struct Typography {
|
|||||||
|
|
||||||
/// 文本样式配置表
|
/// 文本样式配置表
|
||||||
private static let styleConfig: [TypographyStyle: TypographyConfig] = [
|
private static let styleConfig: [TypographyStyle: TypographyConfig] = [
|
||||||
.headline: TypographyConfig(size: 24, weight: .bold, textStyle: .headline),
|
.headline: TypographyConfig(size: 24, weight: .bold, textStyle: .headline),
|
||||||
.title: TypographyConfig(size: 20, weight: .semibold, textStyle: .title2),
|
.title: TypographyConfig(size: 20, weight: .semibold, textStyle: .title2),
|
||||||
.body: TypographyConfig(size: 16, weight: .regular, textStyle: .body),
|
.body: TypographyConfig(size: 16, weight: .regular, textStyle: .body),
|
||||||
.subtitle: TypographyConfig(size: 14, weight: .medium, textStyle: .subheadline),
|
.subtitle: TypographyConfig(size: 14, weight: .medium, textStyle: .subheadline),
|
||||||
.caption: TypographyConfig(size: 12, weight: .light, textStyle: .caption1),
|
.caption: TypographyConfig(size: 12, weight: .light, textStyle: .caption1),
|
||||||
.footnote: TypographyConfig(size: 11, weight: .regular, textStyle: .footnote)
|
.footnote: TypographyConfig(size: 11, weight: .regular, textStyle: .footnote),
|
||||||
]
|
.small: TypographyConfig(size: 10, weight: .regular, textStyle: .headline)
|
||||||
|
]
|
||||||
|
|
||||||
// MARK: - 公共方法
|
// MARK: - 公共方法
|
||||||
|
|
||||||
|
|||||||
0
wake/View/Components/Upload/Avatar.swift
Normal file
0
wake/View/Components/Upload/Avatar.swift
Normal file
@ -7,60 +7,67 @@ import CryptoKit
|
|||||||
struct LoginView: View {
|
struct LoginView: View {
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
|
|
||||||
@Environment(\.dismiss) private var dismiss
|
|
||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
@State private var showError = false
|
@State private var showError = false
|
||||||
@State private var errorMessage = ""
|
@State private var errorMessage = ""
|
||||||
@State private var currentNonce: String?
|
@State private var currentNonce: String?
|
||||||
|
@State private var isLoggedIn = false
|
||||||
|
|
||||||
// MARK: - Body
|
// MARK: - Body
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
NavigationStack {
|
||||||
// Background
|
ZStack {
|
||||||
Color(red: 1.0, green: 0.67, blue: 0.15)
|
// Background
|
||||||
.edgesIgnoringSafeArea(.all)
|
Color(red: 1.0, green: 0.67, blue: 0.15)
|
||||||
|
.edgesIgnoringSafeArea(.all)
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
|
||||||
Text("Hi, I'm MeMo!")
|
|
||||||
.font(.largeTitle)
|
|
||||||
.fontWeight(.semibold)
|
|
||||||
.foregroundColor(.black)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.padding(.leading, 24)
|
|
||||||
.padding(.top, 44)
|
|
||||||
|
|
||||||
Text("Welcome~")
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
.font(.largeTitle)
|
Text("Hi, I'm MeMo!")
|
||||||
.fontWeight(.semibold)
|
.font(.largeTitle)
|
||||||
.foregroundColor(.black)
|
.fontWeight(.semibold)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.foregroundColor(.black)
|
||||||
.padding(.leading, 24)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.padding(.bottom, 20)
|
.padding(.leading, 24)
|
||||||
|
.padding(.top, 44)
|
||||||
|
|
||||||
|
Text("Welcome~")
|
||||||
|
.font(.largeTitle)
|
||||||
|
.fontWeight(.semibold)
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(.leading, 24)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||||
|
|
||||||
Spacer()
|
VStack(spacing: 16) {
|
||||||
|
Spacer()
|
||||||
|
signInButton()
|
||||||
|
termsAndPrivacyView()
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.alert(isPresented: $showError) {
|
||||||
|
Alert(
|
||||||
|
title: Text("Error"),
|
||||||
|
message: Text(errorMessage),
|
||||||
|
dismissButton: .default(Text("OK"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isLoading {
|
||||||
|
loadingView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
.navigationBarHidden(true)
|
||||||
|
.fullScreenCover(isPresented: $isLoggedIn) {
|
||||||
VStack(spacing: 16) {
|
NavigationStack {
|
||||||
Spacer()
|
UserInfo()
|
||||||
signInButton()
|
}
|
||||||
termsAndPrivacyView()
|
|
||||||
}
|
|
||||||
.padding()
|
|
||||||
.alert(isPresented: $showError) {
|
|
||||||
Alert(
|
|
||||||
title: Text("Error"),
|
|
||||||
message: Text(errorMessage),
|
|
||||||
dismissButton: .default(Text("OK"))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if isLoading {
|
|
||||||
loadingView()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationBarHidden(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Views
|
// MARK: - Views
|
||||||
@ -140,6 +147,9 @@ struct LoginView: View {
|
|||||||
|
|
||||||
private func handleAppleSignIn(result: Result<ASAuthorization, Error>) {
|
private func handleAppleSignIn(result: Result<ASAuthorization, Error>) {
|
||||||
print("🔵 [Apple Sign In] 开始处理登录结果...")
|
print("🔵 [Apple Sign In] 开始处理登录结果...")
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.isLoggedIn = true
|
||||||
|
}
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let authResults):
|
case .success(let authResults):
|
||||||
print("✅ [Apple Sign In] 登录授权成功")
|
print("✅ [Apple Sign In] 登录授权成功")
|
||||||
@ -242,9 +252,9 @@ struct LoginView: View {
|
|||||||
// MARK: - Helpers
|
// MARK: - Helpers
|
||||||
|
|
||||||
private func handleSuccessfulAuthentication() {
|
private func handleSuccessfulAuthentication() {
|
||||||
print("✅ [Auth] 登录成功,准备关闭登录页面...")
|
print("✅ [Auth] 登录成功,准备跳转到用户信息页面...")
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.dismiss()
|
self.isLoggedIn = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +267,10 @@ struct LoginView: View {
|
|||||||
private func handleAuthenticationError(_ error: AFError) {
|
private func handleAuthenticationError(_ error: AFError) {
|
||||||
let errorMessage = error.localizedDescription
|
let errorMessage = error.localizedDescription
|
||||||
print("❌ [Auth] 认证错误: \(errorMessage)")
|
print("❌ [Auth] 认证错误: \(errorMessage)")
|
||||||
showError(message: "登录失败: \(errorMessage)")
|
DispatchQueue.main.async {
|
||||||
|
self.isLoggedIn = false
|
||||||
|
self.showError(message: "登录失败: \(errorMessage)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showError(message: String) {
|
private func showError(message: String) {
|
||||||
|
|||||||
146
wake/View/Owner/UserInfo/UserInfo.swift
Normal file
146
wake/View/Owner/UserInfo/UserInfo.swift
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct UserInfo: View {
|
||||||
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
// Sample user data - replace with your actual data model
|
||||||
|
@State private var userName = "MeMo"
|
||||||
|
@State private var userEmail = "memo@example.com"
|
||||||
|
@State private var notificationsEnabled = true
|
||||||
|
@State private var darkModeEnabled = false
|
||||||
|
@State private var showLogoutAlert = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
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))
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
.background(
|
||||||
|
LinearGradient(
|
||||||
|
gradient: Gradient(colors: [
|
||||||
|
Color(red: 1.0, green: 0.97, blue: 0.87),
|
||||||
|
.white,
|
||||||
|
Color(red: 1.0, green: 0.97, blue: 0.84)
|
||||||
|
]),
|
||||||
|
startPoint: .topLeading,
|
||||||
|
endPoint: .bottomTrailing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 10)
|
||||||
|
Spacer()
|
||||||
|
VStack(spacing: 20) {
|
||||||
|
// Title
|
||||||
|
Text("Add Your Avatar")
|
||||||
|
.font(Typography.font(for: .title))
|
||||||
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
|
|
||||||
|
// Avatar
|
||||||
|
ZStack {
|
||||||
|
SVGImage(svgName: "Avatar")
|
||||||
|
.frame(width: 225, height: 225)
|
||||||
|
|
||||||
|
// Fallback in case SVG fails to load
|
||||||
|
Image(systemName: "person.circle.fill")
|
||||||
|
.resizable()
|
||||||
|
.scaledToFit()
|
||||||
|
.frame(width: 225, height: 225)
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
.opacity(0.3)
|
||||||
|
}
|
||||||
|
.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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Button(action: {
|
||||||
|
// Action for second button
|
||||||
|
}) {
|
||||||
|
Text("Take a Photo")
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding()
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 25)
|
||||||
|
.fill(Color(red: 1.0, green: 0.973, blue: 0.871))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.background(Color(.white))
|
||||||
|
.cornerRadius(20)
|
||||||
|
Spacer()
|
||||||
|
Button(action: {
|
||||||
|
// Action for next button
|
||||||
|
}) {
|
||||||
|
Text("Next")
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding()
|
||||||
|
.foregroundColor(.black)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: 25)
|
||||||
|
.fill(Color(red: 1.0, green: 0.714, blue: 0.271))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Settings Row View
|
||||||
|
struct SettingsRow: View {
|
||||||
|
let icon: String
|
||||||
|
let title: String
|
||||||
|
let color: Color
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
Image(systemName: icon)
|
||||||
|
.resizable()
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
|
.frame(width: 20, height: 20)
|
||||||
|
.padding(6)
|
||||||
|
.background(color.opacity(0.1))
|
||||||
|
.foregroundColor(color)
|
||||||
|
.cornerRadius(6)
|
||||||
|
|
||||||
|
Text(title)
|
||||||
|
.padding(.leading, 5)
|
||||||
|
}
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Preview
|
||||||
|
struct UserInfo_Previews: PreviewProvider {
|
||||||
|
static var previews: some View {
|
||||||
|
UserInfo()
|
||||||
|
}
|
||||||
|
}
|
||||||
35
wake/Views/Utils/SVGImage.swift
Normal file
35
wake/Views/Utils/SVGImage.swift
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import WebKit
|
||||||
|
|
||||||
|
struct SVGImage: UIViewRepresentable {
|
||||||
|
let svgName: String
|
||||||
|
|
||||||
|
func makeUIView(context: Context) -> WKWebView {
|
||||||
|
let webView = WKWebView()
|
||||||
|
webView.isOpaque = false
|
||||||
|
webView.backgroundColor = .clear
|
||||||
|
webView.scrollView.isScrollEnabled = false
|
||||||
|
|
||||||
|
// 尝试从根目录加载SVG文件
|
||||||
|
if let url = Bundle.main.url(forResource: svgName, withExtension: "svg") {
|
||||||
|
print("SVG URL found: \(url.path)")
|
||||||
|
let request = URLRequest(url: url)
|
||||||
|
webView.load(request)
|
||||||
|
} else {
|
||||||
|
print("Error: Failed to find SVG file with name: \(svgName).svg in main bundle")
|
||||||
|
// 打印所有可用的SVG文件
|
||||||
|
if let resourceURL = Bundle.main.resourceURL,
|
||||||
|
let files = try? FileManager.default.contentsOfDirectory(at: resourceURL, includingPropertiesForKeys: nil) {
|
||||||
|
let svgFiles = files.filter { $0.pathExtension.lowercased() == "svg" }
|
||||||
|
print("Available SVG files: \(svgFiles)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return webView
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateUIView(_ uiView: WKWebView, context: Context) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Usage:
|
||||||
|
// SVGImage(svgName: "Avatar")
|
||||||
Loading…
x
Reference in New Issue
Block a user