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

Boxoffice mvi #13

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
5 changes: 4 additions & 1 deletion BoxOffice/BoxOffice/BoxOfficeApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ import SwiftUI

@main
struct BoxOfficeApp: App {
@StateObject private var navigationStore = NavigationStore()

var body: some Scene {
WindowGroup {
BoxOfficeView(viewModel: BoxOfficeViewModel())
BoxOfficeView(store: BoxOfficeStore())
.environmentObject(navigationStore)
}
}
}
8 changes: 6 additions & 2 deletions BoxOffice/BoxOffice/Network/Service/BoxOfficeService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import Foundation

import Moya

final class BoxOfficeService {
static let shared = BoxOfficeService()
protocol BoxOfficeServiceProtocol {
func fetchBoxOffice(date: String) async throws -> BoxOfficeResponse
func fetchMovieDetail(movieId: String) async throws -> MovieResponse
}

final class BoxOfficeService: BoxOfficeServiceProtocol {

private let provider = MoyaProvider<BoxOfficeAPI>(plugins: [MoyaLoggingPlugin()])

Expand Down
17 changes: 17 additions & 0 deletions BoxOffice/BoxOffice/Source/Common/NavigationDomain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// NavigationDomain.swift
// BoxOffice
//
// Created by ์ตœ์•ˆ์šฉ on 1/12/25.
//

import Foundation

enum NavigationIntent {
case push(ViewType)
case pop
}

enum ViewType: Hashable {
case movieDetail(String)
}
29 changes: 29 additions & 0 deletions BoxOffice/BoxOffice/Source/Common/NavigationStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// NavigationStore.swift
// BoxOffice
//
// Created by ์ตœ์•ˆ์šฉ on 1/12/25.
//

import SwiftUI

final class NavigationStore: ObservableObject {
@Published var path: [ViewType] = []

@ViewBuilder
func bulid(_ view: ViewType) -> some View {
switch view {
case .movieDetail(let movieId):
MovieDetailView(movieId: movieId)
}
}

func dispatch(_ intent: NavigationIntent) {
switch intent {
case .push(let view):
path.append(view)
case .pop:
path.removeLast()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// BoxOfficeDomain.swift
// BoxOffice
//
// Created by ์ตœ์•ˆ์šฉ on 1/8/25.
//

import Foundation

struct BoxOfficeState {
var boxOffice: [DetailBoxOffice] = []
var movie: MovieInfo? = nil
var date: String = ""
}


enum BoxOfficeIntent {
case onAppear
case changeDate(plus: Int)
case fetchBoxOffice
case fetchMovieInfo(movieId: String)
}
94 changes: 94 additions & 0 deletions BoxOffice/BoxOffice/Source/Features/BoxOffice/BoxOfficeStore.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
//
// BoxOfficeStore.swift
// BoxOffice
//
// Created by ์ตœ์•ˆ์šฉ on 1/5/25.
//

import Foundation

final class BoxOfficeStore: ObservableObject {
@Published private(set) var state: BoxOfficeState

private let boxOfficeService: BoxOfficeService

init(
state: BoxOfficeState = BoxOfficeState(),
boxOfficeService: BoxOfficeService = BoxOfficeService()
) {
self.state = state
self.boxOfficeService = boxOfficeService
}

func dispatch(_ intent: BoxOfficeIntent) {
switch intent {
case .onAppear:
Task {
do {
await currentDate()
try await fetchBoxOffice()
} catch {
print(error)
}
}
case .changeDate(let plus):
Task {
do {
await calulateDate(plus)
try await fetchBoxOffice()
} catch {
print(error)
}
}
case .fetchBoxOffice:
Task {
do {
try await fetchBoxOffice()
} catch {
print(error)
}
}
case .fetchMovieInfo(let movieId):
Task {
do {
try await fetchMovieInfo(movieId)
} catch {
print(error)
}
}
}
}
}

//MARK: - Functions

extension BoxOfficeStore {
@MainActor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๋ฐ˜๋ณต๋˜๋Š” MainActor์˜ ์‚ฌ์šฉ์ด ์ข€๋” ๊ฐœ์„ ํ•  ๋ถ€๋ถ„์ด ์žˆ์„๊ฒƒ๊ฐ™์€๋ฐ,,,, ์ˆ˜์ •์„ ์–ด๋–ป๊ฒŒ ํ•ด์•ผํ•˜๋Š”์ง€๊ฐ€ ์•ˆ๋ณด์ด๋„ค์š” ํ 
์ถ”ํ›„์— ๊ฐ™์ด ๊ณ ๋ฏผํ•ด๋ณด์ฃ !

private func currentDate() {
let date = Date()
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd"
state.date = df.string(from: date)
}

@MainActor
private func fetchBoxOffice() async throws {
let date: String = state.date.split(separator: "-").map { String($0) }.joined()
state.boxOffice = try await boxOfficeService.fetchBoxOffice(date: date).boxOfficeResult.dailyBoxOfficeList
}

@MainActor
private func calulateDate(_ plus: Int) {
let df = DateFormatter()
df.dateFormat = "yyyy-MM-dd"
guard let currentDate = df.date(from: state.date) else { return }

let resultDate = Calendar.current.date(byAdding: .day, value: plus, to: currentDate)
state.date = df.string(from: resultDate ?? Date())
}

@MainActor
private func fetchMovieInfo(_ movieId: String) async throws {
state.movie = try await boxOfficeService.fetchMovieDetail(movieId: movieId).movieInfoResult.movieInfo
}
}
46 changes: 25 additions & 21 deletions BoxOffice/BoxOffice/Source/Features/BoxOffice/BoxOfficeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,33 @@
import SwiftUI

struct BoxOfficeView: View {
@StateObject var viewModel: BoxOfficeViewModel
@StateObject var store: BoxOfficeStore
@EnvironmentObject private var navigationStore: NavigationStore

var body: some View {
NavigationStack {
NavigationStack(path: $navigationStore.path) {
ScrollView(showsIndicators: false) {
TitleView(viewModel: viewModel)
BoxOfficeListView(viewModel: viewModel)
TitleView(store: store)
BoxOfficeListView(store: store)
}
.navigationDestination(item: $viewModel.movie) { movie in
MovieDetailView(movie: movie)
.navigationDestination(for: ViewType.self) { view in
navigationStore.bulid(view)
.environmentObject(store)
}
}
.onAppear {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ‘

store.dispatch(.onAppear)
}
}
}

//MARK: - TitleView

private struct TitleView: View {
@ObservedObject private var viewModel: BoxOfficeViewModel
@ObservedObject private var store: BoxOfficeStore

fileprivate init(viewModel: BoxOfficeViewModel) {
self.viewModel = viewModel
fileprivate init(store: BoxOfficeStore) {
self.store = store
}

fileprivate var body: some View {
Expand All @@ -40,17 +45,17 @@ private struct TitleView: View {

HStack {
Button {
viewModel.calulateDate(-1)
store.dispatch(.changeDate(plus: -1))
} label: {
Image(systemName: "control")
.rotationEffect(Angle(degrees: -90))
}

Text("\(viewModel.date)")
Text("\(store.state.date)")
.font(.headline)

Button {
viewModel.calulateDate(1)
store.dispatch(.changeDate(plus: 1))
} label: {
Image(systemName: "control")
.rotationEffect(Angle(degrees: 90))
Expand All @@ -64,25 +69,24 @@ private struct TitleView: View {
//MARK: - BoxOfficeListView

private struct BoxOfficeListView: View {
@ObservedObject private var viewModel: BoxOfficeViewModel
@ObservedObject private var store: BoxOfficeStore
@EnvironmentObject private var navigationStore: NavigationStore

fileprivate init(viewModel: BoxOfficeViewModel) {
self.viewModel = viewModel
fileprivate init(store: BoxOfficeStore) {
self.store = store
}

fileprivate var body: some View {
Group {
if viewModel.boxOffice.isEmpty {
if store.state.boxOffice.isEmpty {
Text("์ง‘๊ณ„๋˜์ง€ ์•Š์€ ๋‚ ์งœ์ž…๋‹ˆ๋‹ค!")
.font(.headline)
.padding(.top, 300)
} else {
ForEach(viewModel.boxOffice, id: \.self) { boxOffice in
ForEach(store.state.boxOffice, id: \.self) { boxOffice in
BoxOfficeCell(boxOffice: boxOffice)
.onTapGesture {
Task {
try await viewModel.fetchMovieInfo(boxOffice.movieCd)
}
navigationStore.dispatch(.push(.movieDetail(boxOffice.movieCd)))
}
}
}
Expand Down Expand Up @@ -131,5 +135,5 @@ private struct BoxOfficeCell:View {
}

#Preview {
BoxOfficeView(viewModel: BoxOfficeViewModel())
BoxOfficeView(store: BoxOfficeStore())
}

This file was deleted.

20 changes: 12 additions & 8 deletions BoxOffice/BoxOffice/Source/Features/Movie/MovieDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,24 @@
import SwiftUI

struct MovieDetailView: View {
let movie: MovieInfo
@EnvironmentObject private var store: BoxOfficeStore
let movieId: String

var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("\(movie.movieNm)")
Text("์ƒ์˜ ์‹œ๊ฐ„: \(movie.showTm)")
Text("๊ฐœ๋ด‰์ผ: \(movie.openDt)")
Text("์˜ํ™” ์œ ํ˜•: \(movie.typeNm)")
Text("์ œ์ž‘ ๊ตญ๊ฐ€: \(movie.nations.first?.nationNm ?? "์˜ค๋ฅ˜")")
Text("์žฅ๋ฅด: \(movie.genres.first?.genreNm ?? "์˜ค๋ฅ˜")")
Text("\(store.state.movie?.movieNm ?? "")")
Text("์ƒ์˜ ์‹œ๊ฐ„: \(store.state.movie?.showTm ?? "")")
Text("๊ฐœ๋ด‰์ผ: \(store.state.movie?.openDt ?? "")")
Text("์˜ํ™” ์œ ํ˜•: \(store.state.movie?.typeNm ?? "")")
Text("์ œ์ž‘ ๊ตญ๊ฐ€: \(store.state.movie?.nations.first?.nationNm ?? "์˜ค๋ฅ˜")")
Text("์žฅ๋ฅด: \(store.state.movie?.genres.first?.genreNm ?? "์˜ค๋ฅ˜")")
}
.onAppear {
store.dispatch(.fetchMovieInfo(movieId: movieId))
}
}
}

#Preview {
MovieDetailView(movie: .init(movieNm: "dk", showTm: "dlkjf", openDt: "djfowing", typeNm: "dklsjf", nations: [], genres: []))
MovieDetailView(movieId: "kd")
}