From 53c5112803b3902a7bbf1b789ec5b363af0be7aa Mon Sep 17 00:00:00 2001 From: Hamp Date: Sat, 11 May 2024 16:28:18 +0900 Subject: [PATCH 1/7] =?UTF-8?q?:zap:=20::=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EB=A6=AC=EC=95=A1=ED=84=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Reactors/FavoriteReactoer.swift | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift diff --git a/Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift b/Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift new file mode 100644 index 000000000..45aebc5a2 --- /dev/null +++ b/Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift @@ -0,0 +1,164 @@ +import Foundation +import LogManager +import ReactorKit +import RxCocoa +import RxSwift +import UserDomainInterface + +final class FavoriteReactoer: Reactor { + enum Action { + case viewDidLoad + case refresh + case itemMoved(ItemMovedEvent) + case tapDidEditButton + case tapDidSaveButton + case tapDidPlaylist(Int) + case tapAll(isSelecting: Bool) + } + + enum Mutation { + case updateDataSource([MyPlayListSectionModel]) + case switchEditingState(Bool) + case updateOrder([PlayListEntity]) + case changeSelectedState(data: [PlayListEntity], selectedCount: Int) + case changeAllState(data: [PlayListEntity], selectedCount: Int) + } + + struct State { + var isEditing: Bool + var dataSource: [MyPlayListSectionModel] + var backupDataSource: [MyPlayListSectionModel] + var selectedItemCount: Int + } + + var initialState: State + private let storageCommonService: any StorageCommonService + + init(storageCommonService: any StorageCommonService = DefaultStorageCommonService.shared) { + self.initialState = State( + isEditing: false, + dataSource: [], + backupDataSource: [], + selectedItemCount: 0 + ) + + self.storageCommonService = storageCommonService + } + + deinit { + LogManager.printDebug("❌ Deinit \(Self.self)") + } + + func mutate(action: Action) -> Observable { + switch action { + case .viewDidLoad: + updateDataSource() + case .refresh: + updateDataSource() + case .tapDidEditButton: + switchEditing(true) + case .tapDidSaveButton: + // TODO: USECASE 연결 + switchEditing(false) + case let .itemMoved((sourceIndex, destinationIndex)): + updateOrder(src: sourceIndex.row, dest: destinationIndex.row) + case let .tapDidPlaylist(index): + changeSelectingState(index) + case let .tapAll(isSelecting): + tapAll(isSelecting) + } + } + + func reduce(state: State, mutation: Mutation) -> State { + var newState = state + + switch mutation { + case let .updateDataSource(dataSource): + newState.dataSource = dataSource + newState.backupDataSource = dataSource + case let .switchEditingState(flag): + newState.isEditing = flag + case let .updateOrder(dataSource): + newState.dataSource = [MyPlayListSectionModel(model: 0, items: dataSource)] + case let .changeSelectedState(data: data, selectedCount: selectedCount): + newState.dataSource = [MyPlayListSectionModel(model: 0, items: data)] + newState.selectedItemCount = selectedCount + case let .changeAllState(data: data, selectedCount: selectedCount): + newState.dataSource = [MyPlayListSectionModel(model: 0, items: data)] + newState.selectedItemCount = selectedCount + } + + return newState + } + + func transform(mutation: Observable) -> Observable { + let editState = storageCommonService.isEditingState + .map { Mutation.switchEditingState($0) } + + return Observable.merge(mutation, editState) + } +} + +extension FavoriteReactoer { + func updateDataSource() -> Observable { + return .just( + .updateDataSource( + [MyPlayListSectionModel( + model: 0, + items: [ + .init(key: "123", title: "플리1", image: "", songlist: [], image_version: 0), + .init(key: "1234", title: "플리2", image: "", songlist: [], image_version: 0), + .init(key: "1234", title: "플리3", image: "", songlist: [], image_version: 0), + .init(key: "1234", title: "플리4", image: "", songlist: [], image_version: 0) + ] + )] + ) + ) + } + + func switchEditing(_ flag: Bool) -> Observable { + return .just(.switchEditingState(flag)) + } + + /// 순서 변경 + func updateOrder(src: Int, dest: Int) -> Observable { + guard var tmp = currentState.dataSource.first?.items else { + LogManager.printError("playlist datasource is empty") + return .empty() + } + + let target = tmp[src] + tmp.remove(at: src) + tmp.insert(target, at: dest) + return .just(.updateOrder(tmp)) + } + + func changeSelectingState(_ index: Int) -> Observable { + guard var tmp = currentState.dataSource.first?.items else { + LogManager.printError("playlist datasource is empty") + return .empty() + } + + var count = currentState.selectedItemCount + let target = tmp[index] + count = target.isSelected ? count - 1 : count + 1 + tmp[index].isSelected = !tmp[index].isSelected + return .just(.changeSelectedState(data: tmp, selectedCount: count)) + } + + /// 전체 곡 선택 / 해제 + func tapAll(_ flag: Bool) -> Observable { + guard var tmp = currentState.dataSource.first?.items else { + LogManager.printError("playlist datasource is empty") + return .empty() + } + + let count = flag ? tmp.count : 0 + + for i in 0 ..< tmp.count { + tmp[i].isSelected = flag + } + return .just(.changeAllState(data: tmp, selectedCount: count)) + } +} + From 96801c63b22d4494fe4332f3fa1e6dbcd7c688c7 Mon Sep 17 00:00:00 2001 From: Hamp Date: Sat, 11 May 2024 17:08:16 +0900 Subject: [PATCH 2/7] =?UTF-8?q?:recycle:=20::=20=EC=A2=8B=EC=95=84?= =?UTF-8?q?=EC=9A=94=20=EB=A6=AC=EC=95=A1=ED=84=B0=ED=82=B7=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EB=A6=AC=ED=8C=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Application/NeedleGenerated.swift | 4 + .../Sources/Reactors/AfterSearchReactor.swift | 10 +- .../SearchViewController.swift | 2 +- .../Components/FavoriteComponent.swift | 16 +- .../Sources/Reactors/FavoriteReactoer.swift | 54 +-- .../Sources/Reactors/MyPlaylistReactor.swift | 12 +- .../FavoriteViewController.swift | 313 ++++++++---------- .../MyPlayListViewController.swift | 2 +- 8 files changed, 200 insertions(+), 213 deletions(-) diff --git a/Projects/App/Sources/Application/NeedleGenerated.swift b/Projects/App/Sources/Application/NeedleGenerated.swift index 5384a273e..f91932abc 100644 --- a/Projects/App/Sources/Application/NeedleGenerated.swift +++ b/Projects/App/Sources/Application/NeedleGenerated.swift @@ -432,6 +432,9 @@ private class FavoriteDependency8f7fd37aeb6f0e5d0e30Provider: FavoriteDependency var textPopUpFactory: any TextPopUpFactory { return appComponent.textPopUpFactory } + var signInFactory: any SignInFactory { + return appComponent.signInFactory + } private let appComponent: AppComponent init(appComponent: AppComponent) { self.appComponent = appComponent @@ -1002,6 +1005,7 @@ extension FavoriteComponent: Registration { keyPathToName[\FavoriteDependency.deleteFavoriteListUseCase] = "deleteFavoriteListUseCase-any DeleteFavoriteListUseCase" keyPathToName[\FavoriteDependency.logoutUseCase] = "logoutUseCase-any LogoutUseCase" keyPathToName[\FavoriteDependency.textPopUpFactory] = "textPopUpFactory-any TextPopUpFactory" + keyPathToName[\FavoriteDependency.signInFactory] = "signInFactory-any SignInFactory" } } extension RequestComponent: Registration { diff --git a/Projects/Features/SearchFeature/Sources/Reactors/AfterSearchReactor.swift b/Projects/Features/SearchFeature/Sources/Reactors/AfterSearchReactor.swift index 34d7a1b04..58bdbf6bd 100644 --- a/Projects/Features/SearchFeature/Sources/Reactors/AfterSearchReactor.swift +++ b/Projects/Features/SearchFeature/Sources/Reactors/AfterSearchReactor.swift @@ -9,11 +9,11 @@ public final class AfterSearchReactor: Reactor { private let fetchSearchSongUseCase: FetchSearchSongUseCase public enum Action { - case fetchData(String) + case updateData(String) } public enum Mutation { - case fetchData([[SearchSectionModel]]) + case updateData([[SearchSectionModel]]) } public struct State { @@ -39,7 +39,7 @@ public final class AfterSearchReactor: Reactor { var newState = state switch mutation { - case let .fetchData(data): + case let .updateData(data): newState.dataSource = data } @@ -48,7 +48,7 @@ public final class AfterSearchReactor: Reactor { public func mutate(action: Action) -> Observable { switch action { - case let .fetchData(text): + case let .updateData(text): return fetchData(text) } } @@ -90,6 +90,6 @@ private extension AfterSearchReactor { return results } - .map { Mutation.fetchData($0) } + .map { Mutation.updateData($0) } } } diff --git a/Projects/Features/SearchFeature/Sources/ViewControllers/SearchViewController.swift b/Projects/Features/SearchFeature/Sources/ViewControllers/SearchViewController.swift index 1e554b024..65bb7587d 100644 --- a/Projects/Features/SearchFeature/Sources/ViewControllers/SearchViewController.swift +++ b/Projects/Features/SearchFeature/Sources/ViewControllers/SearchViewController.swift @@ -235,7 +235,7 @@ extension SearchViewController { return } - childReactor.action.onNext(.fetchData(text)) + childReactor.action.onNext(.updateData(text)) } else if let nowChildVc = children.first as? AfterSearchViewController { guard state == .before || state == .typing else { return diff --git a/Projects/Features/StorageFeature/Sources/Components/FavoriteComponent.swift b/Projects/Features/StorageFeature/Sources/Components/FavoriteComponent.swift index 4d7c4a856..84dd4ccef 100644 --- a/Projects/Features/StorageFeature/Sources/Components/FavoriteComponent.swift +++ b/Projects/Features/StorageFeature/Sources/Components/FavoriteComponent.swift @@ -4,8 +4,9 @@ import BaseFeature import BaseFeatureInterface import Foundation import NeedleFoundation -import SignInFeature +import SignInFeatureInterface import UserDomainInterface +import UIKit public protocol FavoriteDependency: Dependency { var containSongsFactory: any ContainSongsFactory { get } @@ -14,19 +15,16 @@ public protocol FavoriteDependency: Dependency { var deleteFavoriteListUseCase: any DeleteFavoriteListUseCase { get } var logoutUseCase: any LogoutUseCase { get } var textPopUpFactory: any TextPopUpFactory { get } + var signInFactory: any SignInFactory { get } } public final class FavoriteComponent: Component { - public func makeView() -> FavoriteViewController { + public func makeView() -> UIViewController { return FavoriteViewController.viewController( - viewModel: .init( - fetchFavoriteSongsUseCase: dependency.fetchFavoriteSongsUseCase, - editFavoriteSongsOrderUseCase: dependency.editFavoriteSongsOrderUseCase, - deleteFavoriteListUseCase: dependency.deleteFavoriteListUseCase, - logoutUseCase: dependency.logoutUseCase - ), + reactor: FavoriteReactoer() , containSongsFactory: dependency.containSongsFactory, - textPopUpFactory: dependency.textPopUpFactory + textPopUpFactory: dependency.textPopUpFactory, + signInFactory: dependency.signInFactory ) } } diff --git a/Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift b/Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift index 45aebc5a2..863044215 100644 --- a/Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift +++ b/Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift @@ -1,33 +1,39 @@ +import AuthDomainInterface +import BaseDomainInterface +import BaseFeature import Foundation -import LogManager -import ReactorKit import RxCocoa +import RxRelay import RxSwift +import SongsDomainInterface import UserDomainInterface +import Utility +import ReactorKit +import LogManager final class FavoriteReactoer: Reactor { enum Action { case viewDidLoad case refresh case itemMoved(ItemMovedEvent) - case tapDidEditButton - case tapDidSaveButton - case tapDidPlaylist(Int) + case editButtonDidTap + case saveButtonDidTap + case songDidTap(Int) case tapAll(isSelecting: Bool) } enum Mutation { - case updateDataSource([MyPlayListSectionModel]) + case updateDataSource([FavoriteSectionModel]) case switchEditingState(Bool) - case updateOrder([PlayListEntity]) - case changeSelectedState(data: [PlayListEntity], selectedCount: Int) - case changeAllState(data: [PlayListEntity], selectedCount: Int) + case updateOrder([FavoriteSongEntity]) + case changeSelectedState(data: [FavoriteSongEntity], selectedCount: Int) + case changeAllState(data: [FavoriteSongEntity], selectedCount: Int) } struct State { var isEditing: Bool - var dataSource: [MyPlayListSectionModel] - var backupDataSource: [MyPlayListSectionModel] + var dataSource: [FavoriteSectionModel] + var backupDataSource: [FavoriteSectionModel] var selectedItemCount: Int } @@ -55,14 +61,14 @@ final class FavoriteReactoer: Reactor { updateDataSource() case .refresh: updateDataSource() - case .tapDidEditButton: + case .editButtonDidTap: switchEditing(true) - case .tapDidSaveButton: + case .saveButtonDidTap: // TODO: USECASE 연결 switchEditing(false) case let .itemMoved((sourceIndex, destinationIndex)): updateOrder(src: sourceIndex.row, dest: destinationIndex.row) - case let .tapDidPlaylist(index): + case let .songDidTap(index): changeSelectingState(index) case let .tapAll(isSelecting): tapAll(isSelecting) @@ -79,12 +85,12 @@ final class FavoriteReactoer: Reactor { case let .switchEditingState(flag): newState.isEditing = flag case let .updateOrder(dataSource): - newState.dataSource = [MyPlayListSectionModel(model: 0, items: dataSource)] + newState.dataSource = [FavoriteSectionModel(model: 0, items: dataSource)] case let .changeSelectedState(data: data, selectedCount: selectedCount): - newState.dataSource = [MyPlayListSectionModel(model: 0, items: data)] + newState.dataSource = [FavoriteSectionModel(model: 0, items: data)] newState.selectedItemCount = selectedCount case let .changeAllState(data: data, selectedCount: selectedCount): - newState.dataSource = [MyPlayListSectionModel(model: 0, items: data)] + newState.dataSource = [FavoriteSectionModel(model: 0, items: data)] newState.selectedItemCount = selectedCount } @@ -103,13 +109,13 @@ extension FavoriteReactoer { func updateDataSource() -> Observable { return .just( .updateDataSource( - [MyPlayListSectionModel( + [FavoriteSectionModel( model: 0, items: [ - .init(key: "123", title: "플리1", image: "", songlist: [], image_version: 0), - .init(key: "1234", title: "플리2", image: "", songlist: [], image_version: 0), - .init(key: "1234", title: "플리3", image: "", songlist: [], image_version: 0), - .init(key: "1234", title: "플리4", image: "", songlist: [], image_version: 0) + .init(like: 1, song: SongEntity(id: "1", title: "1234", artist: "!2344", remix: "", reaction: "", views: 0, last: 0, date: ""), isSelected: false), + .init(like: 1, song: SongEntity(id: "2", title: "123", artist: "!23", remix: "", reaction: "", views: 0, last: 0, date: ""), isSelected: false), + .init(like: 1, song: SongEntity(id: "3", title: "112323", artist: "!55523", remix: "", reaction: "", views: 0, last: 0, date: ""), isSelected: false) + ] )] ) @@ -123,7 +129,7 @@ extension FavoriteReactoer { /// 순서 변경 func updateOrder(src: Int, dest: Int) -> Observable { guard var tmp = currentState.dataSource.first?.items else { - LogManager.printError("playlist datasource is empty") + LogManager.printError("favorite datasource is empty") return .empty() } @@ -135,7 +141,7 @@ extension FavoriteReactoer { func changeSelectingState(_ index: Int) -> Observable { guard var tmp = currentState.dataSource.first?.items else { - LogManager.printError("playlist datasource is empty") + LogManager.printError("favorite datasource is empty") return .empty() } diff --git a/Projects/Features/StorageFeature/Sources/Reactors/MyPlaylistReactor.swift b/Projects/Features/StorageFeature/Sources/Reactors/MyPlaylistReactor.swift index 526ff4c6a..477de4957 100644 --- a/Projects/Features/StorageFeature/Sources/Reactors/MyPlaylistReactor.swift +++ b/Projects/Features/StorageFeature/Sources/Reactors/MyPlaylistReactor.swift @@ -10,9 +10,9 @@ final class MyPlaylistReactor: Reactor { case viewDidLoad case refresh case itemMoved(ItemMovedEvent) - case tapDidEditButton - case tapDidSaveButton - case tapDidPlaylist(Int) + case editButtonDidTap + case saveButtonDidTap + case playlistDidTap(Int) case tapAll(isSelecting: Bool) } @@ -55,14 +55,14 @@ final class MyPlaylistReactor: Reactor { updateDataSource() case .refresh: updateDataSource() - case .tapDidEditButton: + case .editButtonDidTap: switchEditing(true) - case .tapDidSaveButton: + case .saveButtonDidTap: // TODO: USECASE 연결 switchEditing(false) case let .itemMoved((sourceIndex, destinationIndex)): updateOrder(src: sourceIndex.row, dest: destinationIndex.row) - case let .tapDidPlaylist(index): + case let .playlistDidTap(index): changeSelectingState(index) case let .tapAll(isSelecting): tapAll(isSelecting) diff --git a/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift b/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift index 63f247700..cf8f77238 100644 --- a/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift +++ b/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift @@ -10,196 +10,185 @@ import SongsDomainInterface import UIKit import UserDomainInterface import Utility +import SignInFeatureInterface +import LogManager -public typealias FavoriteSectionModel = SectionModel +typealias FavoriteSectionModel = SectionModel -public final class FavoriteViewController: BaseViewController, ViewControllerFromStoryBoard, SongCartViewType { +final class FavoriteViewController: BaseStoryboardReactorViewController, SongCartViewType { @IBOutlet weak var tableView: UITableView! @IBOutlet weak var activityIndicator: NVActivityIndicatorView! private var refreshControl = UIRefreshControl() - var viewModel: FavoriteViewModel! var containSongsFactory: ContainSongsFactory! var textPopUpFactory: TextPopUpFactory! - lazy var input = FavoriteViewModel.Input() - lazy var output = viewModel.transform(from: input) - var disposeBag = DisposeBag() + var signInFactory: SignInFactory! - public var songCartView: SongCartView! - public var bottomSheetView: BottomSheetView! + + var songCartView: SongCartView! + var bottomSheetView: BottomSheetView! let playState = PlayState.shared - override public func viewDidLoad() { + override func viewDidLoad() { super.viewDidLoad() - configureUI() - inputBindRx() - outputBindRx() } - public static func viewController( - viewModel: FavoriteViewModel, + static func viewController( + reactor: FavoriteReactoer, containSongsFactory: ContainSongsFactory, - textPopUpFactory: TextPopUpFactory + textPopUpFactory: TextPopUpFactory, + signInFactory:SignInFactory ) -> FavoriteViewController { let viewController = FavoriteViewController.viewController(storyBoardName: "Storage", bundle: Bundle.module) - viewController.viewModel = viewModel + viewController.reactor = reactor viewController.containSongsFactory = containSongsFactory viewController.textPopUpFactory = textPopUpFactory + viewController.signInFactory = signInFactory return viewController } -} - -extension FavoriteViewController { - private func inputBindRx() { - refreshControl.rx - .controlEvent(.valueChanged) - .bind(to: input.likeListLoad) + + override func configureUI() { + super.configureUI() + + self.tableView.refreshControl = self.refreshControl + self.view.backgroundColor = DesignSystemAsset.GrayColor.gray100.color + self.tableView.backgroundColor = .clear + self.tableView.verticalScrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 56, right: 0) + self.activityIndicator.type = .circleStrokeSpin + self.activityIndicator.color = DesignSystemAsset.PrimaryColor.point.color + self.activityIndicator.startAnimating() + + reactor?.action.onNext(.viewDidLoad) + } + + override func bind(reactor: FavoriteReactoer) { + super.bind(reactor: reactor) + tableView.rx.setDelegate(self) .disposed(by: disposeBag) + } + + override func bindAction(reactor: FavoriteReactoer) { + super.bindAction(reactor: reactor) + + let currentState = reactor.state + + tableView.rx.itemSelected + .withUnretained(self) + .withLatestFrom(currentState.map(\.isEditing)) { ($0.0, $0.1, $1) } + .withLatestFrom(currentState.map(\.dataSource)) { ($0.0, $0.1, $0.2, $1) } + .bind { owner, indexPath, isEditing, dataSource in - tableView.rx.itemMoved - .bind(to: input.itemMoved) + guard isEditing else { + + //TODO: 아마 곡 상세로 이동? + + return + } + } .disposed(by: disposeBag) - tableView.rx.itemSelected - .withLatestFrom(output.dataSource) { ($0, $1) } - .withLatestFrom(output.state) { ($0.0, $0.1, $1) } - .filter { $0.2.isEditing == false } - .subscribe(onNext: { indexPath, dataSource, _ in - let song: SongEntity = dataSource[indexPath.section].items[indexPath.row].song - PlayState.shared.loadAndAppendSongsToPlaylist([song]) - }) + tableView.rx.itemMoved + .map { Reactor.Action.itemMoved($0) } + .bind(to: reactor.action) .disposed(by: disposeBag) + } + + override func bindState(reactor: FavoriteReactoer) { + super.bindState(reactor: reactor) + + let sharedState = reactor.state.share(replay: 3) - private func outputBindRx() { - tableView.rx.setDelegate(self).disposed(by: disposeBag) + sharedState.map(\.dataSource) + .skip(1) + .withUnretained(self) + .withLatestFrom(Utility.PreferenceManager.$userInfo) { ($0.0, $0.1, $1) } + .do(onNext: { owner, dataSource, userInfo in - output.dataSource - .do(onNext: { [weak self] model in - guard let self = self else { + owner.activityIndicator.stopAnimating() + owner.refreshControl.endRefreshing() + + guard let userInfo = userInfo else { + // 로그인 안되어있음 + + let view = LoginWarningView( + frame: CGRect(x: .zero, y: .zero, width: APP_WIDTH(), height: APP_HEIGHT() / 5), + text: "로그인 하고\n리스트를 확인해보세요." + ) { + // TODO: 로그인 팝업 요청 (아마 StorageVC로 가야할 듯? + LogManager.printDebug("TAP 로그인 버튼") + } + + owner.tableView.tableFooterView = view return } - self.refreshControl.endRefreshing() - self.activityIndicator.stopAnimating() let warningView = WarningView(frame: CGRect(x: 0, y: 0, width: APP_WIDTH(), height: APP_HEIGHT() / 3)) warningView.text = "좋아요 한 곡이 없습니다." - let items = model.first?.items ?? [] - self.tableView.tableHeaderView = items.isEmpty ? warningView : nil + let items = dataSource.first?.items ?? [] + owner.tableView.tableFooterView = items.isEmpty ? warningView : UIView(frame: CGRect( + x: 0, + y: 0, + width: APP_WIDTH(), + height: 56 + )) }) + .map { $0.1 } .bind(to: tableView.rx.items(dataSource: createDatasources())) .disposed(by: disposeBag) - output.state - .skip(1) - .subscribe(onNext: { [weak self] state in - guard let self = self else { - return - } - if state.isEditing == false && state.force == false { // 정상적인 편집 완료 이벤트 - self.input.runEditing.onNext(()) - } - // TODO: Storage 리팩 후 -// guard let parent = self.parent?.parent as? AfterLoginViewController else { -// return -// } - // 탭맨 쪽 편집 변경 -// let isEdit: Bool = state.isEditing -// parent.output.state.accept(EditState(isEditing: isEdit, force: true)) -// self.tableView.refreshControl = isEdit ? nil : self.refreshControl -// self.tableView.setEditing(isEdit, animated: true) -// self.tableView.reloadData() - }) + sharedState.map(\.isEditing) + .withUnretained(self) + .bind { owner, flag in + owner.tableView.isEditing = flag + owner.tableView.reloadData() + } .disposed(by: disposeBag) - output.indexPathOfSelectedLikeLists - .skip(1) - .debug("indexPathOfSelectedLikeLists") - .withLatestFrom(output.dataSource) { ($0, $1) } - .subscribe(onNext: { [weak self] songs, dataSource in - guard let self = self else { return } - let items = dataSource.first?.items ?? [] + sharedState.map(\.selectedItemCount) + .withUnretained(self) + .bind(onNext: { owner, count in - switch songs.isEmpty { - case true: - self.hideSongCart() - case false: - self.showSongCart( + if count == 0 { + owner.hideSongCart() + } else { + owner.showSongCart( in: UIApplication.shared.windows.first?.rootViewController?.view ?? UIView(), type: .likeSong, - selectedSongCount: songs.count, - totalSongCount: items.count, + selectedSongCount: count, + totalSongCount: owner.reactor?.currentState.dataSource.first?.items.count ?? 0, useBottomSpace: true ) - self.songCartView?.delegate = self - } - }).disposed(by: disposeBag) - - output.willAddSongList - .skip(1) - .debug("willAddSongList") - .subscribe(onNext: { [weak self] songs in - guard let `self` = self else { return } - let viewController = self.containSongsFactory.makeView(songs: songs) - viewController.modalPresentationStyle = .overFullScreen - self.present(viewController, animated: true) { - self.input.allLikeListSelected.onNext(false) + owner.songCartView?.delegate = owner } - }).disposed(by: disposeBag) - output.willAddPlayList - .skip(1) - .debug("willAddPlayList") - .subscribe(onNext: { [weak self] songs in - guard let self = self else { return } - self.playState.appendSongsToPlaylist(songs) - self.input.allLikeListSelected.onNext(false) - self.output.state.accept(EditState(isEditing: false, force: true)) - self.showToast( - text: "\(songs.count)곡이 재생목록에 추가되었습니다. 중복 곡은 제외됩니다.", - font: DesignSystemFontFamily.Pretendard.light.font(size: 14) - ) - }).disposed(by: disposeBag) - - output.showToast - .subscribe(onNext: { [weak self] (result: BaseEntity) in - guard let self = self else { return } - - self.showToast( - text: result.description, - font: DesignSystemFontFamily.Pretendard.light.font(size: 14) - ) }) .disposed(by: disposeBag) - - output.onLogout.bind(with: self) { owner, error in - NotificationCenter.default.post(name: .movedTab, object: 4) - - owner.showToast( - text: error.localizedDescription, - font: DesignSystemFontFamily.Pretendard.light.font(size: 14) - ) - } - .disposed(by: disposeBag) + } + +} + +extension FavoriteViewController { private func createDatasources() -> RxTableViewSectionedReloadDataSource { let datasource = RxTableViewSectionedReloadDataSource( configureCell: { [weak self] _, tableView, indexPath, model -> UITableViewCell in - guard let self = self, - let cell = tableView.dequeueReusableCell( - withIdentifier: "FavoriteTableViewCell", - for: IndexPath(row: indexPath.row, section: 0) - ) as? FavoriteTableViewCell + guard let self = self, let reactor = self.reactor else { return UITableViewCell() + } + guard let cell = tableView.dequeueReusableCell( + withIdentifier: "FavoriteTableViewCell", + for: IndexPath(row: indexPath.row, section: 0) + ) as? FavoriteTableViewCell else { return UITableViewCell() } cell.update( model: model, - isEditing: self.output.state.value.isEditing, + isEditing: reactor.currentState.isEditing, indexPath: indexPath ) cell.delegate = self @@ -216,52 +205,43 @@ extension FavoriteViewController { return datasource } - private func configureUI() { - self.tableView.refreshControl = self.refreshControl - self.view.backgroundColor = DesignSystemAsset.GrayColor.gray100.color - self.tableView.backgroundColor = .clear - self.tableView.tableFooterView = UIView(frame: CGRect(x: 0, y: 0, width: APP_WIDTH(), height: 56)) - self.tableView.verticalScrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 56, right: 0) - self.activityIndicator.type = .circleStrokeSpin - self.activityIndicator.color = DesignSystemAsset.PrimaryColor.point.color - self.activityIndicator.startAnimating() - } } extension FavoriteViewController: SongCartViewDelegate { public func buttonTapped(type: SongCartSelectType) { switch type { case let .allSelect(flag): - input.allLikeListSelected.onNext(flag) + reactor?.action.onNext(.tapAll(isSelecting: flag)) case .addSong: - input.addSongs.onNext(()) - self.output.state.accept(EditState(isEditing: false, force: true)) +// input.addSongs.onNext(()) self.hideSongCart() case .addPlayList: - input.addPlayList.onNext(()) + // input.addPlayList.onNext(()) self.hideSongCart() case .remove: - let count: Int = output.indexPathOfSelectedLikeLists.value.count - - guard let textPopupViewController = self.textPopUpFactory.makeView( - text: "선택한 좋아요 리스트 \(count)곡이 삭제됩니다.?", - cancelButtonIsHidden: false, - allowsDragAndTapToDismiss: nil, - confirmButtonText: nil, - cancelButtonText: nil, - completion: { [weak self] in - - guard let self else { return } - self.input.deleteLikeList.onNext(()) - self.hideSongCart() - - }, - cancelCompletion: nil - ) as? TextPopupViewController else { - return - } - - self.showPanModal(content: textPopupViewController) + break + // TODO: useCase 연결 후 +// let count: Int = output.indexPathOfSelectedLikeLists.value.count +// +// guard let textPopupViewController = self.textPopUpFactory.makeView( +// text: "선택한 좋아요 리스트 \(count)곡이 삭제됩니다.?", +// cancelButtonIsHidden: false, +// allowsDragAndTapToDismiss: nil, +// confirmButtonText: nil, +// cancelButtonText: nil, +// completion: { [weak self] in +// +// guard let self else { return } +// self.input.deleteLikeList.onNext(()) +// self.hideSongCart() +// +// }, +// cancelCompletion: nil +// ) as? TextPopupViewController else { +// return +// } + +//self.showPanModal(content: textPopupViewController) default: return } } @@ -271,9 +251,10 @@ extension FavoriteViewController: FavoriteTableViewCellDelegate { public func buttonTapped(type: FavoriteTableViewCellDelegateConstant) { switch type { case let .listTapped(indexPath): - input.itemSelected.onNext(indexPath) + self.reactor?.action.onNext(.songDidTap(indexPath.row)) case let .playTapped(song): - playState.loadAndAppendSongsToPlaylist([song]) + // TODO: useCase 연결 후 + break } } } @@ -295,8 +276,6 @@ extension FavoriteViewController: UITableViewDelegate { extension FavoriteViewController { func scrollToTop() { - let itemIsEmpty: Bool = output.dataSource.value.first?.items.isEmpty ?? true - guard !itemIsEmpty else { return } tableView.setContentOffset(.zero, animated: true) } } diff --git a/Projects/Features/StorageFeature/Sources/ViewControllers/MyPlayListViewController.swift b/Projects/Features/StorageFeature/Sources/ViewControllers/MyPlayListViewController.swift index 917cf05bd..6d604713b 100644 --- a/Projects/Features/StorageFeature/Sources/ViewControllers/MyPlayListViewController.swift +++ b/Projects/Features/StorageFeature/Sources/ViewControllers/MyPlayListViewController.swift @@ -290,7 +290,7 @@ extension MyPlayListViewController: MyPlayListTableViewCellDelegate { public func buttonTapped(type: MyPlayListTableViewCellDelegateConstant) { switch type { case let .listTapped(indexPath): - self.reactor?.action.onNext(.tapDidPlaylist(indexPath.row)) + self.reactor?.action.onNext(.playlistDidTap(indexPath.row)) case let .playTapped(indexPath): // TODO: useCase 연결 후 break From 9130f4a77c58cb971345e91c49fd17a86e6a831c Mon Sep 17 00:00:00 2001 From: Hamp Date: Sat, 11 May 2024 17:08:30 +0900 Subject: [PATCH 3/7] =?UTF-8?q?:bricks:=20::=20=ED=8F=AC=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/FavoriteComponent.swift | 4 +- .../Sources/Reactors/FavoriteReactoer.swift | 51 ++++++++++++++++--- .../FavoriteViewController.swift | 42 +++++++-------- 3 files changed, 63 insertions(+), 34 deletions(-) diff --git a/Projects/Features/StorageFeature/Sources/Components/FavoriteComponent.swift b/Projects/Features/StorageFeature/Sources/Components/FavoriteComponent.swift index 84dd4ccef..81a7a051c 100644 --- a/Projects/Features/StorageFeature/Sources/Components/FavoriteComponent.swift +++ b/Projects/Features/StorageFeature/Sources/Components/FavoriteComponent.swift @@ -5,8 +5,8 @@ import BaseFeatureInterface import Foundation import NeedleFoundation import SignInFeatureInterface -import UserDomainInterface import UIKit +import UserDomainInterface public protocol FavoriteDependency: Dependency { var containSongsFactory: any ContainSongsFactory { get } @@ -21,7 +21,7 @@ public protocol FavoriteDependency: Dependency { public final class FavoriteComponent: Component { public func makeView() -> UIViewController { return FavoriteViewController.viewController( - reactor: FavoriteReactoer() , + reactor: FavoriteReactoer(), containSongsFactory: dependency.containSongsFactory, textPopUpFactory: dependency.textPopUpFactory, signInFactory: dependency.signInFactory diff --git a/Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift b/Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift index 863044215..7b615d21a 100644 --- a/Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift +++ b/Projects/Features/StorageFeature/Sources/Reactors/FavoriteReactoer.swift @@ -2,14 +2,14 @@ import AuthDomainInterface import BaseDomainInterface import BaseFeature import Foundation +import LogManager +import ReactorKit import RxCocoa import RxRelay import RxSwift import SongsDomainInterface import UserDomainInterface import Utility -import ReactorKit -import LogManager final class FavoriteReactoer: Reactor { enum Action { @@ -112,10 +112,48 @@ extension FavoriteReactoer { [FavoriteSectionModel( model: 0, items: [ - .init(like: 1, song: SongEntity(id: "1", title: "1234", artist: "!2344", remix: "", reaction: "", views: 0, last: 0, date: ""), isSelected: false), - .init(like: 1, song: SongEntity(id: "2", title: "123", artist: "!23", remix: "", reaction: "", views: 0, last: 0, date: ""), isSelected: false), - .init(like: 1, song: SongEntity(id: "3", title: "112323", artist: "!55523", remix: "", reaction: "", views: 0, last: 0, date: ""), isSelected: false) - + .init( + like: 1, + song: SongEntity( + id: "1", + title: "1234", + artist: "!2344", + remix: "", + reaction: "", + views: 0, + last: 0, + date: "" + ), + isSelected: false + ), + .init( + like: 1, + song: SongEntity( + id: "2", + title: "123", + artist: "!23", + remix: "", + reaction: "", + views: 0, + last: 0, + date: "" + ), + isSelected: false + ), + .init( + like: 1, + song: SongEntity( + id: "3", + title: "112323", + artist: "!55523", + remix: "", + reaction: "", + views: 0, + last: 0, + date: "" + ), + isSelected: false + ) ] )] ) @@ -167,4 +205,3 @@ extension FavoriteReactoer { return .just(.changeAllState(data: tmp, selectedCount: count)) } } - diff --git a/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift b/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift index cf8f77238..7f4d86dd7 100644 --- a/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift +++ b/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift @@ -2,16 +2,16 @@ import BaseDomainInterface import BaseFeature import BaseFeatureInterface import DesignSystem +import LogManager import NVActivityIndicatorView import RxDataSources import RxRelay import RxSwift +import SignInFeatureInterface import SongsDomainInterface import UIKit import UserDomainInterface import Utility -import SignInFeatureInterface -import LogManager typealias FavoriteSectionModel = SectionModel @@ -25,22 +25,20 @@ final class FavoriteViewController: BaseStoryboardReactorViewController FavoriteViewController { let viewController = FavoriteViewController.viewController(storyBoardName: "Storage", bundle: Bundle.module) viewController.reactor = reactor @@ -49,10 +47,10 @@ final class FavoriteViewController: BaseStoryboardReactorViewController RxTableViewSectionedReloadDataSource { let datasource = RxTableViewSectionedReloadDataSource( configureCell: { [weak self] _, tableView, indexPath, model -> UITableViewCell in guard let self = self, let reactor = self.reactor else { return UITableViewCell() } guard let cell = tableView.dequeueReusableCell( - withIdentifier: "FavoriteTableViewCell", - for: IndexPath(row: indexPath.row, section: 0) + withIdentifier: "FavoriteTableViewCell", + for: IndexPath(row: indexPath.row, section: 0) ) as? FavoriteTableViewCell else { return UITableViewCell() } @@ -204,7 +197,6 @@ extension FavoriteViewController { ) return datasource } - } extension FavoriteViewController: SongCartViewDelegate { @@ -241,7 +233,7 @@ extension FavoriteViewController: SongCartViewDelegate { // return // } -//self.showPanModal(content: textPopupViewController) + // self.showPanModal(content: textPopupViewController) default: return } } From 5caabe9491c18badad6c35447606baf57057a878 Mon Sep 17 00:00:00 2001 From: Hamp Date: Sat, 11 May 2024 20:14:12 +0900 Subject: [PATCH 4/7] =?UTF-8?q?:zap:=20::=20=EB=B0=91=EC=97=90=20=EB=88=8C?= =?UTF-8?q?=EB=9F=AC=EC=84=9C=20=EC=9D=B4=EB=8F=99=EB=90=98=EB=8A=94?= =?UTF-8?q?=EA=B1=B0=20=EB=A7=89=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Resources/Storage.storyboard | 45 ++++++++++++------- .../StorageViewController.swift | 3 ++ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/Projects/Features/StorageFeature/Resources/Storage.storyboard b/Projects/Features/StorageFeature/Resources/Storage.storyboard index 89eb20950..118c4b460 100644 --- a/Projects/Features/StorageFeature/Resources/Storage.storyboard +++ b/Projects/Features/StorageFeature/Resources/Storage.storyboard @@ -20,13 +20,13 @@ - + + + + + + + + - - + + - - + + - + @@ -61,16 +68,19 @@ + - + + - + - - + + + @@ -614,7 +624,7 @@ - + @@ -711,7 +721,7 @@ - + @@ -735,7 +745,7 @@ - + @@ -833,7 +843,7 @@ - + @@ -1828,6 +1838,9 @@ + + + diff --git a/Projects/Features/StorageFeature/Sources/ViewControllers/StorageViewController.swift b/Projects/Features/StorageFeature/Sources/ViewControllers/StorageViewController.swift index 66b856557..6f1edfcd6 100644 --- a/Projects/Features/StorageFeature/Sources/ViewControllers/StorageViewController.swift +++ b/Projects/Features/StorageFeature/Sources/ViewControllers/StorageViewController.swift @@ -143,6 +143,7 @@ extension StorageViewController { .withUnretained(self) .bind { owner, flag in + owner.isScrollEnabled = !flag // 편집 시 , 옆 탭으로 swipe를 막기 위함 owner.editButton.isHidden = flag owner.saveButton.isHidden = !flag @@ -186,6 +187,8 @@ extension StorageViewController { extension StorageViewController { private func configureUI() { + + editButton.layer.cornerRadius = 4 editButton.layer.borderWidth = 1 editButton.backgroundColor = .clear From 327806a903bd2ffd0fe9907cae1801c8415e778d Mon Sep 17 00:00:00 2001 From: Hamp Date: Sat, 11 May 2024 20:14:21 +0900 Subject: [PATCH 5/7] =?UTF-8?q?:bricks:=20::=20=ED=8F=AC=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/ViewControllers/StorageViewController.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Projects/Features/StorageFeature/Sources/ViewControllers/StorageViewController.swift b/Projects/Features/StorageFeature/Sources/ViewControllers/StorageViewController.swift index 6f1edfcd6..66b856557 100644 --- a/Projects/Features/StorageFeature/Sources/ViewControllers/StorageViewController.swift +++ b/Projects/Features/StorageFeature/Sources/ViewControllers/StorageViewController.swift @@ -143,7 +143,6 @@ extension StorageViewController { .withUnretained(self) .bind { owner, flag in - owner.isScrollEnabled = !flag // 편집 시 , 옆 탭으로 swipe를 막기 위함 owner.editButton.isHidden = flag owner.saveButton.isHidden = !flag @@ -187,8 +186,6 @@ extension StorageViewController { extension StorageViewController { private func configureUI() { - - editButton.layer.cornerRadius = 4 editButton.layer.borderWidth = 1 editButton.backgroundColor = .clear From ba25a83e10830bfb8a1d5d674c030f742eae25d6 Mon Sep 17 00:00:00 2001 From: yongbeomkwak Date: Tue, 14 May 2024 17:31:34 +0900 Subject: [PATCH 6/7] =?UTF-8?q?:zap:=20::=20=EC=A0=91=EA=B7=BC=20=EC=A0=9C?= =?UTF-8?q?=ED=95=9C=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/ViewControllers/FavoriteViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift b/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift index 7f4d86dd7..09246db98 100644 --- a/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift +++ b/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift @@ -21,9 +21,9 @@ final class FavoriteViewController: BaseStoryboardReactorViewController Date: Tue, 14 May 2024 17:31:44 +0900 Subject: [PATCH 7/7] =?UTF-8?q?:bricks:=20::=20=ED=8F=AC=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/ViewControllers/FavoriteViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift b/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift index 09246db98..a012e44fe 100644 --- a/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift +++ b/Projects/Features/StorageFeature/Sources/ViewControllers/FavoriteViewController.swift @@ -21,7 +21,7 @@ final class FavoriteViewController: BaseStoryboardReactorViewController