feat: 移除设置功能并优化主功能状态管理

- 从HomeFeature和MainFeature中移除设置相关状态和逻辑,简化状态管理。
- 更新视图以去除设置页面的展示,提升用户体验。
- 删除SettingFeature及其相关视图,减少冗余代码,增强代码可维护性。
This commit is contained in:
edwinQQQ
2025-07-24 15:04:39 +08:00
parent 6cc4b11e93
commit c072a7e73d
6 changed files with 2 additions and 497 deletions

View File

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

View File

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

View File

@@ -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")
// }

View File

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

View File

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

View File

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