feat: 新增导航功能与设置页面支持

- 在MainFeature中新增导航路径和设置页面状态管理,支持页面导航。
- 更新MainView以集成导航功能,添加测试按钮以触发导航。
- 在MeFeature中新增设置按钮点击事件,交由MainFeature处理。
- 增强MeView以支持设置按钮,提升用户体验。
This commit is contained in:
edwinQQQ
2025-07-23 20:10:37 +08:00
parent 772543243f
commit bb49b00a59
4 changed files with 124 additions and 18 deletions

View File

@@ -12,6 +12,15 @@ struct MainFeature: Reducer {
var feedList: FeedListFeature.State = .init()
var me: MeFeature.State = .init()
var accountModel: AccountModel? = nil
// State
var navigationPath: [Destination] = []
var settingState: SettingFeature.State? = nil
}
//
enum Destination: Hashable, Equatable {
case setting
case test
}
@CasePathable
@@ -21,6 +30,11 @@ struct MainFeature: Reducer {
case feedList(FeedListFeature.Action)
case me(MeFeature.Action)
case accountModelLoaded(AccountModel?)
//
case navigationPathChanged([Destination])
case settingButtonTapped
case settingAction(SettingFeature.Action)
case testButtonTapped
}
var body: some ReducerOf<Self> {
@@ -39,6 +53,7 @@ struct MainFeature: Reducer {
}
case .selectTab(let tab):
state.selectedTab = tab
state.navigationPath = []
if tab == .other, let uidStr = state.accountModel?.uid, let uid = Int(uidStr), uid > 0 {
state.me = MeFeature.State(uid: uid)
return .send(.me(.onAppear))
@@ -49,9 +64,34 @@ struct MainFeature: Reducer {
case let .accountModelLoaded(accountModel):
state.accountModel = accountModel
return .none
case .me(.settingButtonTapped):
// push
state.settingState = SettingFeature.State()
state.navigationPath.append(.setting)
return .none
case .me:
return .none
case .navigationPathChanged(let newPath):
// pop settingState
if !newPath.contains(.setting) {
state.settingState = 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
}
}
//
.ifLet(\ .settingState, action: /Action.settingAction) {
SettingFeature()
}
}
}

View File

@@ -25,6 +25,8 @@ struct MeFeature {
case loadMore
case userInfoResponse(Result<UserInfo, APIError>)
case momentsResponse(Result<MyMomentsResponse, APIError>)
//
case settingButtonTapped
}
func reduce(into state: inout State, action: Action) -> Effect<Action> {
@@ -84,6 +86,9 @@ struct MeFeature {
state.momentsError = error.localizedDescription
}
return .none
case .settingButtonTapped:
// MainFeature
return .none
}
}

View File

@@ -6,7 +6,7 @@ struct MainView: View {
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
NavigationStack {
NavigationStack(path: viewStore.binding(get: \.navigationPath, send: MainFeature.Action.navigationPathChanged)) {
GeometryReader { geometry in
ZStack {
//
@@ -36,6 +36,21 @@ struct MainView: View {
}
}
.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()
@@ -47,6 +62,24 @@ struct MainView: View {
.padding(.bottom, geometry.safeAreaInsets.bottom + 60)
}
}
.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()
}
}
}
.onAppear {
viewStore.send(.onAppear)
@@ -54,3 +87,14 @@ struct MainView: View {
}
}
}
struct TestPushView: View {
var body: some View {
ZStack {
Color.blue.ignoresSafeArea()
Text("Test Push View")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}

View File

@@ -5,17 +5,32 @@ struct MeView: View {
let store: StoreOf<MeFeature>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
GeometryReader { geometry in
ZStack {
Image("bg")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
.ignoresSafeArea(.all)
VStack(spacing: 0) {
//
GeometryReader { geometry in
ZStack {
Image("bg")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.clipped()
.ignoresSafeArea(.all)
VStack(spacing: 0) {
//
HStack {
Spacer()
WithViewStore(self.store, observe: { $0 }) { viewStore in
Button(action: {
viewStore.send(.settingButtonTapped)
}) {
Image(systemName: "gearshape")
.font(.system(size: 22, weight: .medium))
.foregroundColor(.white)
}
.padding(.trailing, 16)
.padding(.top, 8)
}
}
//
WithViewStore(self.store, observe: { $0 }) { viewStore in
if viewStore.isLoadingUserInfo {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
@@ -49,7 +64,9 @@ struct MeView: View {
} else {
Spacer().frame(height: 130)
}
//
}
//
WithViewStore(self.store, observe: { $0 }) { viewStore in
if viewStore.isLoadingMoments && viewStore.moments.isEmpty {
ProgressView("加载中...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
@@ -94,14 +111,14 @@ struct MeView: View {
viewStore.send(.refresh)
}
}
Spacer()
}
.frame(maxWidth: .infinity, alignment: .top)
Spacer()
}
.frame(maxWidth: .infinity, alignment: .top)
}
.onAppear {
viewStore.send(.onAppear)
}
}
.onAppear {
ViewStore(self.store, observe: { $0 }).send(.onAppear)
}
}
}