feat: 移除设置功能并优化主功能状态管理
- 从HomeFeature和MainFeature中移除设置相关状态和逻辑,简化状态管理。 - 更新视图以去除设置页面的展示,提升用户体验。 - 删除SettingFeature及其相关视图,减少冗余代码,增强代码可维护性。
This commit is contained in:
@@ -11,8 +11,6 @@ struct HomeFeature: Reducer {
|
||||
var userInfo: UserInfo?
|
||||
var accountModel: AccountModel?
|
||||
var error: String?
|
||||
var isSettingPresented = false
|
||||
var settingState = SettingFeature.State()
|
||||
var feedState = FeedFeature.State()
|
||||
var meDynamic = MeDynamicFeature.State(uid: 0)
|
||||
var isLoggedOut = false
|
||||
@@ -28,8 +26,6 @@ struct HomeFeature: Reducer {
|
||||
case accountModelLoaded(AccountModel?)
|
||||
case logoutTapped
|
||||
case logout
|
||||
case settingDismissed
|
||||
case setting(SettingFeature.Action)
|
||||
case feed(FeedFeature.Action)
|
||||
case meDynamic(MeDynamicFeature.Action)
|
||||
case logoutCompleted
|
||||
@@ -38,9 +34,6 @@ struct HomeFeature: Reducer {
|
||||
}
|
||||
|
||||
var body: some ReducerOf<Self> {
|
||||
Scope(state: \.settingState, action: \.setting) {
|
||||
SettingFeature()
|
||||
}
|
||||
Scope(state: \.feedState, action: \.feed) {
|
||||
FeedFeature()
|
||||
}
|
||||
@@ -83,10 +76,9 @@ struct HomeFeature: Reducer {
|
||||
case .logoutCompleted:
|
||||
state.isLoggedOut = true
|
||||
return .none
|
||||
case .settingDismissed:
|
||||
state.isSettingPresented = false
|
||||
case .feed:
|
||||
return .none
|
||||
case .setting:
|
||||
case .meDynamic:
|
||||
return .none
|
||||
case .showCreateFeed:
|
||||
state.route = .createFeed
|
||||
@@ -94,10 +86,6 @@ struct HomeFeature: Reducer {
|
||||
case .createFeedDismissed:
|
||||
state.route = nil
|
||||
return .none
|
||||
case .feed:
|
||||
return .none
|
||||
case .meDynamic:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,7 +14,6 @@ struct MainFeature: Reducer {
|
||||
var accountModel: AccountModel? = nil
|
||||
// 新增:导航路径和设置页面 State
|
||||
var navigationPath: [Destination] = []
|
||||
var settingState: SettingFeature.State? = nil
|
||||
var appSettingState: AppSettingFeature.State? = nil
|
||||
// 新增:登出标志
|
||||
var isLoggedOut: Bool = false
|
||||
@@ -22,7 +21,6 @@ struct MainFeature: Reducer {
|
||||
|
||||
// 新增:导航目标
|
||||
enum Destination: Hashable, Equatable {
|
||||
case setting
|
||||
case test
|
||||
case appSetting
|
||||
}
|
||||
@@ -36,8 +34,6 @@ struct MainFeature: Reducer {
|
||||
case accountModelLoaded(AccountModel?)
|
||||
// 新增:导航相关
|
||||
case navigationPathChanged([Destination])
|
||||
case settingButtonTapped
|
||||
case settingAction(SettingFeature.Action)
|
||||
case testButtonTapped
|
||||
case appSettingButtonTapped
|
||||
case appSettingAction(AppSettingFeature.Action)
|
||||
@@ -84,20 +80,11 @@ struct MainFeature: Reducer {
|
||||
return .none
|
||||
case .navigationPathChanged(let newPath):
|
||||
// pop 回来时清空 settingState
|
||||
if !newPath.contains(.setting) {
|
||||
state.settingState = nil
|
||||
}
|
||||
if !newPath.contains(.appSetting) {
|
||||
state.appSettingState = nil
|
||||
}
|
||||
state.navigationPath = newPath
|
||||
return .none
|
||||
case .settingButtonTapped:
|
||||
state.settingState = SettingFeature.State()
|
||||
state.navigationPath.append(.setting)
|
||||
return .none
|
||||
case .settingAction:
|
||||
return .none
|
||||
case .testButtonTapped:
|
||||
state.navigationPath.append(.test)
|
||||
return .none
|
||||
@@ -117,9 +104,6 @@ struct MainFeature: Reducer {
|
||||
}
|
||||
}
|
||||
// 设置页作用域
|
||||
.ifLet(\ .settingState, action: \.settingAction) {
|
||||
SettingFeature()
|
||||
}
|
||||
.ifLet(\ .appSettingState, action: \.appSettingAction) {
|
||||
AppSettingFeature()
|
||||
}
|
||||
|
@@ -1,102 +0,0 @@
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
|
||||
@Reducer
|
||||
struct SettingFeature {
|
||||
@ObservableState
|
||||
struct State: Equatable {
|
||||
var userInfo: UserInfo?
|
||||
var accountModel: AccountModel?
|
||||
var isLoading = false
|
||||
var error: String?
|
||||
var isRefreshingUserInfo = false // 新增:用户信息刷新状态
|
||||
}
|
||||
|
||||
enum Action: Equatable {
|
||||
case onAppear
|
||||
case loadUserInfo
|
||||
case userInfoLoaded(UserInfo?)
|
||||
case loadAccountModel
|
||||
case accountModelLoaded(AccountModel?)
|
||||
case refreshUserInfo // 新增:刷新用户信息
|
||||
case refreshUserInfoResponse(TaskResult<UserInfo?>) // 新增:刷新用户信息响应
|
||||
case logoutTapped
|
||||
case logout
|
||||
case dismissTapped
|
||||
}
|
||||
|
||||
@Dependency(\.apiService) var apiService // 新增:API服务依赖
|
||||
|
||||
var body: some ReducerOf<Self> {
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case .onAppear:
|
||||
return .concatenate(
|
||||
.send(.loadUserInfo),
|
||||
.send(.loadAccountModel)
|
||||
)
|
||||
|
||||
case .loadUserInfo:
|
||||
return .run { send in
|
||||
let userInfo = await UserInfoManager.getUserInfo()
|
||||
await send(.userInfoLoaded(userInfo))
|
||||
}
|
||||
|
||||
case let .userInfoLoaded(userInfo):
|
||||
state.userInfo = userInfo
|
||||
return .none
|
||||
|
||||
case .loadAccountModel:
|
||||
return .run { send in
|
||||
let accountModel = await UserInfoManager.getAccountModel()
|
||||
await send(.accountModelLoaded(accountModel))
|
||||
}
|
||||
|
||||
case let .accountModelLoaded(accountModel):
|
||||
state.accountModel = accountModel
|
||||
return .none
|
||||
|
||||
case .refreshUserInfo: // 新增:刷新用户信息
|
||||
return .none
|
||||
// state.isRefreshingUserInfo = true
|
||||
// state.error = nil
|
||||
// return .run { send in
|
||||
// let userInfo = await UserInfoManager.refreshCurrentUserInfo(apiService: apiService)
|
||||
// await send(.refreshUserInfoResponse(.success(userInfo)))
|
||||
// }
|
||||
|
||||
case let .refreshUserInfoResponse(.success(userInfo)): // 新增:处理刷新响应
|
||||
state.isRefreshingUserInfo = false
|
||||
if let userInfo = userInfo {
|
||||
state.userInfo = userInfo
|
||||
state.error = nil
|
||||
} else {
|
||||
state.error = "刷新用户信息失败"
|
||||
}
|
||||
return .none
|
||||
|
||||
case let .refreshUserInfoResponse(.failure(error)): // 新增:处理刷新错误
|
||||
state.isRefreshingUserInfo = false
|
||||
state.error = error.localizedDescription
|
||||
return .none
|
||||
|
||||
case .logoutTapped:
|
||||
return .send(.logout)
|
||||
|
||||
case .logout:
|
||||
state.isLoading = true
|
||||
return .run { _ in
|
||||
await UserInfoManager.clearAllAuthenticationData()
|
||||
}
|
||||
|
||||
case .dismissTapped:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移除:未使用的通知名称定义
|
||||
// extension Notification.Name {
|
||||
// static let settingsDismiss = Notification.Name("settingsDismiss")
|
||||
// }
|
@@ -58,12 +58,6 @@ struct HomeView: View {
|
||||
.onAppear {
|
||||
store.send(.onAppear)
|
||||
}
|
||||
.sheet(isPresented: Binding(
|
||||
get: { store.withState(\.isSettingPresented) },
|
||||
set: { _ in store.send(.settingDismissed) }
|
||||
)) {
|
||||
SettingView(store: store.scope(state: \.settingState, action: \.setting))
|
||||
}
|
||||
.navigationDestination(isPresented: Binding(
|
||||
get: { store.withState(\.route) == .createFeed },
|
||||
set: { isPresented in
|
||||
|
@@ -48,21 +48,6 @@ struct InternalMainView: View {
|
||||
.isHidden(viewStore.selectedTab != .other)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
// 测试按钮
|
||||
VStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Button("Test Push") {
|
||||
viewStore.send(.testButtonTapped)
|
||||
}
|
||||
.padding()
|
||||
.background(Color.red)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(8)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
// 底部导航栏
|
||||
VStack {
|
||||
Spacer()
|
||||
@@ -76,18 +61,6 @@ struct InternalMainView: View {
|
||||
}
|
||||
.navigationDestination(for: MainFeature.Destination.self) { destination in
|
||||
switch destination {
|
||||
case .setting:
|
||||
IfLetStore(
|
||||
self.store.scope(
|
||||
state: \.settingState,
|
||||
action: MainFeature.Action.settingAction
|
||||
),
|
||||
then: { settingStore in
|
||||
WithPerceptionTracking {
|
||||
SettingView(store: settingStore)
|
||||
}
|
||||
}
|
||||
)
|
||||
case .test:
|
||||
TestPushView()
|
||||
case .appSetting:
|
||||
|
@@ -1,332 +0,0 @@
|
||||
import SwiftUI
|
||||
import ComposableArchitecture
|
||||
|
||||
struct SettingView: View {
|
||||
let store: StoreOf<SettingFeature>
|
||||
@ObservedObject private var localizationManager = LocalizationManager.shared
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
WithPerceptionTracking {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
// 背景图片 - 使用"bg"图片,全屏显示
|
||||
Image("bg")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.ignoresSafeArea(.all)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
// Navigation Bar
|
||||
HStack {
|
||||
// 返回按钮
|
||||
Button(action: {
|
||||
store.send(.dismissTapped)
|
||||
}) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.title2)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
.padding(.leading, 16)
|
||||
|
||||
Spacer()
|
||||
|
||||
// 标题
|
||||
Text(NSLocalizedString("setting.title", comment: "Settings"))
|
||||
.font(.custom("PingFang SC-Semibold", size: 16))
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
// 占位符,保持标题居中
|
||||
Color.clear
|
||||
.frame(width: 44, height: 44)
|
||||
}
|
||||
.padding(.top, 8)
|
||||
.padding(.horizontal)
|
||||
|
||||
// 内容区域
|
||||
ScrollView {
|
||||
VStack(spacing: 24) {
|
||||
UserInfoCardView(userInfo: store.userInfo, accountModel: store.accountModel, isRefreshing: store.isRefreshingUserInfo, onRefresh: {
|
||||
store.send(.refreshUserInfo)
|
||||
})
|
||||
// .padding()
|
||||
.padding(.top, 32)
|
||||
|
||||
SettingOptionsView(
|
||||
onLanguageTapped: {
|
||||
// TODO: 实现语言设置
|
||||
},
|
||||
onAboutTapped: {
|
||||
// TODO: 实现关于页面
|
||||
}
|
||||
)
|
||||
|
||||
Spacer(minLength: 50)
|
||||
|
||||
LogoutButtonView {
|
||||
store.send(.logoutTapped)
|
||||
}
|
||||
.padding(.bottom, 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
store.send(.onAppear)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - User Info Card View
|
||||
struct UserInfoCardView: View {
|
||||
let userInfo: UserInfo?
|
||||
let accountModel: AccountModel?
|
||||
let isRefreshing: Bool // 新增:刷新状态
|
||||
let onRefresh: () -> Void // 新增:刷新回调
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 16) {
|
||||
// 头像区域
|
||||
Image(systemName: "person.circle.fill")
|
||||
.font(.system(size: 60))
|
||||
.foregroundColor(.white.opacity(0.8))
|
||||
|
||||
// 用户信息
|
||||
VStack(spacing: 8) {
|
||||
if let userInfo = userInfo, let userName = userInfo.username {
|
||||
Text(userName)
|
||||
.font(.title2.weight(.semibold))
|
||||
.foregroundColor(.white)
|
||||
} else {
|
||||
Text(NSLocalizedString("setting.user", comment: "User"))
|
||||
.font(.title2.weight(.semibold))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
// 显示用户ID
|
||||
if let userInfo = userInfo, let userId = userInfo.userId {
|
||||
Text("ID: \(userId)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.8))
|
||||
} else if let accountModel = accountModel, let uid = accountModel.uid {
|
||||
Text("UID: \(uid)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.white.opacity(0.8))
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:刷新按钮
|
||||
Button(action: onRefresh) {
|
||||
HStack(spacing: 4) {
|
||||
if isRefreshing {
|
||||
ProgressView()
|
||||
.scaleEffect(0.8)
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
} else {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
.font(.caption)
|
||||
}
|
||||
Text(isRefreshing ? "刷新中..." : "刷新")
|
||||
.font(.caption)
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
.background(Color.white.opacity(0.2))
|
||||
.cornerRadius(12)
|
||||
}
|
||||
.disabled(isRefreshing)
|
||||
}
|
||||
.padding(.vertical, 24)
|
||||
.padding(.horizontal, 20)
|
||||
.background(Color.black.opacity(0.3))
|
||||
.cornerRadius(16)
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Setting Options View
|
||||
// Add this new view for testing COS upload
|
||||
struct TestCOSUploadView: View {
|
||||
@State private var imageURL: String = "https://img.toto.im/mw600/66b3de17ly1i3mpcw0k7yj20hs0md0tf.jpg.webp"
|
||||
@State private var uploadResult: String = ""
|
||||
@State private var isUploading: Bool = false
|
||||
@Dependency(\.apiService) private var apiService
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 20) {
|
||||
TextField("Enter image URL", text: $imageURL)
|
||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
||||
.padding()
|
||||
|
||||
Button(action: {
|
||||
Task {
|
||||
await uploadImageFromURL()
|
||||
}
|
||||
}) {
|
||||
Text(isUploading ? "Uploading..." : "Upload to COS")
|
||||
.padding()
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
.disabled(isUploading || imageURL.isEmpty)
|
||||
|
||||
Text(uploadResult)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding()
|
||||
}
|
||||
.navigationTitle("Test COS Upload")
|
||||
}
|
||||
|
||||
private func uploadImageFromURL() async {
|
||||
guard let url = URL(string: imageURL) else {
|
||||
uploadResult = "Invalid URL"
|
||||
return
|
||||
}
|
||||
isUploading = true
|
||||
uploadResult = ""
|
||||
do {
|
||||
let (data, _) = try await URLSession.shared.data(from: url)
|
||||
if let cloudURL = await COSManager.shared.uploadImage(data, apiService: apiService) {
|
||||
uploadResult = "Upload successful! Cloud URL: \(cloudURL)"
|
||||
} else {
|
||||
uploadResult = "Upload failed"
|
||||
}
|
||||
} catch {
|
||||
uploadResult = "Download failed: \(error.localizedDescription)"
|
||||
}
|
||||
isUploading = false
|
||||
}
|
||||
}
|
||||
|
||||
// Modify SettingOptionsView to add the test row
|
||||
struct SettingOptionsView: View {
|
||||
let onLanguageTapped: () -> Void
|
||||
let onAboutTapped: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
SettingRowView(
|
||||
icon: "globe",
|
||||
title: NSLocalizedString("setting.language", comment: "Language Settings"),
|
||||
action: onLanguageTapped
|
||||
)
|
||||
|
||||
SettingRowView(
|
||||
icon: "info.circle",
|
||||
title: NSLocalizedString("setting.about", comment: "About Us"),
|
||||
action: onAboutTapped
|
||||
)
|
||||
|
||||
#if DEBUG
|
||||
NavigationLink(destination: TestCOSUploadView()) {
|
||||
HStack {
|
||||
Image(systemName: "cloud.upload")
|
||||
.foregroundColor(.white.opacity(0.8))
|
||||
.frame(width: 24)
|
||||
|
||||
Text("Test COS Upload")
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 16)
|
||||
.background(Color.black.opacity(0.2))
|
||||
.cornerRadius(12)
|
||||
}
|
||||
#endif
|
||||
|
||||
HStack {
|
||||
Image(systemName: "app.badge")
|
||||
.foregroundColor(.white.opacity(0.8))
|
||||
.frame(width: 24)
|
||||
|
||||
Text(NSLocalizedString("setting.version", comment: "Version Info"))
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("1.0.0")
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 16)
|
||||
.background(Color.black.opacity(0.2))
|
||||
.cornerRadius(12)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Logout Button View
|
||||
struct LogoutButtonView: View {
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack {
|
||||
Image(systemName: "arrow.right.square")
|
||||
Text(NSLocalizedString("setting.logout", comment: "Logout"))
|
||||
}
|
||||
.font(.body.weight(.medium))
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 16)
|
||||
.background(Color.red.opacity(0.7))
|
||||
.cornerRadius(12)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Setting Row View
|
||||
struct SettingRowView: View {
|
||||
let icon: String
|
||||
let title: String
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack {
|
||||
Image(systemName: icon)
|
||||
.foregroundColor(.white.opacity(0.8))
|
||||
.frame(width: 24)
|
||||
|
||||
Text(title)
|
||||
.foregroundColor(.white)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
.font(.caption)
|
||||
}
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.vertical, 16)
|
||||
.background(Color.black.opacity(0.2))
|
||||
.cornerRadius(12)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#Preview {
|
||||
// SettingView(
|
||||
// store: Store(
|
||||
// initialState: SettingFeature.State()
|
||||
// ) {
|
||||
// SettingFeature()
|
||||
// }
|
||||
// )
|
||||
//}
|
Reference in New Issue
Block a user