feat: token

This commit is contained in:
jinyaqiu 2025-08-20 11:47:56 +08:00
parent be25d07d83
commit fdd2715ec8
10 changed files with 938 additions and 126 deletions

View File

@ -0,0 +1,49 @@
import SwiftUI
import Combine
///
public class AuthState: ObservableObject {
@Published public var isAuthenticated: Bool = false {
didSet {
print("🔔 认证状态变更: \(isAuthenticated ? "已登录" : "已登出")")
}
}
@Published public var isLoading = false
@Published public var errorMessage: String?
@Published public var user: User?
//
public static let shared = AuthState()
private init() {}
///
public func login(user: User? = nil) {
if let user = user {
self.user = user
}
isAuthenticated = true
errorMessage = nil
}
///
public func logout() {
print("👋 用户登出")
user = nil
isAuthenticated = false
//
TokenManager.shared.clearTokens()
UserDefaults.standard.removeObject(forKey: "lastLoginUser")
}
///
public func setLoading(_ loading: Bool) {
isLoading = loading
}
///
public func setError(_ message: String) {
errorMessage = message
}
}

View File

@ -7,12 +7,10 @@ public enum APIConfig {
<<<<<<< HEAD <<<<<<< HEAD
public static let baseURL = "https://api-dev.memorywake.com:31274/api/v1" public static let baseURL = "https://api-dev.memorywake.com:31274/api/v1"
/// token - Keychain /// token
public static var authToken: String { public static var authToken: String {
let token = KeychainHelper.getAccessToken() ?? "" let token = KeychainHelper.getAccessToken() ?? ""
if !token.isEmpty { if token.isEmpty {
print("🔑 [APIConfig] 当前访问令牌: \(token.prefix(10))...") // 10
} else {
print("⚠️ [APIConfig] 未找到访问令牌") print("⚠️ [APIConfig] 未找到访问令牌")
} }
return token return token
@ -42,11 +40,17 @@ public enum APIConfig {
>>>>>>> 1814789 (feat: ) >>>>>>> 1814789 (feat: )
/// ///
public static var authHeaders: [String: String] { public static var authHeaders: [String: String] {
return [ let token = authToken
"Authorization": "Bearer \(authToken)", var headers = [
"Content-Type": "application/json", "Content-Type": "application/json",
"Accept": "application/json" "Accept": "application/json"
] ]
if !token.isEmpty {
headers["Authorization"] = "Bearer \(token)"
}
return headers
} }
<<<<<<< HEAD <<<<<<< HEAD
<<<<<<< HEAD <<<<<<< HEAD

View File

@ -1,34 +0,0 @@
import SwiftUI
class Network: ObservableObject {
@Published var users: [User] = []
func getUsers() {
guard let url = URL(string: "http://192.168.31.156:31646/api/iam/login/password-login") else { fatalError("Missing URL") }
let urlRequest = URLRequest(url: url)
let dataTask = URLSession.shared.dataTask(with: urlRequest) { (data, response, error) in
if let error = error {
print("Request error: ", error)
return
}
guard let response = response as? HTTPURLResponse else { return }
if response.statusCode == 200 {
guard let data = data else { return }
DispatchQueue.main.async {
do {
let decodedUsers = try JSONDecoder().decode([User].self, from: data)
self.users = decodedUsers
} catch let error {
print("Error decoding: ", error)
}
}
}
}
dataTask.resume()
}
}

View File

@ -0,0 +1,207 @@
import Foundation
enum NetworkError: Error {
case invalidURL
case noData
case decodingError(Error)
case serverError(String)
case unauthorized
case other(Error)
case networkError(Error)
case unknownError(Error)
var localizedDescription: String {
switch self {
case .invalidURL:
return "无效的URL"
case .noData:
return "没有收到数据"
case .decodingError(let error):
return "数据解析错误: \(error.localizedDescription)"
case .serverError(let message):
return "服务器错误: \(message)"
case .unauthorized:
return "未授权,请重新登录"
case .other(let error):
return error.localizedDescription
case .networkError(let error):
return "网络请求错误: \(error.localizedDescription)"
case .unknownError(let error):
return "未知错误: \(error.localizedDescription)"
}
}
}
class NetworkService {
static let shared = NetworkService()
//
private let defaultHeaders: [String: String] = [
"Content-Type": "application/json",
"Accept": "application/json"
]
private init() {}
// MARK: -
private func request<T: Decodable>(
_ method: String,
path: String,
parameters: [String: Any]? = nil,
headers: [String: String]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
// URL
guard let url = URL(string: APIConfig.baseURL + path) else {
completion(.failure(.invalidURL))
return
}
//
var request = URLRequest(url: url)
request.httpMethod = method
// -
defaultHeaders.forEach { key, value in
request.setValue(value, forHTTPHeaderField: key)
}
//
APIConfig.authHeaders.forEach { key, value in
request.setValue(value, forHTTPHeaderField: key)
}
//
headers?.forEach { key, value in
request.setValue(value, forHTTPHeaderField: key)
}
// POST/PUT
if let parameters = parameters, (method == "POST" || method == "PUT") {
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters)
} catch {
completion(.failure(.other(error)))
return
}
}
//
print("🌐 [Network] \(method) \(url.absoluteString)")
if let headers = request.allHTTPHeaderFields {
print("📤 Headers: \(headers)")
}
if let body = request.httpBody, let bodyString = String(data: body, encoding: .utf8) {
print("📦 Body: \(bodyString)")
}
//
let task = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
//
self?.handleResponse(data: data, response: response, error: error, completion: completion)
}
//
task.resume()
}
private func handleResponse<T: Decodable>(
data: Data?,
response: URLResponse?,
error: Error?,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
//
if let httpResponse = response as? HTTPURLResponse {
print("📥 [Network] Status: \(httpResponse.statusCode) \(HTTPURLResponse.localizedString(forStatusCode: httpResponse.statusCode))")
if let headers = httpResponse.allHeaderFields as? [String: Any] {
print("📥 Headers: \(headers)")
}
//
if !(200...299).contains(httpResponse.statusCode) {
print("❌ [Network] 请求失败,状态码: \(httpResponse.statusCode)")
if let data = data, let errorResponse = String(data: data, encoding: .utf8) {
print("❌ [Network] 错误响应: \(errorResponse)")
}
}
}
//
if let error = error {
print("❌ [Network] 网络请求错误: \(error.localizedDescription)")
completion(.failure(.networkError(error)))
return
}
//
guard let data = data else {
print("❌ [Network] 没有收到数据")
completion(.failure(.noData))
return
}
//
if let responseString = String(data: data, encoding: .utf8) {
print("📥 [Network] 响应数据: \(responseString)")
}
do {
// JSON
let decoder = JSONDecoder()
let result = try decoder.decode(T.self, from: data)
completion(.success(result))
} catch let decodingError as DecodingError {
print("❌ [Network] JSON解析失败: \(decodingError.localizedDescription)")
if let dataString = String(data: data, encoding: .utf8) {
print("📋 [Network] 原始响应: \(dataString)")
}
completion(.failure(.decodingError(decodingError)))
} catch {
print("❌ [Network] 未知错误: \(error.localizedDescription)")
completion(.failure(.unknownError(error)))
}
}
// MARK: -
/// GET
func get<T: Decodable>(
path: String,
parameters: [String: Any]? = nil,
headers: [String: String]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
request("GET", path: path, parameters: parameters, headers: headers, completion: completion)
}
/// POST
func post<T: Decodable>(
path: String,
parameters: [String: Any]? = nil,
headers: [String: String]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
request("POST", path: path, parameters: parameters, headers: headers, completion: completion)
}
/// DELETE
func delete<T: Decodable>(
path: String,
parameters: [String: Any]? = nil,
headers: [String: String]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
request("DELETE", path: path, parameters: parameters, headers: headers, completion: completion)
}
/// PUT
func put<T: Decodable>(
path: String,
parameters: [String: Any]? = nil,
headers: [String: String]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
request("PUT", path: path, parameters: parameters, headers: headers, completion: completion)
}
}

View File

@ -0,0 +1,424 @@
import Foundation
/// Token
///
class TokenManager {
///
static let shared = TokenManager()
/// tokentoken
/// 300token5
private let tokenValidityThreshold: TimeInterval = 300
///
private init() {}
// MARK: - Token
/// 访
var hasToken: Bool {
return KeychainHelper.getAccessToken()?.isEmpty == false
}
// MARK: - Token
/// token
/// - token
/// - token
/// - token
/// - Parameter completion: /
/// - isValid: token
/// - error:
func validateAndRefreshTokenIfNeeded(completion: @escaping (Bool, Error?) -> Void) {
// 1. token
guard let token = KeychainHelper.getAccessToken(), !token.isEmpty else {
// token
let error = NSError(
domain: "TokenManager",
code: 401,
userInfo: [NSLocalizedDescriptionKey: "未找到访问令牌"]
)
completion(false, error)
return
}
// 2. token
if isTokenValid(token) {
// token
completion(true, nil)
return
}
// 3. token
refreshToken { [weak self] success, error in
if success {
//
completion(true, nil)
} else {
//
let finalError = error ?? NSError(
domain: "TokenManager",
code: 401,
userInfo: [NSLocalizedDescriptionKey: "Token刷新失败"]
)
completion(false, finalError)
}
}
}
/// token
/// - Parameter token: token
/// - Returns: tokentruefalse
///
/// tokentokentoken
///
/// - Note: tokentoken
public func isTokenValid(_ token: String) -> Bool {
print("🔍 TokenManager: 开始验证token...")
// 1. token
guard !token.isEmpty else {
print("❌ TokenManager: Token为空")
return false
}
// 2. token
if let expiryDate = getTokenExpiryDate(token) {
print("⏰ TokenManager: Token过期时间: \(expiryDate)")
if Date() > expiryDate {
print("❌ TokenManager: Token已过期")
return false
}
}
// 3.
let semaphore = DispatchSemaphore(value: 0)
var isValid = false
var requestCompleted = false
print("🌐 TokenManager: 发送验证请求到服务器...")
// 4.
let task = URLSession.shared.dataTask(with: createValidationRequest(token: token)) { data, response, error in
defer {
requestCompleted = true
semaphore.signal()
}
//
if let error = error {
print("❌ TokenManager: 验证请求错误: \(error.localizedDescription)")
return
}
//
guard let httpResponse = response as? HTTPURLResponse else {
print("❌ TokenManager: 无效的服务器响应")
return
}
print("📡 TokenManager: 服务器响应状态码: \(httpResponse.statusCode)")
//
guard (200...299).contains(httpResponse.statusCode) else {
print("❌ TokenManager: 服务器返回错误状态码: \(httpResponse.statusCode)")
return
}
//
if let data = data, !data.isEmpty {
do {
//
let response = try JSONDecoder().decode(IdentityCheckResponse.self, from: data)
isValid = response.isValid
print("✅ TokenManager: Token验证\(isValid ? "成功" : "失败")")
} catch {
print("❌ TokenManager: 解析响应数据失败: \(error.localizedDescription)")
// 200token
isValid = true
print(" TokenManager: 状态码200假设token有效")
}
} else {
// 200token
print(" TokenManager: 没有返回数据但状态码为200假设token有效")
isValid = true
}
}
task.resume()
// 5. 10
let timeoutResult = semaphore.wait(timeout: .now() + 15)
//
if !requestCompleted && timeoutResult == .timedOut {
print("⚠️ TokenManager: 验证请求超时")
task.cancel()
return false
}
return isValid
}
///
private func createValidationRequest(token: String) -> URLRequest {
let url = URL(string: APIConfig.baseURL + "/iam/identity-check")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Accept")
return request
}
/// token
private func getTokenExpiryDate(_ token: String) -> Date? {
// JWTtoken
// JWT token
let parts = token.components(separatedBy: ".")
guard parts.count > 1, let payloadData = base64UrlDecode(parts[1]) else {
return nil
}
do {
if let payload = try JSONSerialization.jsonObject(with: payloadData) as? [String: Any],
let exp = payload["exp"] as? TimeInterval {
return Date(timeIntervalSince1970: exp)
}
} catch {
print("❌ TokenManager: 解析token过期时间失败: \(error.localizedDescription)")
}
return nil
}
private func base64UrlDecode(_ base64Url: String) -> Data? {
var base64 = base64Url
.replacingOccurrences(of: "-", with: "+")
.replacingOccurrences(of: "_", with: "/")
//
let length = Double(base64.lengthOfBytes(using: .utf8))
let requiredLength = 4 * ceil(length / 4.0)
let paddingLength = requiredLength - length
if paddingLength > 0 {
let padding = "".padding(toLength: Int(paddingLength), withPad: "=", startingAt: 0)
base64 += padding
}
return Data(base64Encoded: base64)
}
/// token
/// - Parameter completion:
/// - success:
/// - error:
private func refreshToken(completion: @escaping (Bool, Error?) -> Void) {
//
guard let refreshToken = KeychainHelper.getRefreshToken(), !refreshToken.isEmpty else {
//
let error = NSError(
domain: "TokenManager",
code: 401,
userInfo: [NSLocalizedDescriptionKey: "未找到刷新令牌"]
)
completion(false, error)
return
}
//
let parameters: [String: Any] = [
"refresh_token": refreshToken,
"grant_type": "refresh_token"
]
//
NetworkService.shared.postWithToken(path: "/v1/iam/access-token-refresh", parameters: parameters) {
(result: Result<TokenResponse, NetworkError>) in
switch result {
case .success(let tokenResponse):
// 1. 访
KeychainHelper.saveAccessToken(tokenResponse.accessToken)
// 2.
if let newRefreshToken = tokenResponse.refreshToken {
KeychainHelper.saveRefreshToken(newRefreshToken)
}
print("✅ Token刷新成功")
completion(true, nil)
case .failure(let error):
print("❌ Token刷新失败: \(error.localizedDescription)")
// token
KeychainHelper.clearTokens()
completion(false, error)
}
}
}
/// token
func clearTokens() {
print("🗑️ TokenManager: 清除所有 token")
KeychainHelper.clearTokens()
// token
UserDefaults.standard.removeObject(forKey: "tokenExpiryDate")
UserDefaults.standard.synchronize()
}
}
// MARK: - Token
/// token
private struct TokenResponse: Codable {
/// 访
let accessToken: String
///
let refreshToken: String?
///
let expiresIn: TimeInterval?
/// Bearer
let tokenType: String?
// 使CodingKeys
enum CodingKeys: String, CodingKey {
case accessToken = "access_token"
case refreshToken = "refresh_token"
case expiresIn = "expires_in"
case tokenType = "token_type"
}
}
// MARK: -
///
private struct IdentityCheckResponse: Codable {
///
let isValid: Bool
/// ID
let userId: String?
///
let expiresAt: Date?
enum CodingKeys: String, CodingKey {
case isValid = "is_valid"
case userId = "user_id"
case expiresAt = "expires_at"
}
}
// MARK: - NetworkService
/// NetworkServicetoken
extension NetworkService {
// MARK: -
/// token
/// - Parameters:
/// - method: HTTPGET/POST/PUT/DELETE
/// - path:
/// - parameters:
/// - headers:
/// - completion:
func requestWithToken<T: Decodable>(
_ method: String,
path: String,
parameters: [String: Any]? = nil,
headers: [String: String]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
// 1. token
TokenManager.shared.validateAndRefreshTokenIfNeeded { [weak self] isValid, error in
guard let self = self else { return }
if isValid {
// 2. token
switch method.uppercased() {
case "GET":
self.get(path: path, parameters: parameters, headers: headers, completion: completion)
case "POST":
self.post(path: path, parameters: parameters, headers: headers, completion: completion)
case "PUT":
self.put(path: path, parameters: parameters, headers: headers, completion: completion)
case "DELETE":
self.delete(path: path, parameters: parameters, headers: headers, completion: completion)
default:
let error = NSError(
domain: "NetworkService",
code: 400,
userInfo: [NSLocalizedDescriptionKey: "不支持的HTTP方法: \(method)"]
)
completion(.failure(.other(error)))
}
} else {
// 3. token
let error = error ?? NSError(
domain: "NetworkService",
code: 401,
userInfo: [NSLocalizedDescriptionKey: "未授权访问"]
)
DispatchQueue.main.async {
completion(.failure(.unauthorized))
}
// 4.
NotificationCenter.default.post(name: .userDidLogout, object: nil)
}
}
}
// MARK: - 便
/// tokenGET
func getWithToken<T: Decodable>(
path: String,
parameters: [String: Any]? = nil,
headers: [String: String]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
requestWithToken("GET", path: path, parameters: parameters, headers: headers, completion: completion)
}
/// tokenPOST
func postWithToken<T: Decodable>(
path: String,
parameters: [String: Any]? = nil,
headers: [String: String]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
requestWithToken("POST", path: path, parameters: parameters, headers: headers, completion: completion)
}
/// tokenPUT
func putWithToken<T: Decodable>(
path: String,
parameters: [String: Any]? = nil,
headers: [String: String]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
requestWithToken("PUT", path: path, parameters: parameters, headers: headers, completion: completion)
}
/// tokenDELETE
func deleteWithToken<T: Decodable>(
path: String,
parameters: [String: Any]? = nil,
headers: [String: String]? = nil,
completion: @escaping (Result<T, NetworkError>) -> Void
) {
requestWithToken("DELETE", path: path, parameters: parameters, headers: headers, completion: completion)
}
}
// MARK: -
/// 使
extension Notification.Name {
///
/// token
static let userDidLogout = Notification.Name("UserDidLogoutNotification")
}

View File

@ -1,31 +1,61 @@
import Foundation import Foundation
struct User: Identifiable, Decodable { public struct User: Identifiable, Decodable {
var id: Int public var id: Int
var name: String public var name: String
var username: String public var username: String
var email: String public var email: String
var address: Address public var address: Address
var phone: String public var phone: String
var website: String public var website: String
var company: Company public var company: Company
struct Address: Decodable { public init(id: Int, name: String, username: String, email: String, address: Address, phone: String, website: String, company: Company) {
var street: String self.id = id
var suite: String self.name = name
var city: String self.username = username
var zipcode: String self.email = email
var geo: Geo self.address = address
self.phone = phone
self.website = website
self.company = company
}
struct Geo: Decodable { public struct Address: Decodable {
var lat: String public var street: String
var lng: String public var suite: String
public var city: String
public var zipcode: String
public var geo: Geo
public init(street: String, suite: String, city: String, zipcode: String, geo: Geo) {
self.street = street
self.suite = suite
self.city = city
self.zipcode = zipcode
self.geo = geo
}
public struct Geo: Decodable {
public var lat: String
public var lng: String
public init(lat: String, lng: String) {
self.lat = lat
self.lng = lng
}
} }
} }
struct Company: Decodable { public struct Company: Decodable {
var name: String public var name: String
var catchPhrase: String public var catchPhrase: String
var bs: String public var bs: String
public init(name: String, catchPhrase: String, bs: String) {
self.name = name
self.catchPhrase = catchPhrase
self.bs = bs
}
} }
} }

View File

@ -10,6 +10,7 @@ struct UserInfo: View {
@State private var darkModeEnabled = false @State private var darkModeEnabled = false
@State private var showLogoutAlert = false @State private var showLogoutAlert = false
@State private var avatarImage: UIImage? // Add this line @State private var avatarImage: UIImage? // Add this line
@State private var name: String = ""
var body: some View { var body: some View {
VStack(spacing: 0) { VStack(spacing: 0) {
@ -39,53 +40,49 @@ struct UserInfo: View {
.font(Typography.font(for: .title)) .font(Typography.font(for: .title))
.frame(maxWidth: .infinity, alignment: .center) .frame(maxWidth: .infinity, alignment: .center)
// Avatar // Avatar section
ZStack { VStack {
// Show either the SVG or the uploaded image Text("your name")
if let avatarImage = avatarImage { .font(.headline)
Image(uiImage: avatarImage) .padding(.bottom, 10)
// Avatar image or placeholder
Circle()
.fill(Color.gray.opacity(0.3))
.frame(width: 100, height: 100)
.overlay(
Image(systemName: "person.fill")
.resizable() .resizable()
.scaledToFill() .scaledToFit()
.frame(width: 200, height: 200) .foregroundColor(.white)
.clipShape(Circle()) .padding(30)
} else { )
SVGImage(svgName: "Avatar")
.frame(width: 200, height: 200)
} }
.padding(.top, 30)
// Make sure the AvatarUploader is on top and covers the entire area // Name input field
AvatarUploader(selectedImage: $avatarImage, size: 200) TextField("Enter your name", text: $name)
.contentShape(Rectangle()) // This makes the entire area tappable .textFieldStyle(RoundedBorderTextFieldStyle())
} .padding(.horizontal, 40)
.frame(width: 200, height: 200) .padding(.top, 20)
.padding(.vertical, 20)
// Buttons Spacer()
// Next/Open button
Button(action: { Button(action: {
// Action for first button // Action for open button
}) { }) {
Text("Upload from Gallery") Text("Open")
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
.padding() .padding()
.foregroundColor(.black) .foregroundColor(.black)
.background( .background(
RoundedRectangle(cornerRadius: 25) RoundedRectangle(cornerRadius: 25)
.fill(Color(red: 1.0, green: 0.973, blue: 0.871)) .fill(Color(red: 1.0, green: 0.714, blue: 0.271))
)
}
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(.bottom, 30)
.padding(.horizontal, 20)
} }
.padding() .padding()
.background(Color(.white)) .background(Color(.white))

View File

@ -0,0 +1,92 @@
import SwiftUI
struct SplashView: View {
@State private var isAnimating = false
@State private var showLogin = false
@EnvironmentObject private var authState: AuthState
var body: some View {
NavigationView {
ZStack {
//
LinearGradient(
gradient: Gradient(colors: [
Theme.Colors.primary, // Primary color with some transparency
Theme.Colors.primaryDark, // Darker shade of the primary color
]),
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.edgesIgnoringSafeArea(.all)
VStack(spacing: 50) {
Spacer()
//
Text("Welcome")
.font(.system(size: 40, weight: .bold, design: .rounded))
.foregroundColor(.primary)
.scaleEffect(isAnimating ? 1.1 : 0.9)
.opacity(isAnimating ? 1 : 0.3)
.animation(
.easeInOut(duration: 1.5)
.repeatForever(autoreverses: true),
value: isAnimating
)
//
Image(systemName: "moon.stars.fill")
.font(.system(size: 120))
.foregroundColor(.accentColor)
.rotationEffect(.degrees(isAnimating ? 360 : 0))
.scaleEffect(isAnimating ? 1.2 : 0.8)
.animation(
.easeInOut(duration: 2)
.repeatForever(autoreverses: true),
value: isAnimating
)
Spacer()
//
Button(action: {
withAnimation {
showLogin = true
}
}) {
Image(systemName: "arrow.right")
.font(.title)
.foregroundColor(.white)
.frame(width: 140, height: 140)
.background(
Circle()
.fill(Color.accentColor.opacity(0.7)) // 80% opacity
.shadow(radius: 10)
)
}
.padding(.bottom, 40)
.background(
NavigationLink(destination: LoginView().environmentObject(authState), isActive: $showLogin) {
EmptyView()
}
.hidden()
)
Spacer()
}
.padding()
}
.onAppear {
isAnimating = true
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
//
#if DEBUG
struct SplashView_Previews: PreviewProvider {
static var previews: some View {
SplashView()
.environmentObject(AuthState.shared)
}
}
#endif

View File

@ -1,20 +1,12 @@
import SwiftUI import SwiftUI
import UIKit import UIKit
import SwiftData import SwiftData
@main @main
struct WakeApp: App { struct WakeApp: App {
// init() { @StateObject private var authState = AuthState.shared
// // @State private var showSplash = true
// print("\n=== ===")
// for family in UIFont.familyNames.sorted() {
// print("\n\(family):")
// for name in UIFont.fontNames(forFamilyName: family).sorted() {
// print(" - \(name)")
// }
// }
// }
// 使 // 使
let container: ModelContainer let container: ModelContainer
@ -32,26 +24,77 @@ struct WakeApp: App {
// 3. // 3.
container = try! ModelContainer(for: Login.self) container = try! ModelContainer(for: Login.self)
} }
}
//
configureNetwork()
}
var body: some Scene { var body: some Scene {
WindowGroup { WindowGroup {
ZStack {
if showSplash {
//
SplashView()
.environmentObject(authState)
.onAppear {
// token
checkTokenValidity()
}
} else {
//
if authState.isAuthenticated {
//
ContentView() ContentView()
// SettingsView() .environmentObject(authState)
// } else {
// TabView{ //
// ContentView() ContentView()
// .tabItem{ .environmentObject(authState)
// Label("wake", systemImage: "book") }
// } }
// SettingView() }
// .tabItem{ .onAppear {
// Label("setting", systemImage: "gear") // 3
// DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// withAnimation {
// showSplash = false
// } // }
// } // }
} }
// }
.modelContainer(container) .modelContainer(container)
} }
// MARK: -
///
private func configureNetwork() {
//
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 60
//
}
/// token
private func checkTokenValidity() {
guard TokenManager.shared.hasToken,
let token = KeychainHelper.getAccessToken() else {
showSplash = false
return
}
// token
if TokenManager.shared.isTokenValid(token) {
authState.isAuthenticated = true
}
// 3
// DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
// withAnimation {
// showSplash = false
// }
// }
}
} }