Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat/56] 전체 뉴스 창 추가 #57

Merged
merged 1 commit into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions Targets/D3N/Sources/App/RootStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ struct RootStore: Reducer {
case mainTab(MainTabStore.State)

init() {
if LocalStorageManager.load(.isOnBoardingNeeded) != false {
self = .onboarding(.init())
} else {
// if LocalStorageManager.load(.isOnBoardingNeeded) != false {
// self = .onboarding(.init())
// } else {
self = .mainTab(.init())
}
// }
}
}

Expand Down
111 changes: 111 additions & 0 deletions Targets/D3N/Sources/Feature/AllNews/AllNewsNavigationStackStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
//
// AllNewsNavigationStackStore.swift
// D3N
//
// Created by Younghoon Ahn on 11/14/23.
// Copyright © 2023 sju. All rights reserved.
//

import ComposableArchitecture

public struct AllNewsNavigationStackStore: Reducer {
public struct State: Equatable {
var main: AllNewsStore.State = .init()

var path: StackState<Path.State> = .init()
}

public enum Action: BindableAction, Equatable {
case binding(BindingAction<State>)

case onAppear

case main(AllNewsStore.Action)
case path(StackAction<Path.State, Path.Action>)

case popToRoot
case delegate(Delegate)

public enum Delegate {
case complete
}
}

public struct Path: Reducer {
public enum State: Equatable {
case quizMain(QuizMainStore.State)
case quizResult(QuizResultStore.State)
case newsList(AllNewsStore.State)
}

public enum Action: Equatable {
case quizMain(QuizMainStore.Action)
case quizResult(QuizResultStore.Action)
case newsList(AllNewsStore.Action)
}

public var body: some Reducer<State, Action> {
Scope(state: /State.quizMain, action: /Action.quizMain) {
QuizMainStore()
}
Scope(state: /State.quizResult, action: /Action.quizResult) {
QuizResultStore()
}
Scope(state: /State.newsList, action: /Action.newsList) {
AllNewsStore()
}
}
}

public var body: some ReducerOf<Self> {
BindingReducer()

Reduce { state, action in
switch action {
case .onAppear:
return .none

case let .main(.delegate(action)):
switch action {
case let .select(newsEntity):
state.path.append(.quizMain(.init(newsEntity: newsEntity)))
return .none
}

case let .path(.element(id: _, action: .quizMain(.delegate(action)))):
switch action {
case let .solved(quizEntityList):
state.path.append(.quizResult(.init(quizEntityList: quizEntityList)))
return .none
}

case let .path(.element(id: _, action: .quizResult(.delegate(action)))):
switch action {
case .backToRoot:
return .send(.popToRoot)
}

case let .path(.element(id: _, action: .newsList(.delegate(action)))):
switch action {
case let .select(newsEntity):
state.path.append(.quizMain(.init(newsEntity: newsEntity)))
}
return .none

case .popToRoot:
state.path.removeAll()
return .none

default:
return .none
}
}
Scope(state: \.main, action: /Action.main) {
AllNewsStore()
}
.forEach(\.path, action: /Action.path) {
Path()
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// AllNewsNavigationStackView.swift
// D3N
//
// Created by Younghoon Ahn on 11/14/23.
// Copyright © 2023 sju. All rights reserved.
//

import Foundation
import SwiftUI

import ComposableArchitecture

public struct AllNewsNavigationStackView: View {
let store: StoreOf<AllNewsNavigationStackStore>

public init(store: StoreOf<AllNewsNavigationStackStore>) {
self.store = store
}

public var body: some View {
NavigationStackStore(self.store.scope(
state: \.path,
action: AllNewsNavigationStackStore.Action.path)) {
WithViewStore(self.store, observe: { $0 }) { viewStore in
AllNewsView(store: self.store.scope(state: \.main, action: AllNewsNavigationStackStore.Action.main))
.onAppear {
viewStore.send(.onAppear)
}
}
} destination: {
switch $0 {
case .quizMain:
CaseLet(
/AllNewsNavigationStackStore.Path.State.quizMain,
action: AllNewsNavigationStackStore.Path.Action.quizMain,
then: QuizMainView.init(store:)
)

case .quizResult:
CaseLet(
/AllNewsNavigationStackStore.Path.State.quizResult,
action: AllNewsNavigationStackStore.Path.Action.quizResult,
then: QuizResultView.init(store:)
)

case .newsList:
CaseLet(
/AllNewsNavigationStackStore.Path.State.newsList,
action: TodayNavigationStackStore.Path.Action.newsList,
then: AllNewsView.init(store:)
)
}
}

}
}
98 changes: 98 additions & 0 deletions Targets/D3N/Sources/Feature/AllNews/Main/AllNewsStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// AllNewsStore.swift
// D3N
//
// Created by Younghoon Ahn on 11/14/23.
// Copyright © 2023 sju. All rights reserved.
//

import Foundation

import ComposableArchitecture

public struct AllNewsStore: Reducer {
let FIXED_PAGE_SIZE = 10

public struct State: Equatable, Identifiable {
public let id: UUID

var newsEntityList: [NewsEntity] = []
var pageIndex: Int = 0

var newsListItems: IdentifiedArrayOf<NewsListItemCellStore.State> = []

public init(
id: UUID = .init()
) {
self.id = id
}
}

@Dependency(\.newsClient) var newsClient

public enum Action: Equatable {
case onAppear

case fetchNewsListRequest
case fetchNewsListResponse(Result<[NewsEntity], D3NAPIError>)

case newsListItems(id: NewsListItemCellStore.State.ID, action: NewsListItemCellStore.Action)
case delegate(Delegate)

public enum Delegate: Equatable {
case select(NewsEntity)
}
}

public var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .onAppear:
return .concatenate([
.send(.fetchNewsListRequest)
])

case .fetchNewsListRequest:
return .run { [pageIndex = state.pageIndex] send in
let response = await newsClient.fetchNewsList(pageIndex, FIXED_PAGE_SIZE)
await send(.fetchNewsListResponse(response))
}

case let .fetchNewsListResponse(.success(newsEntityList)):
let newsListItems = state.makeNewsListItems(from: newsEntityList)
state.newsListItems.append(contentsOf: newsListItems)
state.pageIndex += 1
return .none

case let .newsListItems(id: id, action: .delegate(action)):
switch action {
case .onAppear:
if id == state.newsListItems.ids.last {
return .send(.fetchNewsListRequest)
}
case .tapped:
if let newsEntity = state.newsListItems[id: id]?.newsEntity {
return .send(.delegate(.select(newsEntity)))
}
}
return .none

default:
return .none
}
}
.forEach(\.newsListItems, action: /Action.newsListItems(id:action:)) {
NewsListItemCellStore()
}
}
}

public extension AllNewsStore.State {
func makeNewsListItems(from newsEntityList: [NewsEntity]) -> IdentifiedArrayOf<NewsListItemCellStore.State> {
return .init(
uniqueElements: newsEntityList.map { newsEntity in
return .init(newsEntity: newsEntity)
}
)
}
}
45 changes: 45 additions & 0 deletions Targets/D3N/Sources/Feature/AllNews/Main/AllNewsView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//
// AllNewsView.swift
// D3N
//
// Created by Younghoon Ahn on 11/14/23.
// Copyright © 2023 sju. All rights reserved.
//

import Foundation
import SwiftUI

import ComposableArchitecture

public struct AllNewsView: View {
let store: StoreOf<AllNewsStore>

public init(store: StoreOf<AllNewsStore>) {
self.store = store
}

public var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
ScrollView {
VStack {
newsListItemsView
.padding(.horizontal)

Spacer()
}
}
.navigationTitle("전체 뉴스")
.onAppear {
viewStore.send(.onAppear)
}
}
}

private var newsListItemsView: some View {
LazyVStack {
ForEachStore(self.store.scope(state: \.newsListItems, action: AllNewsStore.Action.newsListItems(id:action:))) {
NewsListItemCellView(store: $0)
}
}
}
}
5 changes: 5 additions & 0 deletions Targets/D3N/Sources/Feature/MainTab/MainTabStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ struct MainTabStore: Reducer {
var currentScene: MainScene = .question

var today: TodayNavigationStackStore.State? = .init()
var allNews: AllNewsNavigationStackStore.State? = .init()
var myPage: MyPageNavigationStackStore.State? = .init()
}

Expand All @@ -27,6 +28,7 @@ struct MainTabStore: Reducer {
case selectTab(MainScene)

case today(TodayNavigationStackStore.Action)
case allNews(AllNewsNavigationStackStore.Action)
case myPage(MyPageNavigationStackStore.Action)
case delegate(Delegate)

Expand All @@ -52,6 +54,9 @@ struct MainTabStore: Reducer {
.ifLet(\.today, action: /Action.today) {
TodayNavigationStackStore()
}
.ifLet(\.allNews, action: /Action.allNews) {
AllNewsNavigationStackStore()
}
.ifLet(\.myPage, action: /Action.myPage) {
MyPageNavigationStackStore()
}
Expand Down
8 changes: 8 additions & 0 deletions Targets/D3N/Sources/Feature/MainTab/MainTabView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ struct MainTabView: View {
}
}

IfLetStore(self.store.scope(state: \.allNews, action: MainTabStore.Action.allNews)){
AllNewsNavigationStackView(store: $0)
.tabItem {
Image(systemName: "text.justify.left")
Text("전체 뉴스")
}
}

IfLetStore(self.store.scope(state: \.myPage, action: MainTabStore.Action.myPage)) {
MyPageNavigationStackView(store: $0)
.tabItem {
Expand Down
Loading