feat: 登录页面
This commit is contained in:
parent
d27f665009
commit
5df804d115
@ -7,7 +7,7 @@
|
||||
<key>wake.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
|
||||
BIN
wake/.DS_Store
vendored
BIN
wake/.DS_Store
vendored
Binary file not shown.
@ -70,7 +70,9 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
// 登录按钮
|
||||
NavigationLink(destination: LoginView()) {
|
||||
Button(action: {
|
||||
showLogin = true
|
||||
}) {
|
||||
Text("登录")
|
||||
.font(.headline)
|
||||
.padding(.horizontal, 16)
|
||||
@ -80,6 +82,9 @@ struct ContentView: View {
|
||||
.cornerRadius(8)
|
||||
}
|
||||
.padding(.trailing)
|
||||
.fullScreenCover(isPresented: $showLogin) {
|
||||
LoginView()
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
@ -22,8 +22,8 @@
|
||||
<string>Sign in with Apple is used to authenticate your account</string>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Quicksand x.ttf</string>
|
||||
<string>SankeiCutePopanime.ttf</string>
|
||||
<string>Inter.ttf</string>
|
||||
<string>Quicksand X.ttf</string>
|
||||
<string>Quicksand-Regular.ttf</string>
|
||||
<string>Quicksand-Bold.ttf</string>
|
||||
<string>Quicksand-SemiBold.ttf</string>
|
||||
|
||||
BIN
wake/Resources/.DS_Store
vendored
Normal file
BIN
wake/Resources/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
wake/Resources/Fonts/Inter.ttf
Normal file
BIN
wake/Resources/Fonts/Inter.ttf
Normal file
Binary file not shown.
Binary file not shown.
@ -32,6 +32,8 @@ struct Theme {
|
||||
static let textSecondary = Color(hex: "6B7280") // 次级文本色
|
||||
static let textTertiary = Color(hex: "9CA3AF") // 三级文本色
|
||||
static let textInverse = Color.white // 反色文本
|
||||
static let textMessage = Color(hex: "7B7B7B") // 注释颜色
|
||||
static let textMessageMain = Color(hex: "000000") // 注释主要颜色
|
||||
|
||||
// MARK: - 状态色
|
||||
static let success = Color(hex: "10B981") // 成功色
|
||||
@ -115,6 +117,8 @@ extension Color {
|
||||
static var themeSurface: Color { Theme.Colors.surface }
|
||||
static var themeTextPrimary: Color { Theme.Colors.textPrimary }
|
||||
static var themeTextSecondary: Color { Theme.Colors.textSecondary }
|
||||
static var themeTextMessage: Color { Theme.Colors.textMessage }
|
||||
static var themeTextMessageMain: Color { Theme.Colors.textMessageMain }
|
||||
}
|
||||
|
||||
// MARK: - 预览
|
||||
|
||||
@ -3,12 +3,10 @@ import SwiftUI
|
||||
// MARK: - 字体库枚举
|
||||
/// 定义应用中可用的字体库
|
||||
enum FontFamily: String, CaseIterable {
|
||||
case sankeiCute = "SankeiCutePopanime" // 可爱风格字体
|
||||
case quicksand = "Quicksand x" // 主题字体
|
||||
case quicksand = "Quicksand x"
|
||||
case quicksandBold = "Quicksand-Bold"
|
||||
case quicksandRegular = "Quicksand-Regular"
|
||||
// 后续添加新字体库时在这里添加新 case
|
||||
// 例如: case anotherFont = "AnotherFontName"
|
||||
case inter = "Inter"
|
||||
|
||||
/// 获取字体名称
|
||||
var name: String {
|
||||
@ -19,6 +17,7 @@ enum FontFamily: String, CaseIterable {
|
||||
// MARK: - 文本样式枚举
|
||||
/// 定义应用中使用的文本样式类型
|
||||
enum TypographyStyle {
|
||||
case largeTitle // 大标题
|
||||
case headline // 大标题
|
||||
case title // 标题
|
||||
case body // 正文
|
||||
@ -44,11 +43,12 @@ struct Typography {
|
||||
|
||||
/// 文本样式配置表
|
||||
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),
|
||||
.body: TypographyConfig(size: 16, weight: .regular, textStyle: .body),
|
||||
.subtitle: TypographyConfig(size: 14, weight: .medium, textStyle: .subheadline),
|
||||
.caption: TypographyConfig(size: 12, weight: .light, textStyle: .caption1),
|
||||
.caption: TypographyConfig(size: 12, weight: .regular, textStyle: .caption1),
|
||||
.footnote: TypographyConfig(size: 11, weight: .regular, textStyle: .footnote),
|
||||
.small: TypographyConfig(size: 10, weight: .regular, textStyle: .headline)
|
||||
]
|
||||
|
||||
95
wake/View/Components/AppleSignInButton.swift
Normal file
95
wake/View/Components/AppleSignInButton.swift
Normal file
@ -0,0 +1,95 @@
|
||||
import SwiftUI
|
||||
import AuthenticationServices
|
||||
import CryptoKit
|
||||
|
||||
/// 自定义的 Apple 登录按钮组件
|
||||
struct AppleSignInButton: View {
|
||||
// MARK: - 属性
|
||||
|
||||
/// 授权请求回调
|
||||
let onRequest: (ASAuthorizationAppleIDRequest) -> Void
|
||||
|
||||
/// 授权完成回调
|
||||
let onCompletion: (Result<ASAuthorization, Error>) -> Void
|
||||
|
||||
/// 按钮文字
|
||||
let buttonText: String
|
||||
|
||||
// MARK: - 初始化方法
|
||||
|
||||
init(buttonText: String = "Continue with Apple",
|
||||
onRequest: @escaping (ASAuthorizationAppleIDRequest) -> Void,
|
||||
onCompletion: @escaping (Result<ASAuthorization, Error>) -> Void) {
|
||||
self.buttonText = buttonText
|
||||
self.onRequest = onRequest
|
||||
self.onCompletion = onCompletion
|
||||
}
|
||||
|
||||
// MARK: - 视图主体
|
||||
|
||||
var body: some View {
|
||||
Button(action: handleSignIn) {
|
||||
HStack(alignment: .center, spacing: 8) {
|
||||
Image(systemName: "applelogo")
|
||||
.font(.system(size: 20, weight: .regular))
|
||||
Text(buttonText)
|
||||
.font(.system(size: 18, weight: .regular))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 60)
|
||||
.background(Color.white)
|
||||
.foregroundColor(.black)
|
||||
.cornerRadius(30)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 30)
|
||||
.stroke(Color.black, lineWidth: 1) // 使用黑色边框
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - 私有方法
|
||||
|
||||
private func handleSignIn() {
|
||||
let provider = ASAuthorizationAppleIDProvider()
|
||||
let request = provider.createRequest()
|
||||
request.requestedScopes = [.fullName, .email]
|
||||
|
||||
// 创建 nonce 用于安全验证
|
||||
let nonce = String.randomURLSafeString(length: 32)
|
||||
request.nonce = sha256(nonce)
|
||||
|
||||
// 调用请求回调
|
||||
onRequest(request)
|
||||
|
||||
// 创建并显示授权控制器
|
||||
let controller = ASAuthorizationController(authorizationRequests: [request])
|
||||
controller.delegate = Coordinator(onCompletion: onCompletion)
|
||||
controller.performRequests()
|
||||
}
|
||||
|
||||
private func sha256(_ input: String) -> String {
|
||||
let inputData = Data(input.utf8)
|
||||
let hashedData = SHA256.hash(data: inputData)
|
||||
return hashedData.compactMap { String(format: "%02x", $0) }.joined()
|
||||
}
|
||||
|
||||
// MARK: - 协调器
|
||||
|
||||
private class Coordinator: NSObject, ASAuthorizationControllerDelegate {
|
||||
let onCompletion: (Result<ASAuthorization, Error>) -> Void
|
||||
|
||||
init(onCompletion: @escaping (Result<ASAuthorization, Error>) -> Void) {
|
||||
self.onCompletion = onCompletion
|
||||
}
|
||||
|
||||
// 授权成功回调
|
||||
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
|
||||
onCompletion(.success(authorization))
|
||||
}
|
||||
|
||||
// 授权失败回调
|
||||
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
|
||||
onCompletion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -16,56 +16,54 @@ struct LoginView: View {
|
||||
// MARK: - Body
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
ZStack {
|
||||
// Background
|
||||
Color(red: 1.0, green: 0.67, blue: 0.15)
|
||||
.edgesIgnoringSafeArea(.all)
|
||||
ZStack {
|
||||
// Background
|
||||
Color(red: 1.0, green: 0.67, blue: 0.15)
|
||||
.ignoresSafeArea()
|
||||
|
||||
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)
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text("Hi, I'm MeMo!")
|
||||
.font(Typography.font(for: .largeTitle))
|
||||
.foregroundColor(.black)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 44)
|
||||
|
||||
Text("Welcome~")
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.black)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 24)
|
||||
.padding(.bottom, 20)
|
||||
Text("Welcome~")
|
||||
.font(Typography.font(for: .largeTitle))
|
||||
.foregroundColor(.black)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.bottom, 20)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
|
||||
VStack(spacing: 16) {
|
||||
Spacer()
|
||||
signInButton()
|
||||
termsAndPrivacyView()
|
||||
}
|
||||
.padding()
|
||||
.alert(isPresented: $showError) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("OK"))
|
||||
)
|
||||
}
|
||||
|
||||
if isLoading {
|
||||
loadingView()
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
.fullScreenCover(isPresented: $isLoggedIn) {
|
||||
NavigationStack {
|
||||
UserInfo()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
||||
|
||||
VStack(spacing: 16) {
|
||||
Spacer()
|
||||
signInButton()
|
||||
.padding(.horizontal, 24)
|
||||
termsAndPrivacyView()
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.alert(isPresented: $showError) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(errorMessage),
|
||||
dismissButton: .default(Text("OK"))
|
||||
)
|
||||
}
|
||||
|
||||
if isLoading {
|
||||
loadingView()
|
||||
}
|
||||
}
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.navigationBarHidden(true)
|
||||
.fullScreenCover(isPresented: $isLoggedIn) {
|
||||
NavigationStack {
|
||||
UserInfo()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -73,22 +71,22 @@ struct LoginView: View {
|
||||
// MARK: - Views
|
||||
|
||||
private func signInButton() -> some View {
|
||||
SignInWithAppleButton(
|
||||
onRequest: { request in
|
||||
let nonce = String.randomURLSafeString(length: 32)
|
||||
self.currentNonce = nonce
|
||||
request.requestedScopes = [.fullName, .email]
|
||||
request.nonce = self.sha256(nonce)
|
||||
},
|
||||
onCompletion: handleAppleSignIn
|
||||
)
|
||||
.signInWithAppleButtonStyle(.white)
|
||||
.frame(height: 50)
|
||||
.cornerRadius(25)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 25)
|
||||
.stroke(Color.black, lineWidth: 1)
|
||||
)
|
||||
AppleSignInButton { request in
|
||||
let nonce = String.randomURLSafeString(length: 32)
|
||||
self.currentNonce = nonce
|
||||
request.nonce = self.sha256(nonce)
|
||||
} onCompletion: { result in
|
||||
switch result {
|
||||
case .success(let authResults):
|
||||
print("✅ [Apple Sign In] 登录授权成功")
|
||||
if let appleIDCredential = authResults.credential as? ASAuthorizationAppleIDCredential {
|
||||
self.processAppleIDCredential(appleIDCredential)
|
||||
}
|
||||
case .failure(let error):
|
||||
print("❌ [Apple Sign In] 登录失败: \(error.localizedDescription)")
|
||||
self.handleSignInError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func termsAndPrivacyView() -> some View {
|
||||
@ -96,13 +94,13 @@ struct LoginView: View {
|
||||
HStack {
|
||||
Text("By continuing, you agree to our")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(.themeTextMessage)
|
||||
|
||||
Button("Terms of") {
|
||||
openURL("https://yourwebsite.com/terms")
|
||||
}
|
||||
.font(.caption2)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
}
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal, 24)
|
||||
@ -112,24 +110,24 @@ struct LoginView: View {
|
||||
openURL("https://yourwebsite.com/terms")
|
||||
}
|
||||
.font(.caption2)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
|
||||
Text("and")
|
||||
.foregroundColor(.secondary)
|
||||
.foregroundColor(.themeTextMessage)
|
||||
.font(.caption)
|
||||
|
||||
Button("Privacy Policy") {
|
||||
openURL("https://yourwebsite.com/privacy")
|
||||
}
|
||||
.font(.caption2)
|
||||
.foregroundColor(.blue)
|
||||
.foregroundColor(.themeTextMessageMain)
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.bottom, 24)
|
||||
.padding(.vertical, 12)
|
||||
}
|
||||
|
||||
private func loadingView() -> some View {
|
||||
|
||||
@ -44,9 +44,7 @@ struct WakeApp: App {
|
||||
// 根据登录状态显示不同视图
|
||||
if authState.isAuthenticated {
|
||||
// 已登录:显示userInfo页面
|
||||
// UserInfo()
|
||||
// .environmentObject(authState)
|
||||
MediaUploadDemo()
|
||||
UserInfo()
|
||||
.environmentObject(authState)
|
||||
} else {
|
||||
// 未登录:显示登录界面
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user