模块化 AppStore 并完善素材管理与个人空间设置。

将会话、权限、位置、收款与排队存储拆分为独立模块,同步更新各 ViewModel 与单元测试,并补充素材管理 UI 与个人空间设置交互。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-13 11:01:08 +08:00
parent ceca780ab3
commit 005349f8e6
128 changed files with 2953 additions and 1123 deletions

View File

@ -0,0 +1,233 @@
//
// AppSessionStore.swift
// suixinkan
//
import Foundation
///
enum AppAccountType: Equatable, Sendable {
case scenicUser
case storeUser
case unknown(String)
///
init(rawValue: String) {
switch rawValue {
case "scenic_user": self = .scenicUser
case "store_user": self = .storeUser
default: self = .unknown(rawValue)
}
}
///
var rawValue: String {
switch self {
case .scenicUser: "scenic_user"
case .storeUser: "store_user"
case let .unknown(value): value
}
}
}
///
final class AppSessionStore {
private enum Key: String {
case token = "key_in_token"
case lastLoginUsername = "key_last_login_username"
case privacyAgreementAccepted = "key_privacy_agreement_accepted"
case userId = "key_in_user_id"
case userName = "key_in_user_name"
case realName = "key_in_real_name"
case avatar = "key_in_avatar"
case phone = "key_in_phone"
case accountType = "key_in_account_type"
case accountDisplayName = "key_in_account_display_name"
case roleCode = "key_in_role_code"
case roleName = "key_in_role_name"
case currentScenicId = "key_in_current_scenic_id"
case currentScenicName = "key_in_current_scenic_name"
case currentStoreId = "key_in_current_store_id"
}
private let defaults: UserDefaults
/// 使 UserDefaults
init(defaults: UserDefaults) {
self.defaults = defaults
}
/// Token
var token: String {
get { string(for: .token) }
set { defaults.set(newValue, forKey: Key.token.rawValue) }
}
///
var lastLoginUsername: String? {
get { defaults.string(forKey: Key.lastLoginUsername.rawValue) }
set {
if let newValue {
defaults.set(newValue, forKey: Key.lastLoginUsername.rawValue)
} else {
defaults.removeObject(forKey: Key.lastLoginUsername.rawValue)
}
}
}
///
var privacyAgreementAccepted: Bool {
get { defaults.bool(forKey: Key.privacyAgreementAccepted.rawValue) }
set { defaults.set(newValue, forKey: Key.privacyAgreementAccepted.rawValue) }
}
/// ID
var userId: String {
get { string(for: .userId) }
set { defaults.set(newValue, forKey: Key.userId.rawValue) }
}
///
var userName: String {
get { string(for: .userName) }
set { defaults.set(newValue, forKey: Key.userName.rawValue) }
}
///
var realName: String {
get { string(for: .realName) }
set { defaults.set(newValue, forKey: Key.realName.rawValue) }
}
///
var avatar: String {
get { string(for: .avatar) }
set { defaults.set(newValue, forKey: Key.avatar.rawValue) }
}
///
var phone: String {
get { string(for: .phone) }
set { defaults.set(newValue, forKey: Key.phone.rawValue) }
}
///
var accountType: AppAccountType {
get { AppAccountType(rawValue: string(for: .accountType)) }
set { defaults.set(newValue.rawValue, forKey: Key.accountType.rawValue) }
}
///
var accountDisplayName: String {
get { string(for: .accountDisplayName) }
set { defaults.set(newValue, forKey: Key.accountDisplayName.rawValue) }
}
///
var roleCode: String {
get { string(for: .roleCode) }
set { defaults.set(newValue, forKey: Key.roleCode.rawValue) }
}
///
var roleName: String {
get { string(for: .roleName) }
set { defaults.set(newValue, forKey: Key.roleName.rawValue) }
}
/// ID0
var currentScenicId: Int {
get { Int(string(for: .currentScenicId)) ?? 0 }
set { defaults.set(newValue > 0 ? String(newValue) : "", forKey: Key.currentScenicId.rawValue) }
}
///
var currentScenicName: String {
get { string(for: .currentScenicName) }
set { defaults.set(newValue, forKey: Key.currentScenicName.rawValue) }
}
/// ID0
var currentStoreId: Int {
get { defaults.integer(forKey: Key.currentStoreId.rawValue) }
set { defaults.set(newValue, forKey: Key.currentStoreId.rawValue) }
}
/// Android `getAccountCachePrefix`
var accountCachePrefix: String {
let uid = userId.trimmingCharacters(in: .whitespacesAndNewlines)
let type = accountType.rawValue.trimmingCharacters(in: .whitespacesAndNewlines)
if uid.isEmpty { return "guest" }
if type.isEmpty { return uid }
return "\(uid)_\(type)"
}
///
var currentAppRole: AppRoleCode? {
AppRoleCode.fromCode(roleCode) ?? AppRoleCode.fromRoleName(roleName)
}
///
var isPhotographerRole: Bool {
if let role = currentAppRole {
return role.isPhotographer
}
return roleName.trimmingCharacters(in: .whitespacesAndNewlines) == "摄影师"
}
/// Token
var isLoggedIn: Bool {
!token.isEmpty
}
/// Token
func saveToken(_ token: String) {
self.token = token
}
///
func applyAccount(_ account: AccountSwitchAccount) {
userId = String(account.businessUserId)
accountType = AppAccountType(rawValue: account.accountType)
accountDisplayName = account.title
userName = account.subtitle
phone = account.phone
if !account.avatar.isEmpty {
avatar = account.avatar
}
currentScenicName = account.scenicName
currentScenicId = account.scenicId ?? 0
currentStoreId = account.storeId ?? 0
}
///
func applyUserInfo(_ info: UserInfoResponse) {
if !info.nickname.isEmpty { userName = info.nickname }
if !info.realName.isEmpty { realName = info.realName }
if !info.avatar.isEmpty { avatar = info.avatar }
if !info.phone.isEmpty { phone = info.phone }
if !info.roleName.isEmpty, roleName.isEmpty { roleName = info.roleName }
}
///
func clearAuthenticatedSession() {
token = ""
userId = ""
userName = ""
realName = ""
avatar = ""
phone = ""
accountType = .unknown("")
accountDisplayName = ""
roleCode = ""
roleName = ""
currentScenicId = 0
currentScenicName = ""
currentStoreId = 0
}
private func string(for key: Key) -> String {
defaults.string(forKey: key.rawValue) ?? ""
}
}