From 62c8236801612956afef0836d7229665f1788932 Mon Sep 17 00:00:00 2001 From: KTH Date: Sun, 11 Aug 2024 20:34:28 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=E2=9C=A8=20::=20[#1067]=20=EC=95=84?= =?UTF-8?q?=ED=8B=B0=EC=8A=A4=ED=8A=B8=20=EC=83=81=EC=84=B8=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Application/AppComponent+Artist.swift | 6 ++ .../DataSource/RemoteArtistDataSource.swift | 3 +- ...istListEntity.swift => ArtistEntity.swift} | 10 +-- .../Repository/ArtistRepository.swift | 3 +- .../UseCase/FetchArtistDetailUseCase.swift | 6 ++ .../UseCase/FetchArtistListUseCase.swift | 2 +- .../ArtistDomain/Sources/API/ArtistAPI.swift | 7 +- .../RemoteArtistDataSourceImpl.swift | 8 +- .../Repository/ArtistRepositoryImpl.swift | 6 +- .../ResponseDTO/ArtistDetailResponseDTO.swift | 89 +++++++++++++++++++ .../ResponseDTO/ArtistListResponseDTO.swift | 6 +- .../FetchArtistDetailUseCaseImpl.swift | 17 ++++ .../UseCase/FetchArtistListUseCaseImpl.swift | 2 +- .../UseCase/FetchArtistListUseCaseSpy.swift | 4 +- .../Interface/ArtistDetailFactory.swift | 2 +- .../ArtistFeature/Resources/Artist.storyboard | 10 +++ .../Components/ArtistDetailComponent.swift | 6 +- .../Components/ArtistMusicComponent.swift | 2 +- .../ArtistMusicContentComponent.swift | 2 +- .../Sources/Reactors/ArtistReactor.swift | 10 +-- .../ArtistDetailHeaderViewController.swift | 4 +- .../ArtistDetailViewController.swift | 34 ++++--- .../ArtistMusicContentViewController.swift | 34 +++++-- .../ArtistMusicViewController.swift | 4 +- .../ArtistViewController.swift | 4 +- .../ViewModels/ArtistDetailViewModel.swift | 20 ++++- .../ArtistMusicContentViewModel.swift | 4 +- .../Sources/Views/ArtistListCell.swift | 2 +- .../Tests/ArtistReactorTests.swift | 6 +- .../Features/SongCreditFeature/Project.swift | 1 + .../Component/SongCreditComponent.swift | 5 +- .../Sources/SongCreditViewController.swift | 10 ++- 32 files changed, 262 insertions(+), 67 deletions(-) rename Projects/Domains/ArtistDomain/Interface/Entity/{ArtistListEntity.swift => ArtistEntity.swift} (86%) create mode 100644 Projects/Domains/ArtistDomain/Interface/UseCase/FetchArtistDetailUseCase.swift create mode 100644 Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistDetailResponseDTO.swift create mode 100644 Projects/Domains/ArtistDomain/Sources/UseCase/FetchArtistDetailUseCaseImpl.swift diff --git a/Projects/App/Sources/Application/AppComponent+Artist.swift b/Projects/App/Sources/Application/AppComponent+Artist.swift index 56c8173fc..c6ff090c8 100644 --- a/Projects/App/Sources/Application/AppComponent+Artist.swift +++ b/Projects/App/Sources/Application/AppComponent+Artist.swift @@ -32,6 +32,12 @@ public extension AppComponent { ArtistDetailComponent(parent: self) } + var fetchArtistDetailUseCase: any FetchArtistDetailUseCase { + shared { + FetchArtistDetailUseCaseImpl(artistRepository: artistRepository) + } + } + var fetchArtistSongListUseCase: any FetchArtistSongListUseCase { shared { FetchArtistSongListUseCaseImpl(artistRepository: artistRepository) diff --git a/Projects/Domains/ArtistDomain/Interface/DataSource/RemoteArtistDataSource.swift b/Projects/Domains/ArtistDomain/Interface/DataSource/RemoteArtistDataSource.swift index 38bf5153a..13b9d9204 100644 --- a/Projects/Domains/ArtistDomain/Interface/DataSource/RemoteArtistDataSource.swift +++ b/Projects/Domains/ArtistDomain/Interface/DataSource/RemoteArtistDataSource.swift @@ -2,7 +2,8 @@ import Foundation import RxSwift public protocol RemoteArtistDataSource { - func fetchArtistList() -> Single<[ArtistListEntity]> + func fetchArtistList() -> Single<[ArtistEntity]> + func fetchArtistDetail(id: String) -> Single func fetchArtistSongList(id: String, sort: ArtistSongSortType, page: Int) -> Single<[ArtistSongListEntity]> func fetchArtistSubscriptionStatus(id: String) -> Single func subscriptionArtist(id: String, on: Bool) -> Completable diff --git a/Projects/Domains/ArtistDomain/Interface/Entity/ArtistListEntity.swift b/Projects/Domains/ArtistDomain/Interface/Entity/ArtistEntity.swift similarity index 86% rename from Projects/Domains/ArtistDomain/Interface/Entity/ArtistListEntity.swift rename to Projects/Domains/ArtistDomain/Interface/Entity/ArtistEntity.swift index b0c9e69c2..b4cf8500a 100644 --- a/Projects/Domains/ArtistDomain/Interface/Entity/ArtistListEntity.swift +++ b/Projects/Domains/ArtistDomain/Interface/Entity/ArtistEntity.swift @@ -1,6 +1,6 @@ import Foundation -public struct ArtistListEntity: Equatable { +public struct ArtistEntity: Equatable { public init( id: String, krName: String, @@ -12,7 +12,7 @@ public struct ArtistListEntity: Equatable { roundImage: String, squareImage: String, graduated: Bool, - playlist: ArtistListEntity.Playlist, + playlist: ArtistEntity.Playlist, isHiddenItem: Bool ) { self.id = id @@ -38,12 +38,12 @@ public struct ArtistListEntity: Equatable { public let personalColor: String public let roundImage, squareImage: String public let graduated: Bool - public let playlist: ArtistListEntity.Playlist + public let playlist: ArtistEntity.Playlist public var isHiddenItem: Bool = false } -public extension ArtistListEntity { - struct Playlist: Decodable { +public extension ArtistEntity { + struct Playlist { public let latest, popular, oldest: String public init(latest: String, popular: String, oldest: String) { diff --git a/Projects/Domains/ArtistDomain/Interface/Repository/ArtistRepository.swift b/Projects/Domains/ArtistDomain/Interface/Repository/ArtistRepository.swift index 22de95d4f..45d84abd2 100644 --- a/Projects/Domains/ArtistDomain/Interface/Repository/ArtistRepository.swift +++ b/Projects/Domains/ArtistDomain/Interface/Repository/ArtistRepository.swift @@ -2,7 +2,8 @@ import Foundation import RxSwift public protocol ArtistRepository { - func fetchArtistList() -> Single<[ArtistListEntity]> + func fetchArtistList() -> Single<[ArtistEntity]> + func fetchArtistDetail(id: String) -> Single func fetchArtistSongList(id: String, sort: ArtistSongSortType, page: Int) -> Single<[ArtistSongListEntity]> func fetchArtistSubscriptionStatus(id: String) -> Single func subscriptionArtist(id: String, on: Bool) -> Completable diff --git a/Projects/Domains/ArtistDomain/Interface/UseCase/FetchArtistDetailUseCase.swift b/Projects/Domains/ArtistDomain/Interface/UseCase/FetchArtistDetailUseCase.swift new file mode 100644 index 000000000..f953bebd1 --- /dev/null +++ b/Projects/Domains/ArtistDomain/Interface/UseCase/FetchArtistDetailUseCase.swift @@ -0,0 +1,6 @@ +import Foundation +import RxSwift + +public protocol FetchArtistDetailUseCase { + func execute(id: String) -> Single +} diff --git a/Projects/Domains/ArtistDomain/Interface/UseCase/FetchArtistListUseCase.swift b/Projects/Domains/ArtistDomain/Interface/UseCase/FetchArtistListUseCase.swift index 7290fdb63..33071ee5b 100644 --- a/Projects/Domains/ArtistDomain/Interface/UseCase/FetchArtistListUseCase.swift +++ b/Projects/Domains/ArtistDomain/Interface/UseCase/FetchArtistListUseCase.swift @@ -2,5 +2,5 @@ import Foundation import RxSwift public protocol FetchArtistListUseCase { - func execute() -> Single<[ArtistListEntity]> + func execute() -> Single<[ArtistEntity]> } diff --git a/Projects/Domains/ArtistDomain/Sources/API/ArtistAPI.swift b/Projects/Domains/ArtistDomain/Sources/API/ArtistAPI.swift index c51ff6b0c..7ce3569a1 100644 --- a/Projects/Domains/ArtistDomain/Sources/API/ArtistAPI.swift +++ b/Projects/Domains/ArtistDomain/Sources/API/ArtistAPI.swift @@ -6,6 +6,7 @@ import Moya public enum ArtistAPI { case fetchArtistList + case fetchArtistDetail(id: String) case fetchArtistSongList(id: String, sort: ArtistSongSortType, page: Int) case fetchSubscriptionStatus(id: String) case subscriptionArtist(id: String, on: Bool) @@ -20,6 +21,8 @@ extension ArtistAPI: WMAPI { switch self { case .fetchArtistList: return "/list" + case let .fetchArtistDetail(id): + return "/\(id)" case let .fetchArtistSongList(id, _, _): return "/\(id)/songs" case let .fetchSubscriptionStatus(id): @@ -32,6 +35,7 @@ extension ArtistAPI: WMAPI { public var method: Moya.Method { switch self { case .fetchArtistList, + .fetchArtistDetail, .fetchArtistSongList, .fetchSubscriptionStatus: return .get @@ -43,6 +47,7 @@ extension ArtistAPI: WMAPI { public var task: Moya.Task { switch self { case .fetchArtistList, + .fetchArtistDetail, .fetchSubscriptionStatus, .subscriptionArtist: return .requestPlain @@ -59,7 +64,7 @@ extension ArtistAPI: WMAPI { public var jwtTokenType: JwtTokenType { switch self { - case .fetchArtistList, .fetchArtistSongList: + case .fetchArtistList, .fetchArtistDetail, .fetchArtistSongList: return .none case .fetchSubscriptionStatus, .subscriptionArtist: return .accessToken diff --git a/Projects/Domains/ArtistDomain/Sources/DataSource/RemoteArtistDataSourceImpl.swift b/Projects/Domains/ArtistDomain/Sources/DataSource/RemoteArtistDataSourceImpl.swift index adaf79105..36c18ffe4 100644 --- a/Projects/Domains/ArtistDomain/Sources/DataSource/RemoteArtistDataSourceImpl.swift +++ b/Projects/Domains/ArtistDomain/Sources/DataSource/RemoteArtistDataSourceImpl.swift @@ -4,12 +4,18 @@ import Foundation import RxSwift public final class RemoteArtistDataSourceImpl: BaseRemoteDataSource, RemoteArtistDataSource { - public func fetchArtistList() -> Single<[ArtistListEntity]> { + public func fetchArtistList() -> Single<[ArtistEntity]> { request(.fetchArtistList) .map([ArtistListResponseDTO].self) .map { $0.map { $0.toDomain() } } } + public func fetchArtistDetail(id: String) -> Single { + request(.fetchArtistDetail(id: id)) + .map(ArtistDetailResponseDTO.self) + .map { $0.toDomain() } + } + public func fetchArtistSongList(id: String, sort: ArtistSongSortType, page: Int) -> Single<[ArtistSongListEntity]> { request(.fetchArtistSongList(id: id, sort: sort, page: page)) .map([ArtistSongListResponseDTO].self) diff --git a/Projects/Domains/ArtistDomain/Sources/Repository/ArtistRepositoryImpl.swift b/Projects/Domains/ArtistDomain/Sources/Repository/ArtistRepositoryImpl.swift index 4719ff14f..80b82c2ce 100644 --- a/Projects/Domains/ArtistDomain/Sources/Repository/ArtistRepositoryImpl.swift +++ b/Projects/Domains/ArtistDomain/Sources/Repository/ArtistRepositoryImpl.swift @@ -10,10 +10,14 @@ public final class ArtistRepositoryImpl: ArtistRepository { self.remoteArtistDataSource = remoteArtistDataSource } - public func fetchArtistList() -> Single<[ArtistListEntity]> { + public func fetchArtistList() -> Single<[ArtistEntity]> { remoteArtistDataSource.fetchArtistList() } + public func fetchArtistDetail(id: String) -> Single { + remoteArtistDataSource.fetchArtistDetail(id: id) + } + public func fetchArtistSongList(id: String, sort: ArtistSongSortType, page: Int) -> Single<[ArtistSongListEntity]> { remoteArtistDataSource.fetchArtistSongList(id: id, sort: sort, page: page) } diff --git a/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistDetailResponseDTO.swift b/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistDetailResponseDTO.swift new file mode 100644 index 000000000..dbb50ca3d --- /dev/null +++ b/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistDetailResponseDTO.swift @@ -0,0 +1,89 @@ +import ArtistDomainInterface +import Foundation + +public struct ArtistDetailResponseDTO: Decodable, Equatable { + let id: String + let name: ArtistDetailResponseDTO.Name + let group: ArtistDetailResponseDTO.Group + let info: ArtistDetailResponseDTO.Info + let imageURL: ArtistDetailResponseDTO.ImageURL + let graduated: Bool + + public static func == (lhs: Self, rhs: Self) -> Bool { + return lhs.id == rhs.id + } + + private enum CodingKeys: String, CodingKey { + case id + case name + case group + case info + case imageURL = "imageUrl" + case graduated + } +} + +public extension ArtistDetailResponseDTO { + struct Name: Decodable { + let krName: String + let enName: String + + private enum CodingKeys: String, CodingKey { + case krName = "krShort" + case enName = "en" + } + } + + struct Group: Decodable { + let name: String + } + + struct Info: Decodable { + let title: ArtistDetailResponseDTO.Info.Title + let description: String + let color: ArtistDetailResponseDTO.Info.Color + let playlist: ArtistDetailResponseDTO.Info.Playlist + } + + struct ImageURL: Decodable { + let round: String + let square: String + } +} + +public extension ArtistDetailResponseDTO.Info { + struct Title: Decodable { + let short: String + } + + struct Color: Decodable { + let background: [[String]] + } + + struct Playlist: Decodable { + let latest, popular, oldest: String + } +} + +public extension ArtistDetailResponseDTO { + func toDomain() -> ArtistEntity { + ArtistEntity( + id: id, + krName: name.krName, + enName: name.enName, + groupName: group.name, + title: info.title.short, + description: info.description, + personalColor: info.color.background.flatMap { $0 }.first ?? "ffffff", + roundImage: imageURL.round, + squareImage: imageURL.square, + graduated: graduated, + playlist: ArtistEntity.Playlist( + latest: info.playlist.latest, + popular: info.playlist.popular, + oldest: info.playlist.oldest + ), + isHiddenItem: false + ) + } +} diff --git a/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistListResponseDTO.swift b/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistListResponseDTO.swift index cc5711076..98f1ca88a 100644 --- a/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistListResponseDTO.swift +++ b/Projects/Domains/ArtistDomain/Sources/ResponseDTO/ArtistListResponseDTO.swift @@ -66,8 +66,8 @@ public extension ArtistListResponseDTO.Info { } public extension ArtistListResponseDTO { - func toDomain() -> ArtistListEntity { - ArtistListEntity( + func toDomain() -> ArtistEntity { + ArtistEntity( id: id, krName: name.krName, enName: name.enName, @@ -78,7 +78,7 @@ public extension ArtistListResponseDTO { roundImage: imageURL.round, squareImage: imageURL.square, graduated: graduated, - playlist: ArtistListEntity.Playlist( + playlist: ArtistEntity.Playlist( latest: info.playlist.latest, popular: info.playlist.popular, oldest: info.playlist.oldest diff --git a/Projects/Domains/ArtistDomain/Sources/UseCase/FetchArtistDetailUseCaseImpl.swift b/Projects/Domains/ArtistDomain/Sources/UseCase/FetchArtistDetailUseCaseImpl.swift new file mode 100644 index 000000000..52e0dc79b --- /dev/null +++ b/Projects/Domains/ArtistDomain/Sources/UseCase/FetchArtistDetailUseCaseImpl.swift @@ -0,0 +1,17 @@ +import ArtistDomainInterface +import Foundation +import RxSwift + +public struct FetchArtistDetailUseCaseImpl: FetchArtistDetailUseCase { + private let artistRepository: any ArtistRepository + + public init( + artistRepository: ArtistRepository + ) { + self.artistRepository = artistRepository + } + + public func execute(id: String) -> Single { + artistRepository.fetchArtistDetail(id: id) + } +} diff --git a/Projects/Domains/ArtistDomain/Sources/UseCase/FetchArtistListUseCaseImpl.swift b/Projects/Domains/ArtistDomain/Sources/UseCase/FetchArtistListUseCaseImpl.swift index b4d99770c..97ec4579c 100644 --- a/Projects/Domains/ArtistDomain/Sources/UseCase/FetchArtistListUseCaseImpl.swift +++ b/Projects/Domains/ArtistDomain/Sources/UseCase/FetchArtistListUseCaseImpl.swift @@ -11,7 +11,7 @@ public struct FetchArtistListUseCaseImpl: FetchArtistListUseCase { self.artistRepository = artistRepository } - public func execute() -> Single<[ArtistListEntity]> { + public func execute() -> Single<[ArtistEntity]> { artistRepository.fetchArtistList() } } diff --git a/Projects/Domains/ArtistDomain/Testing/UseCase/FetchArtistListUseCaseSpy.swift b/Projects/Domains/ArtistDomain/Testing/UseCase/FetchArtistListUseCaseSpy.swift index ecba80163..56f15cd9c 100644 --- a/Projects/Domains/ArtistDomain/Testing/UseCase/FetchArtistListUseCaseSpy.swift +++ b/Projects/Domains/ArtistDomain/Testing/UseCase/FetchArtistListUseCaseSpy.swift @@ -4,8 +4,8 @@ import RxSwift public final class FetchArtistListUseCaseSpy: FetchArtistListUseCase { public private(set) var callCount = 0 - public var handler: (() -> Single<[ArtistListEntity]>) = { .never() } - public func execute() -> Single<[ArtistListEntity]> { + public var handler: (() -> Single<[ArtistEntity]>) = { .never() } + public func execute() -> Single<[ArtistEntity]> { callCount += 1 return handler() } diff --git a/Projects/Features/ArtistFeature/Interface/ArtistDetailFactory.swift b/Projects/Features/ArtistFeature/Interface/ArtistDetailFactory.swift index c494a3931..21751cdc3 100644 --- a/Projects/Features/ArtistFeature/Interface/ArtistDetailFactory.swift +++ b/Projects/Features/ArtistFeature/Interface/ArtistDetailFactory.swift @@ -3,5 +3,5 @@ import Foundation import UIKit public protocol ArtistDetailFactory { - func makeView(model: ArtistListEntity) -> UIViewController + func makeView(artistID: String) -> UIViewController } diff --git a/Projects/Features/ArtistFeature/Resources/Artist.storyboard b/Projects/Features/ArtistFeature/Resources/Artist.storyboard index 88d51b36d..b9a40a4da 100644 --- a/Projects/Features/ArtistFeature/Resources/Artist.storyboard +++ b/Projects/Features/ArtistFeature/Resources/Artist.storyboard @@ -351,12 +351,21 @@ + + + + + + + + + @@ -373,6 +382,7 @@ + diff --git a/Projects/Features/ArtistFeature/Sources/Components/ArtistDetailComponent.swift b/Projects/Features/ArtistFeature/Sources/Components/ArtistDetailComponent.swift index 74bcc8393..1ccccc77e 100644 --- a/Projects/Features/ArtistFeature/Sources/Components/ArtistDetailComponent.swift +++ b/Projects/Features/ArtistFeature/Sources/Components/ArtistDetailComponent.swift @@ -8,6 +8,7 @@ import UIKit public protocol ArtistDetailDependency: Dependency { var artistMusicComponent: ArtistMusicComponent { get } + var fetchArtistDetailUseCase: any FetchArtistDetailUseCase { get } var fetchArtistSubscriptionStatusUseCase: any FetchArtistSubscriptionStatusUseCase { get } var subscriptionArtistUseCase: any SubscriptionArtistUseCase { get } var textPopUpFactory: any TextPopUpFactory { get } @@ -15,10 +16,11 @@ public protocol ArtistDetailDependency: Dependency { } public final class ArtistDetailComponent: Component, ArtistDetailFactory { - public func makeView(model: ArtistListEntity) -> UIViewController { + public func makeView(artistID: String) -> UIViewController { return ArtistDetailViewController.viewController( viewModel: .init( - model: model, + artistID: artistID, + fetchArtistDetailUseCase: dependency.fetchArtistDetailUseCase, fetchArtistSubscriptionStatusUseCase: dependency.fetchArtistSubscriptionStatusUseCase, subscriptionArtistUseCase: dependency.subscriptionArtistUseCase ), diff --git a/Projects/Features/ArtistFeature/Sources/Components/ArtistMusicComponent.swift b/Projects/Features/ArtistFeature/Sources/Components/ArtistMusicComponent.swift index 1722d00f1..b3251233f 100644 --- a/Projects/Features/ArtistFeature/Sources/Components/ArtistMusicComponent.swift +++ b/Projects/Features/ArtistFeature/Sources/Components/ArtistMusicComponent.swift @@ -15,7 +15,7 @@ public protocol ArtistMusicDependency: Dependency { } public final class ArtistMusicComponent: Component { - public func makeView(model: ArtistListEntity?) -> ArtistMusicViewController { + public func makeView(model: ArtistEntity?) -> ArtistMusicViewController { return ArtistMusicViewController.viewController( model: model, artistMusicContentComponent: dependency.artistMusicContentComponent diff --git a/Projects/Features/ArtistFeature/Sources/Components/ArtistMusicContentComponent.swift b/Projects/Features/ArtistFeature/Sources/Components/ArtistMusicContentComponent.swift index 79641f34e..2932a305e 100644 --- a/Projects/Features/ArtistFeature/Sources/Components/ArtistMusicContentComponent.swift +++ b/Projects/Features/ArtistFeature/Sources/Components/ArtistMusicContentComponent.swift @@ -16,7 +16,7 @@ public protocol ArtistMusicContentDependency: Dependency { public final class ArtistMusicContentComponent: Component { public func makeView( type: ArtistSongSortType, - model: ArtistListEntity? + model: ArtistEntity? ) -> ArtistMusicContentViewController { return ArtistMusicContentViewController.viewController( viewModel: .init( diff --git a/Projects/Features/ArtistFeature/Sources/Reactors/ArtistReactor.swift b/Projects/Features/ArtistFeature/Sources/Reactors/ArtistReactor.swift index c8ff1e425..90d2c1ce0 100644 --- a/Projects/Features/ArtistFeature/Sources/Reactors/ArtistReactor.swift +++ b/Projects/Features/ArtistFeature/Sources/Reactors/ArtistReactor.swift @@ -8,11 +8,11 @@ public final class ArtistReactor: Reactor { } public enum Mutation { - case updateArtistList([ArtistListEntity]) + case updateArtistList([ArtistEntity]) } public struct State { - var artistList: [ArtistListEntity] + var artistList: [ArtistEntity] } public var initialState: State @@ -60,7 +60,7 @@ private extension ArtistReactor { // Waterfall Grid UI가 기본적으로 왼쪽부터 쌓이게 되기에 첫번째 Cell을 hide 시킵니다 if newArtistList.count == 1 { - let hiddenItem: ArtistListEntity = self.makeHiddenArtistEntity() + let hiddenItem: ArtistEntity = self.makeHiddenArtistEntity() newArtistList.insert(hiddenItem, at: 0) } else { newArtistList.swapAt(0, 1) @@ -73,8 +73,8 @@ private extension ArtistReactor { // MARK: - Reusable private extension ArtistReactor { - func makeHiddenArtistEntity() -> ArtistListEntity { - ArtistListEntity( + func makeHiddenArtistEntity() -> ArtistEntity { + ArtistEntity( id: "", krName: "", enName: "", diff --git a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailHeaderViewController.swift b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailHeaderViewController.swift index d04340187..7346553c5 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailHeaderViewController.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailHeaderViewController.swift @@ -27,7 +27,7 @@ class ArtistDetailHeaderViewController: UIViewController, ViewControllerFromStor @IBOutlet weak var introDescriptionLabel: UILabel! var disposeBag: DisposeBag = DisposeBag() - private var model: ArtistListEntity? + private var model: ArtistEntity? deinit { DEBUG_LOG("\(Self.self) Deinit") @@ -49,7 +49,7 @@ class ArtistDetailHeaderViewController: UIViewController, ViewControllerFromStor } extension ArtistDetailHeaderViewController { - func update(model: ArtistListEntity) { + func update(model: ArtistEntity) { self.model = model let artistName: String = model.krName let artistEngName: String = model.enName.capitalizingFirstLetter diff --git a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift index d2c8c80f9..107deb81f 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift @@ -7,6 +7,7 @@ import RxSwift import SignInFeatureInterface import UIKit import Utility +import NVActivityIndicatorView public final class ArtistDetailViewController: UIViewController, ViewControllerFromStoryBoard, ContainerViewType { @IBOutlet weak var gradationView: UIView! @@ -15,16 +16,14 @@ public final class ArtistDetailViewController: UIViewController, ViewControllerF @IBOutlet weak var headerContentView: UIView! @IBOutlet weak var headerContentViewTopConstraint: NSLayoutConstraint! @IBOutlet public weak var contentView: UIView! + @IBOutlet weak var activityIndicator: NVActivityIndicatorView! private lazy var headerViewController: ArtistDetailHeaderViewController = { let header = ArtistDetailHeaderViewController.viewController() return header }() - private lazy var contentViewController: ArtistMusicViewController = { - let content = artistMusicComponent.makeView(model: viewModel.model) - return content - }() + private var contentViewController: ArtistMusicViewController? private let gradientLayer = CAGradientLayer() private var artistMusicComponent: ArtistMusicComponent! @@ -53,8 +52,6 @@ public final class ArtistDetailViewController: UIViewController, ViewControllerF override public func viewDidLoad() { super.viewDidLoad() configureUI() - configureHeader() - configureContent() outputBind() inputBind() } @@ -90,6 +87,17 @@ public final class ArtistDetailViewController: UIViewController, ViewControllerF private extension ArtistDetailViewController { func outputBind() { + output.dataSource + .filter { $0 != nil } + .compactMap { $0 } + .bind(with: self) { owner, entity in + owner.configureGradation(model: entity) + owner.configureHeader(model: entity) + owner.configureContent(model: entity) + owner.activityIndicator.stopAnimating() + } + .disposed(by: disposeBag) + output.isSubscription .skip(1) .bind { [subscriptionButton] isSubscription in @@ -141,6 +149,7 @@ private extension ArtistDetailViewController { } func inputBind() { + input.fetchArtistDetail.onNext(()) input.fetchArtistSubscriptionStatus.onNext(()) subscriptionButton.rx.tap @@ -154,8 +163,12 @@ private extension ArtistDetailViewController { subscriptionButton.setImage(DesignSystemAsset.Artist.subscriptionOff.image, for: .normal) subscriptionButton.setImage(DesignSystemAsset.Artist.subscriptionOn.image, for: .selected) subscriptionButton.isHidden = true + activityIndicator.color = DesignSystemAsset.PrimaryColor.point.color + activityIndicator.type = .circleStrokeSpin + activityIndicator.startAnimating() + } - let model = viewModel.model + func configureGradation(model: ArtistEntity) { let flatColor: String = model.personalColor guard !flatColor.isEmpty else { return } @@ -169,7 +182,7 @@ private extension ArtistDetailViewController { gradationView.layer.addSublayer(gradientLayer) } - func configureHeader() { + func configureHeader(model: ArtistEntity) { self.addChild(headerViewController) self.headerContentView.addSubview(headerViewController.view) headerViewController.didMove(toParent: self) @@ -177,12 +190,11 @@ private extension ArtistDetailViewController { headerViewController.view.snp.makeConstraints { $0.edges.equalTo(headerContentView) } - - let model = viewModel.model headerViewController.update(model: model) } - func configureContent() { + func configureContent(model: ArtistEntity) { + contentViewController = artistMusicComponent.makeView(model: model) self.add(asChildViewController: contentViewController) } } diff --git a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift index 92f2059ed..dd6976c2c 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift @@ -29,6 +29,7 @@ public final class ArtistMusicContentViewController: lazy var input = ArtistMusicContentViewModel.Input() lazy var output = viewModel.transform(from: input) private let disposeBag = DisposeBag() + private var isFromArtistScene: Bool = false deinit { LogManager.printDebug("\(Self.self) Deinit") @@ -101,11 +102,18 @@ private extension ArtistMusicContentViewController { .withLatestFrom(output.indexOfSelectedSongs) { ($0, $1) } .do(onNext: { [weak self] dataSource, songs in guard let self = self else { return } - let height = self.tableView.frame.height / 3 * 2 - let warningView = WarningView(frame: CGRect(x: 0, y: 0, width: APP_WIDTH(), height: height)) - warningView.text = "아티스트 곡이 없습니다." - self.tableView.tableFooterView = dataSource.isEmpty ? - warningView : UIView(frame: CGRect(x: 0, y: 0, width: APP_WIDTH(), height: PLAYER_HEIGHT())) + if dataSource.isEmpty { + let height = self.tableView.frame.height / 3 * 2 + let warningView = WarningView(frame: CGRect(x: 0, y: 0, width: APP_WIDTH(), height: height)) + warningView.text = "아티스트 곡이 없습니다." + self.tableView.tableFooterView = warningView + } else { + if self.isFromArtistScene { + self.tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: APP_WIDTH(), height: PLAYER_HEIGHT())) + } else { + self.tableView.tableFooterView = nil + } + } self.activityIndidator.stopAnimating() guard let songCart = self.songCartView else { return } songCart.updateAllSelect(isAll: songs.count == dataSource.count) @@ -144,7 +152,7 @@ private extension ArtistMusicContentViewController { type: .artistSong, selectedSongCount: songs.count, totalSongCount: dataSource.count, - useBottomSpace: false + useBottomSpace: self.isFromArtistScene ? false : true ) self.songCartView?.delegate = self } @@ -153,11 +161,18 @@ private extension ArtistMusicContentViewController { output.showToast .bind(with: self) { owner, message in + var options: WMToastOptions + if owner.isFromArtistScene { + options = owner.output.songEntityOfSelectedSongs.value.isEmpty ? + [.tabBar] : [.tabBar, .songCart] + + } else { + options = owner.output.songEntityOfSelectedSongs.value.isEmpty ? + [.empty] : [.songCart] + } owner.showToast( text: message, - options: owner.output.songEntityOfSelectedSongs.value.isEmpty ? - [.tabBar] : - [.tabBar, .songCart] + options: options ) } .disposed(by: disposeBag) @@ -187,6 +202,7 @@ private extension ArtistMusicContentViewController { activityIndidator.startAnimating() tableView.backgroundColor = .clear songCartOnView.alpha = .zero + isFromArtistScene = navigationController?.viewControllers.first is ArtistViewController } } diff --git a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicViewController.swift b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicViewController.swift index 726c07e64..cefc45db5 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicViewController.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicViewController.swift @@ -19,7 +19,7 @@ public class ArtistMusicViewController: TabmanViewController, ViewControllerFrom }() var artistMusicContentComponent: ArtistMusicContentComponent! - var model: ArtistListEntity? + var model: ArtistEntity? deinit { DEBUG_LOG("\(Self.self) Deinit") @@ -46,7 +46,7 @@ public class ArtistMusicViewController: TabmanViewController, ViewControllerFrom } public static func viewController( - model: ArtistListEntity?, + model: ArtistEntity?, artistMusicContentComponent: ArtistMusicContentComponent ) -> ArtistMusicViewController { let viewController = ArtistMusicViewController.viewController(storyBoardName: "Artist", bundle: Bundle.module) diff --git a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistViewController.swift b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistViewController.swift index 45a4f3934..7214e8451 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistViewController.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistViewController.swift @@ -73,12 +73,12 @@ public final class ArtistViewController: } } else { LogManager.analytics(ArtistAnalyticsLog.clickArtistItem(artist: entity.id)) - let viewController = owner.artistDetailFactory.makeView(model: entity) + let viewController = owner.artistDetailFactory.makeView(artistID: entity.id) owner.navigationController?.pushViewController(viewController, animated: true) } #else LogManager.analytics(ArtistAnalyticsLog.clickArtistItem(artist: entity.id)) - let viewController = owner.artistDetailFactory.makeView(model: entity) + let viewController = owner.artistDetailFactory.makeView(artistID: entity.id) owner.navigationController?.pushViewController(viewController, animated: true) #endif } diff --git a/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift b/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift index 0f72af387..75c5570f4 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift @@ -7,27 +7,32 @@ import RxSwift import Utility public final class ArtistDetailViewModel: ViewModelType { - let model: ArtistListEntity + let artistID: String + private let fetchArtistDetailUseCase: FetchArtistDetailUseCase private let fetchArtistSubscriptionStatusUseCase: FetchArtistSubscriptionStatusUseCase private let subscriptionArtistUseCase: SubscriptionArtistUseCase private let disposeBag = DisposeBag() public init( - model: ArtistListEntity, + artistID: String, + fetchArtistDetailUseCase: any FetchArtistDetailUseCase, fetchArtistSubscriptionStatusUseCase: any FetchArtistSubscriptionStatusUseCase, subscriptionArtistUseCase: any SubscriptionArtistUseCase ) { - self.model = model + self.artistID = artistID + self.fetchArtistDetailUseCase = fetchArtistDetailUseCase self.fetchArtistSubscriptionStatusUseCase = fetchArtistSubscriptionStatusUseCase self.subscriptionArtistUseCase = subscriptionArtistUseCase } public struct Input { + let fetchArtistDetail: PublishSubject = PublishSubject() let fetchArtistSubscriptionStatus: PublishSubject = PublishSubject() let didTapSubscription: PublishSubject = PublishSubject() } public struct Output { + let dataSource: BehaviorRelay = BehaviorRelay(value: nil) let isSubscription: BehaviorRelay = BehaviorRelay(value: false) let showToast: PublishSubject = PublishSubject() let showLogin: PublishSubject = PublishSubject() @@ -36,7 +41,14 @@ public final class ArtistDetailViewModel: ViewModelType { public func transform(from input: Input) -> Output { let output = Output() - let id = model.id + let id = self.artistID + + input.fetchArtistDetail + .flatMap { [fetchArtistDetailUseCase] _ in + return fetchArtistDetailUseCase.execute(id: id) + } + .bind(to: output.dataSource) + .disposed(by: disposeBag) input.fetchArtistSubscriptionStatus .withLatestFrom(PreferenceManager.$userInfo) diff --git a/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistMusicContentViewModel.swift b/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistMusicContentViewModel.swift index c91afd270..21f66e9d2 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistMusicContentViewModel.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistMusicContentViewModel.swift @@ -18,12 +18,12 @@ import Utility public final class ArtistMusicContentViewModel: ViewModelType { private let fetchArtistSongListUseCase: FetchArtistSongListUseCase var type: ArtistSongSortType - var model: ArtistListEntity? + var model: ArtistEntity? private let disposeBag = DisposeBag() public init( type: ArtistSongSortType, - model: ArtistListEntity?, + model: ArtistEntity?, fetchArtistSongListUseCase: any FetchArtistSongListUseCase ) { self.type = type diff --git a/Projects/Features/ArtistFeature/Sources/Views/ArtistListCell.swift b/Projects/Features/ArtistFeature/Sources/Views/ArtistListCell.swift index 83fd75a9c..697a0fa15 100644 --- a/Projects/Features/ArtistFeature/Sources/Views/ArtistListCell.swift +++ b/Projects/Features/ArtistFeature/Sources/Views/ArtistListCell.swift @@ -21,7 +21,7 @@ class ArtistListCell: UICollectionViewCell { } extension ArtistListCell { - func update(model: ArtistListEntity) { + func update(model: ArtistEntity) { self.contentView.alpha = model.isHiddenItem ? 0 : 1 let artistNameAttributedString = NSMutableAttributedString( string: model.krName, diff --git a/Projects/Features/ArtistFeature/Tests/ArtistReactorTests.swift b/Projects/Features/ArtistFeature/Tests/ArtistReactorTests.swift index a3ba24a13..9baa9dcb2 100644 --- a/Projects/Features/ArtistFeature/Tests/ArtistReactorTests.swift +++ b/Projects/Features/ArtistFeature/Tests/ArtistReactorTests.swift @@ -56,9 +56,9 @@ final class ArtistReactorTests: XCTestCase { XCTAssertEqual(sut.currentState.artistList, expectedArtistList) } - private func makeTwoDummyArtistList() -> [ArtistListEntity] { + private func makeTwoDummyArtistList() -> [ArtistEntity] { [ - ArtistListEntity( + ArtistEntity( id: "", krName: "", enName: "", @@ -72,7 +72,7 @@ final class ArtistReactorTests: XCTestCase { playlist: .init(latest: "", popular: "", oldest: ""), isHiddenItem: false ), - ArtistListEntity( + ArtistEntity( id: "2", krName: "nam2", enName: "eng2", diff --git a/Projects/Features/SongCreditFeature/Project.swift b/Projects/Features/SongCreditFeature/Project.swift index 0f2ecd24e..4bbb64a84 100644 --- a/Projects/Features/SongCreditFeature/Project.swift +++ b/Projects/Features/SongCreditFeature/Project.swift @@ -9,6 +9,7 @@ let project = Project.module( .implements(module: .feature(.SongCreditFeature), dependencies: [ .feature(target: .SongCreditFeature, type: .interface), .feature(target: .CreditSongListFeature, type: .interface), + .feature(target: .ArtistFeature, type: .interface), .feature(target: .BaseFeature), .domain(target: .SongsDomain, type: .interface) ]), diff --git a/Projects/Features/SongCreditFeature/Sources/Component/SongCreditComponent.swift b/Projects/Features/SongCreditFeature/Sources/Component/SongCreditComponent.swift index 1f1dce8e7..cccc6cf69 100644 --- a/Projects/Features/SongCreditFeature/Sources/Component/SongCreditComponent.swift +++ b/Projects/Features/SongCreditFeature/Sources/Component/SongCreditComponent.swift @@ -1,3 +1,4 @@ +import ArtistFeatureInterface import CreditSongListFeatureInterface import NeedleFoundation import SongCreditFeatureInterface @@ -7,6 +8,7 @@ import UIKit public protocol SongCreditDependency: Dependency { var fetchSongCreditsUseCase: any FetchSongCreditsUseCase { get } var creditSongListFactory: any CreditSongListFactory { get } + var artistDetailFactory: any ArtistDetailFactory { get } } public final class SongCreditComponent: Component, SongCreditFactory { @@ -17,7 +19,8 @@ public final class SongCreditComponent: Component, SongCre ) let viewController = SongCreditViewController( reactor: reactor, - creditSongListFactory: dependency.creditSongListFactory + creditSongListFactory: dependency.creditSongListFactory, + artistDetailFactory: dependency.artistDetailFactory ) return viewController } diff --git a/Projects/Features/SongCreditFeature/Sources/SongCreditViewController.swift b/Projects/Features/SongCreditFeature/Sources/SongCreditViewController.swift index 8f9264ba6..ea7f39ec0 100644 --- a/Projects/Features/SongCreditFeature/Sources/SongCreditViewController.swift +++ b/Projects/Features/SongCreditFeature/Sources/SongCreditViewController.swift @@ -1,3 +1,4 @@ +import ArtistFeatureInterface import BaseFeature import CreditSongListFeatureInterface import DesignSystem @@ -74,12 +75,15 @@ final class SongCreditViewController: BaseReactorViewController Date: Sun, 11 Aug 2024 20:34:34 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=F0=9F=8E=A8=20::=20=EC=BD=94=EB=93=9C=20Fo?= =?UTF-8?q?rmatting=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewControllers/ArtistDetailViewController.swift | 2 +- .../ViewControllers/ArtistMusicContentViewController.swift | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift index 107deb81f..187b156a6 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift @@ -2,12 +2,12 @@ import ArtistDomainInterface import BaseFeatureInterface import DesignSystem import LogManager +import NVActivityIndicatorView import RxCocoa import RxSwift import SignInFeatureInterface import UIKit import Utility -import NVActivityIndicatorView public final class ArtistDetailViewController: UIViewController, ViewControllerFromStoryBoard, ContainerViewType { @IBOutlet weak var gradationView: UIView! diff --git a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift index dd6976c2c..9a36f271e 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift @@ -109,7 +109,12 @@ private extension ArtistMusicContentViewController { self.tableView.tableFooterView = warningView } else { if self.isFromArtistScene { - self.tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: APP_WIDTH(), height: PLAYER_HEIGHT())) + self.tableView.tableFooterView = UIView(frame: CGRect( + x: 0, + y: 0, + width: APP_WIDTH(), + height: PLAYER_HEIGHT() + )) } else { self.tableView.tableFooterView = nil } From 9a633679afcc96a2bd68a785240f53c1927f9bb3 Mon Sep 17 00:00:00 2001 From: KTH Date: Sun, 11 Aug 2024 20:49:49 +0900 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=90=9B=20::=20[#1067]=20=EB=8D=B0?= =?UTF-8?q?=EB=AA=A8=20=EB=88=84=EB=9D=BD=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SongCreditFeature/Demo/Sources/AppDelegate.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Projects/Features/SongCreditFeature/Demo/Sources/AppDelegate.swift b/Projects/Features/SongCreditFeature/Demo/Sources/AppDelegate.swift index bec9fb502..04ffc7ed7 100644 --- a/Projects/Features/SongCreditFeature/Demo/Sources/AppDelegate.swift +++ b/Projects/Features/SongCreditFeature/Demo/Sources/AppDelegate.swift @@ -1,3 +1,4 @@ +import ArtistFeatureInterface import CreditSongListFeatureInterface import RxSwift @testable import SongCreditFeature @@ -34,7 +35,8 @@ final class AppDelegate: UIResponder, UIApplicationDelegate { ) let viewController = SongCreditViewController( reactor: reactor, - creditSongListFactory: DummyCreditSongListFactory() + creditSongListFactory: DummyCreditSongListFactory(), + artistDetailFactory: DummyArtistDetailFactory() ) window?.rootViewController = viewController window?.makeKeyAndVisible() @@ -49,3 +51,9 @@ final class DummyCreditSongListFactory: CreditSongListFactory { return viewController } } + +final class DummyArtistDetailFactory: ArtistDetailFactory { + func makeView(artistID: String) -> UIViewController { + return UIViewController() + } +} From 5d152b938c4d8cd7e4a1fb285782bc82717c2af1 Mon Sep 17 00:00:00 2001 From: KTH Date: Sun, 11 Aug 2024 20:59:48 +0900 Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=90=9B=20::=20[#1067]=20=EC=8F=AD?= =?UTF-8?q?=EC=B9=B4=ED=8A=B8::=20=EC=A0=84=EC=B2=B4=EC=84=A0=ED=83=9D=20?= =?UTF-8?q?=EC=95=88=EB=88=84=EB=A6=AC=EB=8A=94=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ArtistFeature/Resources/Artist.storyboard | 1 + .../ArtistMusicContentViewController.swift | 23 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Projects/Features/ArtistFeature/Resources/Artist.storyboard b/Projects/Features/ArtistFeature/Resources/Artist.storyboard index b9a40a4da..9f9175a06 100644 --- a/Projects/Features/ArtistFeature/Resources/Artist.storyboard +++ b/Projects/Features/ArtistFeature/Resources/Artist.storyboard @@ -558,6 +558,7 @@ + diff --git a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift index 9a36f271e..9634a69e8 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift @@ -17,7 +17,8 @@ public final class ArtistMusicContentViewController: @IBOutlet weak var tableView: UITableView! @IBOutlet weak var activityIndidator: NVActivityIndicatorView! @IBOutlet weak var songCartOnView: UIView! - + @IBOutlet weak var songCartOnViewHeightConstraint: NSLayoutConstraint! + public var songCartView: SongCartView! public var bottomSheetView: BottomSheetView! private var containSongsFactory: ContainSongsFactory! @@ -108,16 +109,12 @@ private extension ArtistMusicContentViewController { warningView.text = "아티스트 곡이 없습니다." self.tableView.tableFooterView = warningView } else { - if self.isFromArtistScene { - self.tableView.tableFooterView = UIView(frame: CGRect( - x: 0, - y: 0, - width: APP_WIDTH(), - height: PLAYER_HEIGHT() - )) - } else { - self.tableView.tableFooterView = nil - } + self.tableView.tableFooterView = UIView(frame: CGRect( + x: 0, + y: 0, + width: APP_WIDTH(), + height: PLAYER_HEIGHT() + )) } self.activityIndidator.stopAnimating() guard let songCart = self.songCartView else { return } @@ -206,8 +203,10 @@ private extension ArtistMusicContentViewController { activityIndidator.type = .circleStrokeSpin activityIndidator.startAnimating() tableView.backgroundColor = .clear - songCartOnView.alpha = .zero isFromArtistScene = navigationController?.viewControllers.first is ArtistViewController + songCartOnView.alpha = .zero + songCartOnViewHeightConstraint.constant = isFromArtistScene ? + PLAYER_HEIGHT() : PLAYER_HEIGHT() + SAFEAREA_BOTTOM_HEIGHT() } } From 2d67bb4038bed4409d12b27e20903c386d77147a Mon Sep 17 00:00:00 2001 From: "pikagreen@nate.com" Date: Sun, 11 Aug 2024 20:59:52 +0900 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=8E=A8=20::=20=EC=BD=94=EB=93=9C=20Fo?= =?UTF-8?q?rmatting=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ViewControllers/ArtistMusicContentViewController.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift index 9634a69e8..1a3cd5d1c 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistMusicContentViewController.swift @@ -18,7 +18,7 @@ public final class ArtistMusicContentViewController: @IBOutlet weak var activityIndidator: NVActivityIndicatorView! @IBOutlet weak var songCartOnView: UIView! @IBOutlet weak var songCartOnViewHeightConstraint: NSLayoutConstraint! - + public var songCartView: SongCartView! public var bottomSheetView: BottomSheetView! private var containSongsFactory: ContainSongsFactory! @@ -205,7 +205,7 @@ private extension ArtistMusicContentViewController { tableView.backgroundColor = .clear isFromArtistScene = navigationController?.viewControllers.first is ArtistViewController songCartOnView.alpha = .zero - songCartOnViewHeightConstraint.constant = isFromArtistScene ? + songCartOnViewHeightConstraint.constant = isFromArtistScene ? PLAYER_HEIGHT() : PLAYER_HEIGHT() + SAFEAREA_BOTTOM_HEIGHT() } } From cde9125aa030be8dd64541941b8ea57fe4b3a7ab Mon Sep 17 00:00:00 2001 From: KTH Date: Sun, 11 Aug 2024 21:18:17 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=E2=9C=A8=20::=20[#1067]=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=BA=90=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ArtistDetailViewController.swift | 18 ++++++++++++++++++ .../ViewModels/ArtistDetailViewModel.swift | 7 +++++++ 2 files changed, 25 insertions(+) diff --git a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift index 187b156a6..9eb4b5354 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewControllers/ArtistDetailViewController.swift @@ -130,6 +130,24 @@ private extension ArtistDetailViewController { } .disposed(by: disposeBag) + output.occurredError + .bind(with: self) { owner, message in + owner.showBottomSheet( + content: owner.textPopupFactory.makeView( + text: message, + cancelButtonIsHidden: true, + confirmButtonText: "확인", + cancelButtonText: nil, + completion: { + owner.navigationController?.popViewController(animated: true) + }, + cancelCompletion: nil + ), + dismissOnOverlayTapAndPull: false + ) + } + .disposed(by: disposeBag) + output.showWarningNotification .bind(with: self) { owner, _ in let viewController = owner.textPopupFactory.makeView( diff --git a/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift b/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift index 75c5570f4..a9730f9b6 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift @@ -5,6 +5,7 @@ import LogManager import RxRelay import RxSwift import Utility +import Localization public final class ArtistDetailViewModel: ViewModelType { let artistID: String @@ -37,6 +38,7 @@ public final class ArtistDetailViewModel: ViewModelType { let showToast: PublishSubject = PublishSubject() let showLogin: PublishSubject = PublishSubject() let showWarningNotification: PublishSubject = PublishSubject() + let occurredError: PublishSubject = PublishSubject() } public func transform(from input: Input) -> Output { @@ -46,6 +48,11 @@ public final class ArtistDetailViewModel: ViewModelType { input.fetchArtistDetail .flatMap { [fetchArtistDetailUseCase] _ in return fetchArtistDetailUseCase.execute(id: id) + .asObservable() + .catch { error in + output.occurredError.onNext(error.asWMError.errorDescription ?? LocalizationStrings.unknownErrorWarning) + return Observable.empty() + } } .bind(to: output.dataSource) .disposed(by: disposeBag) From d44ce975a73ce1a0f30f7d7f131fe9fff9125507 Mon Sep 17 00:00:00 2001 From: "pikagreen@nate.com" Date: Sun, 11 Aug 2024 21:18:20 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=8E=A8=20::=20=EC=BD=94=EB=93=9C=20Fo?= =?UTF-8?q?rmatting=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/ViewModels/ArtistDetailViewModel.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift b/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift index a9730f9b6..71b8c47c2 100644 --- a/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift +++ b/Projects/Features/ArtistFeature/Sources/ViewModels/ArtistDetailViewModel.swift @@ -1,11 +1,11 @@ import ArtistDomainInterface import BaseFeature import Foundation +import Localization import LogManager import RxRelay import RxSwift import Utility -import Localization public final class ArtistDetailViewModel: ViewModelType { let artistID: String @@ -50,7 +50,8 @@ public final class ArtistDetailViewModel: ViewModelType { return fetchArtistDetailUseCase.execute(id: id) .asObservable() .catch { error in - output.occurredError.onNext(error.asWMError.errorDescription ?? LocalizationStrings.unknownErrorWarning) + output.occurredError + .onNext(error.asWMError.errorDescription ?? LocalizationStrings.unknownErrorWarning) return Observable.empty() } }