feat: 登录页面

This commit is contained in:
jinyaqiu 2025-08-21 14:21:31 +08:00
parent d27f665009
commit 5df804d115
14 changed files with 181 additions and 81 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -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

Binary file not shown.

View File

@ -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()

View File

@ -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

Binary file not shown.

Binary file not shown.

View File

@ -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: -

View File

@ -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)
]

View 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))
}
}
}

View File

@ -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 {

View File

@ -44,9 +44,7 @@ struct WakeApp: App {
//
if authState.isAuthenticated {
// userInfo
// UserInfo()
// .environmentObject(authState)
MediaUploadDemo()
UserInfo()
.environmentObject(authState)
} else {
//