From 32a280d7df1084fcb6d5661c2432b6d56b57552b Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Sun, 1 Dec 2024 17:26:56 +0900 Subject: [PATCH 01/20] =?UTF-8?q?fix:=20=EC=98=A8=EB=B3=B4=EB=94=A9?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=A1=9C=EB=B9=84=EB=B7=B0=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=95=A0=20=EA=B2=BD=EC=9A=B0=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A5=BC=20=EA=B0=80?= =?UTF-8?q?=EC=A0=B8=EC=99=80=EC=84=9C=20push=EB=A5=BC=202=EB=B2=88?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EC=9D=B4=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 데이터를 가져올 떄 Debug Logger가 추가되었습니다. --- alsongDalsong/ASEntity/ASEntity/Player.swift | 6 ++++++ alsongDalsong/ASEntity/ASEntity/Record.swift | 9 +++++++++ alsongDalsong/ASEntity/ASEntity/Room.swift | 19 +++++++++++++++++++ alsongDalsong/ASEntity/ASEntity/Status.swift | 17 +++++++++++++++++ .../Firebase/ASFirebaseDatabase.swift | 14 ++++++++++++-- .../UIKitComponents/ASAlertController.swift | 2 +- .../Views/Lobby/LobbyViewController.swift | 3 ++- .../Sources/Views/Lobby/LobbyViewModel.swift | 4 ++++ .../Result/HummingResultViewController.swift | 1 + .../SubmitAnswerViewController.swift | 1 + 10 files changed, 72 insertions(+), 4 deletions(-) diff --git a/alsongDalsong/ASEntity/ASEntity/Player.swift b/alsongDalsong/ASEntity/ASEntity/Player.swift index 4c8539c6..e9cd8844 100644 --- a/alsongDalsong/ASEntity/ASEntity/Player.swift +++ b/alsongDalsong/ASEntity/ASEntity/Player.swift @@ -28,3 +28,9 @@ extension Player { public static let playerStub3: Player = Player(id: "2", avatarUrl: nil, nickname: "Moral-life", score: nil, order: 2) public static let playerStub4: Player = Player(id: "3", avatarUrl: nil, nickname: "Sang₩", score: nil, order: 3) } + +extension Player: CustomStringConvertible { + public var description: String { + return "\(nickname ?? "Unknown")" + } +} diff --git a/alsongDalsong/ASEntity/ASEntity/Record.swift b/alsongDalsong/ASEntity/ASEntity/Record.swift index c6225f81..cdba02d1 100644 --- a/alsongDalsong/ASEntity/ASEntity/Record.swift +++ b/alsongDalsong/ASEntity/ASEntity/Record.swift @@ -30,3 +30,12 @@ extension Record { public static let recordStub4_2 = Record(player: Player.playerStub4, recordOrder: 1, fileUrl: stubm4aData) public static let recordStub4_3 = Record(player: Player.playerStub4, recordOrder: 2, fileUrl: stubm4aData) } + +extension Record: CustomStringConvertible { + public var description: String { + return """ + player: \(player?.description ?? "nil") + recordOrder: \(String(describing: recordOrder)) + """ + } +} diff --git a/alsongDalsong/ASEntity/ASEntity/Room.swift b/alsongDalsong/ASEntity/ASEntity/Room.swift index c977815c..05f7a407 100644 --- a/alsongDalsong/ASEntity/ASEntity/Room.swift +++ b/alsongDalsong/ASEntity/ASEntity/Room.swift @@ -42,3 +42,22 @@ public struct Room: Codable { self.submits = submits } } + +extension Room: CustomStringConvertible { + public var description: String { + return """ + number: \(number ?? "nil") + host: \(host?.description ?? "nil") + players: \(players?.description ?? "nil") + mode: \(mode?.title ?? "nil") + round: \(round ?? 0) + status: \(status?.description ?? "nil") + recordOrder: \(recordOrder ?? 0) + records: \(records?.description ?? "nil") + answers: \(answers?.description ?? "nil") + dueTime: \(dueTime?.description ?? "nil") + selectedRecords: \(selectedRecords?.description ?? "nil") + submits: \(submits?.description ?? "nil") + """ + } +} diff --git a/alsongDalsong/ASEntity/ASEntity/Status.swift b/alsongDalsong/ASEntity/ASEntity/Status.swift index 5354948b..1574782e 100644 --- a/alsongDalsong/ASEntity/ASEntity/Status.swift +++ b/alsongDalsong/ASEntity/ASEntity/Status.swift @@ -7,3 +7,20 @@ public enum Status: String, Codable { case hint case result } + +extension Status: CustomStringConvertible { + public var description: String { + switch self { + case .humming: + return "humming" + case .rehumming: + return "rehumming" + case .waiting: + return "waiting" + case .hint: + return "hint" + case .result: + return "result" + } + } +} diff --git a/alsongDalsong/ASNetworkKit/ASNetworkKit/Firebase/ASFirebaseDatabase.swift b/alsongDalsong/ASNetworkKit/ASNetworkKit/Firebase/ASFirebaseDatabase.swift index 0158b3bc..ad4d7937 100644 --- a/alsongDalsong/ASNetworkKit/ASNetworkKit/Firebase/ASFirebaseDatabase.swift +++ b/alsongDalsong/ASNetworkKit/ASNetworkKit/Firebase/ASFirebaseDatabase.swift @@ -1,11 +1,12 @@ import ASEntity +import ASLogKit import Combine @preconcurrency internal import FirebaseFirestore public final class ASFirebaseDatabase: ASFirebaseDatabaseProtocol { private let firestoreRef = Firestore.firestore() private var roomListeners: ListenerRegistration? - private var roomPublisher = PassthroughSubject() + private var roomPublisher = CurrentValueSubject(nil) public func addRoomListener(roomNumber: String) -> AnyPublisher { let roomRef = firestoreRef.collection("rooms").document(roomNumber) @@ -18,8 +19,14 @@ public final class ASFirebaseDatabase: ASFirebaseDatabaseProtocol { return self.roomPublisher.send(completion: .failure(ASNetworkErrors.FirebaseListenerError)) } + if document.metadata.isFromCache { + Logger.debug("로컬 캐시에서 데이터를 가져온 경우") + return + } + do { let room = try document.data(as: Room.self) + Logger.debug("방 정보를 가져왔습니다.\n\(room)") return self.roomPublisher.send(room) } catch { return self.roomPublisher.send(completion: .failure(ASNetworkErrors.FirebaseListenerError)) @@ -27,10 +34,13 @@ public final class ASFirebaseDatabase: ASFirebaseDatabaseProtocol { } roomListeners = listener - return roomPublisher.eraseToAnyPublisher() + return roomPublisher + .compactMap { $0 } + .eraseToAnyPublisher() } public func removeRoomListener() { + roomPublisher.send(nil) roomListeners?.remove() } } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Components/UIKitComponents/ASAlertController.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Components/UIKitComponents/ASAlertController.swift index 7002a63b..ac366ee3 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Components/UIKitComponents/ASAlertController.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Components/UIKitComponents/ASAlertController.swift @@ -373,7 +373,7 @@ enum ASAlertText { case .startGame: "게임을 시작하는 중..." case .submitMusic: "노래를 전송하는 중..." case .submitHumming: "허밍을 전송하는 중..." - case .nextResult: "다음 결과를 가져오는 중..." + case .nextResult: "결과를 가져오는 중..." } } } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewController.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewController.swift index 4e6ab120..89c99a47 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewController.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewController.swift @@ -41,7 +41,8 @@ final class LobbyViewController: UIViewController { override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) if viewmodel.isLeaveRoom { - viewmodel.leaveRoom() + viewmodel.cancelSubscriptions() + cancellables.removeAll() } } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift index faabcb8a..0754bcae 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift @@ -123,4 +123,8 @@ final class LobbyViewModel: ObservableObject, @unchecked Sendable { } } } + + public func cancelSubscriptions() { + cancellables.removeAll() + } } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewController.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewController.swift index 12f0575b..ab701430 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewController.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewController.swift @@ -31,6 +31,7 @@ class HummingResultViewController: UIViewController { override func viewDidDisappear(_ animated: Bool) { viewModel?.cancelSubscriptions() + cancellables.removeAll() } private func setResultTableView() { diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift index fbf4c8ed..a83892be 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift @@ -32,6 +32,7 @@ final class SubmitAnswerViewController: UIViewController { override func viewDidDisappear(_ animated: Bool) { viewModel.cancelSubscriptions() + cancellables.removeAll() } private func bindToComponents() { From 41a605414910ea1c9ab09d96338327fc9ad4a39a Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Sun, 1 Dec 2024 18:08:36 +0900 Subject: [PATCH 02/20] =?UTF-8?q?refactor:=20Entity=20Custom=20StringConve?= =?UTF-8?q?rtible=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- alsongDalsong/ASEntity/ASEntity/Answer.swift | 9 +++++++++ alsongDalsong/ASEntity/ASEntity/Music.swift | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/alsongDalsong/ASEntity/ASEntity/Answer.swift b/alsongDalsong/ASEntity/ASEntity/Answer.swift index d161467d..365d2efb 100644 --- a/alsongDalsong/ASEntity/ASEntity/Answer.swift +++ b/alsongDalsong/ASEntity/ASEntity/Answer.swift @@ -26,3 +26,12 @@ extension Answer { music: Music.musicStub4, playlist: Playlist()) } + +extension Answer: CustomStringConvertible { + public var description: String { + return """ + player: \(player?.description ?? "nil") + music: \(music?.description ?? "nil") + """ + } +} diff --git a/alsongDalsong/ASEntity/ASEntity/Music.swift b/alsongDalsong/ASEntity/ASEntity/Music.swift index d9070493..5797783b 100644 --- a/alsongDalsong/ASEntity/ASEntity/Music.swift +++ b/alsongDalsong/ASEntity/ASEntity/Music.swift @@ -38,3 +38,9 @@ extension Music { public static let musicStub3 = Music(title: "으아~", artist: "김흥국") public static let musicStub4 = Music(title: "이브, 프시케 그리고 푸른 수염의 아내", artist: "르세라핌") } + +extension Music: CustomStringConvertible { + public var description: String { + return "\(title ?? "Unknown") - \(artist ?? "Unknown")" + } +} From 84e91f46037559b3d3ad9ac03f023b2a30f3ff85 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Sun, 1 Dec 2024 18:13:24 +0900 Subject: [PATCH 03/20] =?UTF-8?q?refactor:=20Main=20Repository=EC=97=90?= =?UTF-8?q?=EC=84=9C=20Room=20=ED=95=98=EB=82=98=EC=9D=98=20=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=A6=BC=EB=A7=8C=20=EA=B0=80=EC=A7=80=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 이전 값과 중복된 값인지 판단은 .removeDuplicates() 로 제거 --- .../ASEntity/ASEntity/GameState.swift | 2 +- .../Protocols/MainRepositoryProtocol.swift | 13 +---- .../Repositories/AnswersRepository.swift | 18 ++++--- .../Repositories/GameStateRepository.swift | 9 ++-- .../Repositories/GameStatusRepository.swift | 19 ++++--- .../HummingResultRepository.swift | 52 ++++++++++++++----- .../Repositories/MainRepository.swift | 43 ++------------- .../Repositories/PlayersRepository.swift | 15 +++--- .../Repositories/RecordsRepository.swift | 18 ++++--- .../Repositories/RoomInfoRepository.swift | 15 +++--- .../SelectedRecordsRepository.swift | 5 +- .../Repositories/SubmitsRepository.swift | 11 ++-- 12 files changed, 108 insertions(+), 112 deletions(-) diff --git a/alsongDalsong/ASEntity/ASEntity/GameState.swift b/alsongDalsong/ASEntity/ASEntity/GameState.swift index 1c6d2e7f..a38d03f8 100644 --- a/alsongDalsong/ASEntity/ASEntity/GameState.swift +++ b/alsongDalsong/ASEntity/ASEntity/GameState.swift @@ -1,4 +1,4 @@ -public struct GameState { +public struct GameState: Equatable { public let mode: Mode? public let recordOrder: UInt8? public let status: Status? diff --git a/alsongDalsong/ASRepository/ASRepository/Protocols/MainRepositoryProtocol.swift b/alsongDalsong/ASRepository/ASRepository/Protocols/MainRepositoryProtocol.swift index 6467d5fa..1aebf1a4 100644 --- a/alsongDalsong/ASRepository/ASRepository/Protocols/MainRepositoryProtocol.swift +++ b/alsongDalsong/ASRepository/ASRepository/Protocols/MainRepositoryProtocol.swift @@ -4,18 +4,7 @@ import Foundation public protocol MainRepositoryProtocol { var myId: String? { get } - var number: CurrentValueSubject { get } - var host: CurrentValueSubject { get } - var players: CurrentValueSubject<[Player]?, Never> { get } - var mode: CurrentValueSubject { get } - var round: CurrentValueSubject { get } - var status: CurrentValueSubject { get } - var recordOrder: CurrentValueSubject { get } - var records: CurrentValueSubject<[ASEntity.Record]?, Never> { get } - var answers: CurrentValueSubject<[Answer]?, Never> { get } - var dueTime: CurrentValueSubject { get } - var selectedRecords: CurrentValueSubject<[UInt8]?, Never> { get } - var submits: CurrentValueSubject<[Answer]?, Never> { get } + var room: CurrentValueSubject { get } func connectRoom(roomNumber: String) func disconnectRoom() diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/AnswersRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/AnswersRepository.swift index 8259529b..dfc21a5f 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/AnswersRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/AnswersRepository.swift @@ -9,23 +9,26 @@ import ASRepositoryProtocol public final class AnswersRepository: AnswersRepositoryProtocol { private var mainRepository: MainRepositoryProtocol private var networkManager: ASNetworkManagerProtocol + public init(mainRepository: MainRepositoryProtocol, networkManager: ASNetworkManagerProtocol) { self.mainRepository = mainRepository self.networkManager = networkManager } public func getAnswers() -> AnyPublisher<[Answer], Never> { - mainRepository.answers + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.answers } + .removeDuplicates() .eraseToAnyPublisher() } public func getAnswersCount() -> AnyPublisher { - mainRepository.answers + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.answers } .map { $0.count } + .removeDuplicates() .eraseToAnyPublisher() } @@ -34,9 +37,10 @@ public final class AnswersRepository: AnswersRepositoryProtocol { return Just(nil).eraseToAnyPublisher() } - return mainRepository.answers + return mainRepository.room .receive(on: DispatchQueue.main) - .compactMap(\.self) + .compactMap { $0?.answers } + .removeDuplicates() .flatMap { answers in Just(answers.first { $0.player?.id == myId }) .eraseToAnyPublisher() @@ -46,7 +50,7 @@ public final class AnswersRepository: AnswersRepositoryProtocol { public func submitMusic(answer: ASEntity.Music) async throws -> Bool { let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), - URLQueryItem(name: "roomNumber", value: mainRepository.number.value)] + URLQueryItem(name: "roomNumber", value: mainRepository.room.value?.number)] let endPoint = FirebaseEndpoint(path: .submitMusic, method: .post) .update(\.queryItems, with: queryItems) diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/GameStateRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/GameStateRepository.swift index 586287a4..1fa8bab5 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/GameStateRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/GameStateRepository.swift @@ -11,12 +11,13 @@ public final class GameStateRepository: GameStateRepositoryProtocol { } public func getGameState() -> AnyPublisher { - Publishers.CombineLatest4(mainRepository.mode, mainRepository.recordOrder, mainRepository.status, mainRepository.round) + mainRepository.room .receive(on: DispatchQueue.main) - .map { mode, recordOrder, status, round in - guard let mode, let round, let players = self.mainRepository.players.value else { return nil } - return ASEntity.GameState(mode: mode, recordOrder: recordOrder, status: status, round: round, players: players) + .compactMap { room in + guard let mode = room?.mode, let players = room?.players else { return nil } + return ASEntity.GameState(mode: mode, recordOrder: room?.recordOrder, status: room?.status, round: room?.round, players: players) } + .removeDuplicates() .eraseToAnyPublisher() } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/GameStatusRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/GameStatusRepository.swift index 9c555c94..206b3eed 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/GameStatusRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/GameStatusRepository.swift @@ -11,29 +11,34 @@ public final class GameStatusRepository: GameStatusRepositoryProtocol { } public func getStatus() -> AnyPublisher { - mainRepository.status + mainRepository.room .receive(on: DispatchQueue.main) + .compactMap { $0?.status } + .removeDuplicates() .eraseToAnyPublisher() } public func getRound() -> AnyPublisher { - mainRepository.round + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.round } + .removeDuplicates() .eraseToAnyPublisher() } public func getRecordOrder() -> AnyPublisher { - mainRepository.recordOrder + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.recordOrder } + .removeDuplicates() .eraseToAnyPublisher() } public func getDueTime() -> AnyPublisher { - mainRepository.dueTime + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.dueTime } + .removeDuplicates() .eraseToAnyPublisher() } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/HummingResultRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/HummingResultRepository.swift index fd5bf857..0c95e8b2 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/HummingResultRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/HummingResultRepository.swift @@ -1,15 +1,15 @@ -import Foundation -import Combine import ASEntity import ASNetworkKit import ASRepositoryProtocol +import Combine +import Foundation public final class HummingResultRepository: HummingResultRepositoryProtocol { private var mainRepository: MainRepositoryProtocol private let storageManager: ASFirebaseStorageProtocol private let networkManager: ASNetworkManagerProtocol - public init ( + public init( storageManager: ASFirebaseStorageProtocol, networkManager: ASNetworkManagerProtocol, mainRepository: MainRepositoryProtocol @@ -20,18 +20,22 @@ public final class HummingResultRepository: HummingResultRepositoryProtocol { } public func getResult() -> AnyPublisher<[(answer: Answer, records: [ASEntity.Record], submit: Answer, recordOrder: UInt8)], Never> { - Publishers.Zip4(mainRepository.answers, mainRepository.records, mainRepository.submits, mainRepository.recordOrder) - .compactMap { answers, records, submits, recordOrder in - answers?.map { answer in + mainRepository.room + .compactMap { room in + guard let room else { return [] } + return room.answers?.map { answer in let relatedRecords: [ASEntity.Record] = self.getRelatedRecords(for: answer, - from: records, - count: answers?.count ?? 0) - let relatedSubmit: Answer = self.getRelatedSubmit(for: answer, from: submits) + from: room.records, + count: room.answers?.count ?? 0) + let relatedSubmit: Answer = self.getRelatedSubmit(for: answer, from: room.submits) - return (answer: answer, records: relatedRecords, submit: relatedSubmit, recordOrder: recordOrder ?? 0) + return (answer: answer, records: relatedRecords, submit: relatedSubmit, recordOrder: room.recordOrder ?? 0) } } .receive(on: DispatchQueue.main) + .removeDuplicates(by: { lhs, rhs in + self.areResultsEqual(lhs: lhs, rhs: rhs) + }) .eraseToAnyPublisher() } @@ -42,7 +46,7 @@ public final class HummingResultRepository: HummingResultRepositoryProtocol { let tempCheck: Int = (((answer.player?.order ?? 0) + i) % count) if let filteredRecord = records?.first(where: { record in (tempCheck == record.player?.order) && - (record.recordOrder ?? 0 == i) + (record.recordOrder ?? 0 == i) }) { filteredRecords.append(filteredRecord) } @@ -59,7 +63,7 @@ public final class HummingResultRepository: HummingResultRepositoryProtocol { targetOrder == submit.player?.order }) - //TODO: nil 값에 대한 처리 필요 + // TODO: nil 값에 대한 처리 필요 return submit ?? Answer.answerStub1 } @@ -78,11 +82,31 @@ public final class HummingResultRepository: HummingResultRepositoryProtocol { } } +extension HummingResultRepository { + private func areResultsEqual( + lhs: [(answer: Answer, records: [ASEntity.Record], submit: Answer, recordOrder: UInt8)], + rhs: [(answer: Answer, records: [ASEntity.Record], submit: Answer, recordOrder: UInt8)] + ) -> Bool { + guard lhs.count == rhs.count else { return false } + for (index, lhsItem) in lhs.enumerated() { + let rhsItem = rhs[index] + if lhsItem.answer != rhsItem.answer || + lhsItem.records != rhsItem.records || + lhsItem.submit != rhsItem.submit || + lhsItem.recordOrder != rhsItem.recordOrder + { + return false + } + } + return true + } +} + public final class LocalHummingResultRepository: HummingResultRepositoryProtocol { private let storageManager: ASFirebaseStorageProtocol private let networkManager: ASNetworkManagerProtocol - public init ( + public init( storageManager: ASFirebaseStorageProtocol, networkManager: ASNetworkManagerProtocol ) { @@ -117,7 +141,7 @@ public final class LocalHummingResultRepository: HummingResultRepositoryProtocol let tempCheck: Int = (((answer.player?.order ?? 0) + i) % count) if let filteredRecord = records?.first(where: { record in (tempCheck == record.player?.order) && - (record.recordOrder ?? 0 == i) + (record.recordOrder ?? 0 == i) }) { filteredRecords.append(filteredRecord) } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift index 4bf32fde..0d550c06 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift @@ -9,18 +9,7 @@ import ASRepositoryProtocol public final class MainRepository: MainRepositoryProtocol { public var myId: String? { ASFirebaseAuth.myID } - public var number = CurrentValueSubject(nil) - public var host = CurrentValueSubject(nil) - public var players = CurrentValueSubject<[Player]?, Never>(nil) - public var mode = CurrentValueSubject(nil) - public var round = CurrentValueSubject(nil) - public var status = CurrentValueSubject(nil) - public var recordOrder = CurrentValueSubject(nil) - public var answers = CurrentValueSubject<[ASEntity.Answer]?, Never>(nil) - public var dueTime = CurrentValueSubject(nil) - public var submits = CurrentValueSubject<[ASEntity.Answer]?, Never>(nil) - public var records = CurrentValueSubject<[ASEntity.Record]?, Never>(nil) - public var selectedRecords = CurrentValueSubject<[UInt8]?, Never>(nil) + public var room = CurrentValueSubject(nil) private let databaseManager: ASFirebaseDatabaseProtocol private let networkManager: ASNetworkManagerProtocol @@ -44,35 +33,13 @@ public final class MainRepository: MainRepositoryProtocol { } } receiveValue: { [weak self] room in guard let self else { return } - update(\.number, with: room.number) - update(\.host, with: room.host) - update(\.players, with: room.players) - update(\.mode, with: room.mode) - update(\.round, with: room.round) - update(\.status, with: room.status) - update(\.recordOrder, with: room.recordOrder) - update(\.answers, with: room.answers) - update(\.dueTime, with: room.dueTime) - update(\.submits, with: room.submits) - update(\.records, with: room.records) - update(\.selectedRecords, with: room.selectedRecords) + self.room.send(room) } .store(in: &cancellables) } public func disconnectRoom() { - update(\.number, with: nil) - update(\.host, with: nil) - update(\.players, with: nil) - update(\.mode, with: nil) - update(\.round, with: nil) - update(\.status, with: nil) - update(\.recordOrder, with: nil) - update(\.answers, with: nil) - update(\.dueTime, with: nil) - update(\.submits, with: nil) - update(\.records, with: nil) - update(\.selectedRecords, with: nil) + room.send(nil) databaseManager.removeRoomListener() } @@ -88,7 +55,7 @@ public final class MainRepository: MainRepositoryProtocol { public func postRecording(_ record: Data) async throws -> Bool { let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), - URLQueryItem(name: "roomNumber", value: number.value)] + URLQueryItem(name: "roomNumber", value: room.value?.number)] let endPoint = FirebaseEndpoint(path: .uploadRecording, method: .post) .update(\.queryItems, with: queryItems) @@ -105,7 +72,7 @@ public final class MainRepository: MainRepositoryProtocol { public func postResetGame() async throws -> Bool { let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), - URLQueryItem(name: "roomNumber", value: number.value)] + URLQueryItem(name: "roomNumber", value: room.value?.number)] let endPoint = FirebaseEndpoint(path: .resetGame, method: .post) .update(\.queryItems, with: queryItems) diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/PlayersRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/PlayersRepository.swift index 3e4e1038..d40d789a 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/PlayersRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/PlayersRepository.swift @@ -15,30 +15,29 @@ public final class PlayersRepository: PlayersRepositoryProtocol { } public func getPlayers() -> AnyPublisher<[Player], Never> { - mainRepository.players + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.players } + .removeDuplicates() .eraseToAnyPublisher() } public func getPlayersCount() -> AnyPublisher { - mainRepository.players - .receive(on: DispatchQueue.main) - .compactMap { $0 } + self.getPlayers() .map { $0.count } .eraseToAnyPublisher() } public func getHost() -> AnyPublisher { - mainRepository.host + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.host } + .removeDuplicates() .eraseToAnyPublisher() } public func isHost() -> AnyPublisher { self.getHost() - .receive(on: DispatchQueue.main) .map { $0.id == ASFirebaseAuth.myID } .eraseToAnyPublisher() } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/RecordsRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/RecordsRepository.swift index 01179f9d..b129453d 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/RecordsRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/RecordsRepository.swift @@ -12,14 +12,15 @@ public final class RecordsRepository: RecordsRepositoryProtocol { } public func getRecords() -> AnyPublisher<[ASEntity.Record], Never> { - mainRepository.records + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.records } + .removeDuplicates() .eraseToAnyPublisher() } public func getRecordsCount(on recordOrder: UInt8) -> AnyPublisher { - return mainRepository.records + return self.getRecords() .compactMap { $0 } .map { records in return records.filter { Int($0.recordOrder ?? 0) == recordOrder } @@ -31,11 +32,13 @@ public final class RecordsRepository: RecordsRepositoryProtocol { } public func getHumming(on recordOrder: UInt8) -> AnyPublisher { - let recordsPublisher = mainRepository.records - let playersPublisher = mainRepository.players - return recordsPublisher - .combineLatest(playersPublisher) + return mainRepository.room + .receive(on: DispatchQueue.main) + .compactMap { room in + guard let room = room else { return nil } + return (room.records, room.players) + } .map { [weak self] records, players -> ASEntity.Record? in self?.findRecord( records: records, @@ -43,6 +46,7 @@ public final class RecordsRepository: RecordsRepositoryProtocol { recordOrder: recordOrder ) } + .removeDuplicates() .eraseToAnyPublisher() } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/RoomInfoRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/RoomInfoRepository.swift index eebd887f..19ed0012 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/RoomInfoRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/RoomInfoRepository.swift @@ -11,23 +11,26 @@ public final class RoomInfoRepository: RoomInfoRepositoryProtocol { } public func getRoomNumber() -> AnyPublisher { - mainRepository.number + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.number } + .removeDuplicates() .eraseToAnyPublisher() } public func getMode() -> AnyPublisher { - mainRepository.mode + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.mode } + .removeDuplicates() .eraseToAnyPublisher() } public func getRecordOrder() -> AnyPublisher { - mainRepository.recordOrder + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.recordOrder } + .removeDuplicates() .eraseToAnyPublisher() } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/SelectedRecordsRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/SelectedRecordsRepository.swift index 5adf1dc1..822b2f8b 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/SelectedRecordsRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/SelectedRecordsRepository.swift @@ -11,9 +11,10 @@ public final class SelectedRecordsRepository: SelectedRecordsRepositoryProtocol } public func getSelectedRecords() -> AnyPublisher<[UInt8], Never> { - mainRepository.selectedRecords + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.selectedRecords } + .removeDuplicates() .eraseToAnyPublisher() } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/SubmitsRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/SubmitsRepository.swift index 3b1cd8f4..b3ae3992 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/SubmitsRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/SubmitsRepository.swift @@ -16,23 +16,22 @@ public final class SubmitsRepository: SubmitsRepositoryProtocol { } public func getSubmits() -> AnyPublisher<[Answer], Never> { - mainRepository.answers + mainRepository.room .receive(on: DispatchQueue.main) - .compactMap { $0 } + .compactMap { $0?.submits } + .removeDuplicates() .eraseToAnyPublisher() } public func getSubmitsCount() -> AnyPublisher { - mainRepository.submits - .receive(on: DispatchQueue.main) - .compactMap { $0 } + self.getSubmits() .map { $0.count } .eraseToAnyPublisher() } public func submitAnswer(answer: Music) async throws -> Bool { let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), - URLQueryItem(name: "roomNumber", value: mainRepository.number.value)] + URLQueryItem(name: "roomNumber", value: mainRepository.room.value?.number)] let endPoint = FirebaseEndpoint(path: .submitAnswer, method: .post) .update(\.queryItems, with: queryItems) .update(\.headers, with: ["Content-Type": "application/json"]) From 5e7383b4ce9fb8d94e3eeead28e405575eec14c0 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Sun, 1 Dec 2024 22:17:35 +0900 Subject: [PATCH 04/20] =?UTF-8?q?feat:=20Network=20Helper=20=EB=A7=8C?= =?UTF-8?q?=EB=93=9C=EB=8A=94=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ASNetworkKit/FirebaseEndpoint.swift | 15 +- .../Protocols/MainRepositoryProtocol.swift | 15 +- .../Protocols/RepositoryProtocols.swift | 10 +- .../Repositories/AnswersRepository.swift | 23 +- .../Repositories/AvatarRepository.swift | 21 +- .../Repositories/GameStateRepository.swift | 2 +- .../Repositories/GameStatusRepository.swift | 4 +- .../HummingResultRepository.swift | 46 +--- .../Repositories/MainRepository.swift | 229 +++++++++++++++--- .../Repositories/MusicRepository.swift | 23 +- .../Repositories/PlayersRepository.swift | 19 +- .../Repositories/RecordsRepository.swift | 29 +-- .../Repositories/RoomActionRepository.swift | 78 +----- .../Repositories/RoomInfoRepository.swift | 4 +- .../SelectedRecordsRepository.swift | 9 +- .../Repositories/SubmitsRepository.swift | 28 +-- .../ASRepository/RepsotioryAssembly.swift | 39 +-- .../Sources/Views/Lobby/LobbyViewModel.swift | 6 +- .../MusicPanel/MusicPanelViewModel.swift | 4 +- .../Views/Result/HummingResultViewModel.swift | 39 +-- .../SelectMusic/SelectMusicViewModel.swift | 30 +-- .../SubmitAnswer/SubmitAnswerViewModel.swift | 8 +- firebase/functions/api/JoinRoom.js | 1 + 23 files changed, 352 insertions(+), 330 deletions(-) diff --git a/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift b/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift index 05e7c199..abe35869 100644 --- a/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift +++ b/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift @@ -14,7 +14,7 @@ public struct FirebaseEndpoint: Endpoint, Equatable { public init(path: Path, method: HTTPMethod) { self.path = path self.method = method - headers = [:] + self.headers = [:] } // TODO: - firebase api/cloud func에 맞는 path 넣기 @@ -29,7 +29,7 @@ public struct FirebaseEndpoint: Endpoint, Equatable { case submitMusic case submitAnswer case resetGame - + public var description: String { switch self { case .auth: @@ -69,4 +69,15 @@ public extension FirebaseEndpoint { Self(path: .auth, method: .get) .update(\.queryItems, with: [.init(name: "listAvatarUrls", value: "true")]) } + + func withCommonQueryItems(roomNumber: String?, userID: String?) -> Self { + var items = [URLQueryItem]() + if let userID { + items.append(URLQueryItem(name: "userId", value: userID)) + } + if let roomNumber { + items.append(URLQueryItem(name: "roomNumber", value: roomNumber)) + } + return self.update(\.queryItems, with: items) + } } diff --git a/alsongDalsong/ASRepository/ASRepository/Protocols/MainRepositoryProtocol.swift b/alsongDalsong/ASRepository/ASRepository/Protocols/MainRepositoryProtocol.swift index 1aebf1a4..e15c8564 100644 --- a/alsongDalsong/ASRepository/ASRepository/Protocols/MainRepositoryProtocol.swift +++ b/alsongDalsong/ASRepository/ASRepository/Protocols/MainRepositoryProtocol.swift @@ -8,7 +8,18 @@ public protocol MainRepositoryProtocol { func connectRoom(roomNumber: String) func disconnectRoom() - + + func createRoom(nickname: String, avatar: URL) async throws -> String + func joinRoom(nickname: String, avatar: URL, roomNumber: String) async throws -> Bool + func leaveRoom() async throws -> Bool + func startGame() async throws -> Bool + func changeMode(mode: Mode) async throws -> Bool + func changeRecordOrder() async throws -> Bool + func resetGame() async throws -> Bool + func submitAnswer(answer: Music) async throws -> Bool + func getAvatarUrls() async throws -> [URL] + func getResource(url: URL) async throws -> Data + + func submitMusic(answer: ASEntity.Music) async throws -> Bool func postRecording(_ record: Data) async throws -> Bool - func postResetGame() async throws -> Bool } diff --git a/alsongDalsong/ASRepository/ASRepository/Protocols/RepositoryProtocols.swift b/alsongDalsong/ASRepository/ASRepository/Protocols/RepositoryProtocols.swift index b0813a62..cc914b44 100644 --- a/alsongDalsong/ASRepository/ASRepository/Protocols/RepositoryProtocols.swift +++ b/alsongDalsong/ASRepository/ASRepository/Protocols/RepositoryProtocols.swift @@ -55,14 +55,14 @@ public protocol RoomActionRepositoryProtocol { func createRoom(nickname: String, avatar: URL) async throws -> String func joinRoom(nickname: String, avatar: URL, roomNumber: String) async throws -> Bool func leaveRoom() async throws -> Bool - func startGame(roomNumber: String) async throws -> Bool - func changeMode(roomNumber: String, mode: Mode) async throws -> Bool - func changeRecordOrder(roomNumber: String) async throws -> Bool + func startGame() async throws -> Bool + func changeMode(mode: Mode) async throws -> Bool + func changeRecordOrder() async throws -> Bool func resetGame() async throws -> Bool } public protocol MusicRepositoryProtocol { - func getMusicData(url: URL) async -> Data? + func getMusicData(url: URL) async throws -> Data? } public protocol GameStateRepositoryProtocol { @@ -71,5 +71,5 @@ public protocol GameStateRepositoryProtocol { public protocol HummingResultRepositoryProtocol { func getResult() -> AnyPublisher<[(answer: Answer, records: [ASEntity.Record], submit: Answer, recordOrder: UInt8)], Never> - func getRecordData(url: URL) -> Future + func getRecordData(url: URL) async throws -> Data } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/AnswersRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/AnswersRepository.swift index dfc21a5f..ccc61d92 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/AnswersRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/AnswersRepository.swift @@ -1,18 +1,13 @@ -import ASDecoder -import ASEncoder import ASEntity -import ASNetworkKit +import ASRepositoryProtocol import Combine import Foundation -import ASRepositoryProtocol public final class AnswersRepository: AnswersRepositoryProtocol { private var mainRepository: MainRepositoryProtocol - private var networkManager: ASNetworkManagerProtocol - - public init(mainRepository: MainRepositoryProtocol, networkManager: ASNetworkManagerProtocol) { + + public init(mainRepository: MainRepositoryProtocol) { self.mainRepository = mainRepository - self.networkManager = networkManager } public func getAnswers() -> AnyPublisher<[Answer], Never> { @@ -27,7 +22,7 @@ public final class AnswersRepository: AnswersRepositoryProtocol { mainRepository.room .receive(on: DispatchQueue.main) .compactMap { $0?.answers } - .map { $0.count } + .map(\.count) .removeDuplicates() .eraseToAnyPublisher() } @@ -49,14 +44,6 @@ public final class AnswersRepository: AnswersRepositoryProtocol { } public func submitMusic(answer: ASEntity.Music) async throws -> Bool { - let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), - URLQueryItem(name: "roomNumber", value: mainRepository.room.value?.number)] - let endPoint = FirebaseEndpoint(path: .submitMusic, method: .post) - .update(\.queryItems, with: queryItems) - - let body = try ASEncoder.encode(answer) - let response = try await networkManager.sendRequest(to: endPoint, type: .json, body: body, option: .none) - let responseDict = try ASDecoder.decode([String: String].self, from: response) - return !responseDict.isEmpty + try await mainRepository.submitMusic(answer: answer) } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/AvatarRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/AvatarRepository.swift index fcaf211d..7dc796bd 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/AvatarRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/AvatarRepository.swift @@ -1,24 +1,19 @@ -import ASNetworkKit +import ASRepositoryProtocol import Combine import Foundation -import ASRepositoryProtocol public final class AvatarRepository: AvatarRepositoryProtocol { - // TODO: - Container로 주입 - private let storageManager: ASFirebaseStorageProtocol - private let networkManager: ASNetworkManagerProtocol + private let mainRepository: MainRepositoryProtocol - public init ( - storageManager: ASFirebaseStorageProtocol, - networkManager: ASNetworkManagerProtocol + public init( + mainRepository: MainRepositoryProtocol ) { - self.storageManager = storageManager - self.networkManager = networkManager + self.mainRepository = mainRepository } public func getAvatarUrls() async throws -> [URL] { do { - let urls = try await self.storageManager.getAvatarUrls() + let urls = try await self.mainRepository.getAvatarUrls() return urls } catch { throw error @@ -27,9 +22,7 @@ public final class AvatarRepository: AvatarRepositoryProtocol { public func getAvatarData(url: URL) async -> Data? { do { - guard let endpoint = ResourceEndpoint(url: url) else { return nil } - let data = try await self.networkManager.sendRequest(to: endpoint, type: .none, body: nil, option: .both) - return data + return try await mainRepository.getResource(url: url) } catch { return nil } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/GameStateRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/GameStateRepository.swift index 1fa8bab5..a6928518 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/GameStateRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/GameStateRepository.swift @@ -1,7 +1,7 @@ import ASEntity +import ASRepositoryProtocol import Combine import Foundation -import ASRepositoryProtocol public final class GameStateRepository: GameStateRepositoryProtocol { private var mainRepository: MainRepositoryProtocol diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/GameStatusRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/GameStatusRepository.swift index 206b3eed..6ebdc247 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/GameStatusRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/GameStatusRepository.swift @@ -1,7 +1,7 @@ -import Foundation -import Combine import ASEntity import ASRepositoryProtocol +import Combine +import Foundation public final class GameStatusRepository: GameStatusRepositoryProtocol { private var mainRepository: MainRepositoryProtocol diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/HummingResultRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/HummingResultRepository.swift index 0c95e8b2..c2d7891b 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/HummingResultRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/HummingResultRepository.swift @@ -1,21 +1,14 @@ import ASEntity -import ASNetworkKit import ASRepositoryProtocol import Combine import Foundation public final class HummingResultRepository: HummingResultRepositoryProtocol { private var mainRepository: MainRepositoryProtocol - private let storageManager: ASFirebaseStorageProtocol - private let networkManager: ASNetworkManagerProtocol public init( - storageManager: ASFirebaseStorageProtocol, - networkManager: ASNetworkManagerProtocol, mainRepository: MainRepositoryProtocol ) { - self.storageManager = storageManager - self.networkManager = networkManager self.mainRepository = mainRepository } @@ -67,18 +60,8 @@ public final class HummingResultRepository: HummingResultRepositoryProtocol { return submit ?? Answer.answerStub1 } - public func getRecordData(url: URL) -> Future { - Future { promise in - Task { - do { - guard let endpoint = ResourceEndpoint(url: url) else { return promise(.failure(ASNetworkErrors.urlError)) } - let data = try await self.networkManager.sendRequest(to: endpoint, type: .json, body: nil, option: .both) - promise(.success(data)) - } catch { - promise(.failure(error)) - } - } - } + public func getRecordData(url: URL) async throws -> Data { + try await mainRepository.getResource(url: url) } } @@ -103,16 +86,7 @@ extension HummingResultRepository { } public final class LocalHummingResultRepository: HummingResultRepositoryProtocol { - private let storageManager: ASFirebaseStorageProtocol - private let networkManager: ASNetworkManagerProtocol - - public init( - storageManager: ASFirebaseStorageProtocol, - networkManager: ASNetworkManagerProtocol - ) { - self.storageManager = storageManager - self.networkManager = networkManager - } + public init() {} public func getResult() -> AnyPublisher<[(answer: Answer, records: [ASEntity.Record], submit: Answer, recordOrder: UInt8)], Never> { let tempAnswers = [Answer.answerStub1, Answer.answerStub2, Answer.answerStub3, Answer.answerStub4] @@ -160,17 +134,7 @@ public final class LocalHummingResultRepository: HummingResultRepositoryProtocol return submit ?? Answer.answerStub1 } - public func getRecordData(url: URL) -> Future { - Future { promise in - Task { - do { - guard let endpoint = ResourceEndpoint(url: url) else { return promise(.failure(ASNetworkErrors.urlError)) } - let data = try await self.networkManager.sendRequest(to: endpoint, type: .json, body: nil, option: .both) - promise(.success(data)) - } catch { - promise(.failure(error)) - } - } - } + public func getRecordData(url: URL) async throws -> Data { + Data() } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift index 0d550c06..8e3cf6d1 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift @@ -3,25 +3,34 @@ import ASEncoder import ASEntity import ASLogKit import ASNetworkKit +import ASRepositoryProtocol import Combine import Foundation -import ASRepositoryProtocol public final class MainRepository: MainRepositoryProtocol { public var myId: String? { ASFirebaseAuth.myID } public var room = CurrentValueSubject(nil) private let databaseManager: ASFirebaseDatabaseProtocol + private let authManager: ASFirebaseAuthProtocol + private let storageManager: ASFirebaseStorageProtocol private let networkManager: ASNetworkManagerProtocol + private let networkHelper: NetworkHelper private var cancellables: Set = [] - public init(databaseManager: ASFirebaseDatabaseProtocol, networkManager: ASNetworkManagerProtocol) { + public init(databaseManager: ASFirebaseDatabaseProtocol, + authManager: ASFirebaseAuthProtocol, + storageManager: ASFirebaseStorageProtocol, + networkManager: ASNetworkManagerProtocol) + { self.databaseManager = databaseManager - self.networkManager = networkManager + self.authManager = authManager + self.storageManager = storageManager + self.networkHelper = NetworkHelper(networkManager: networkManager) } public func connectRoom(roomNumber: String) { - databaseManager.addRoomListener(roomNumber: roomNumber) + self.databaseManager.addRoomListener(roomNumber: roomNumber) .receive(on: DispatchQueue.main) .sink { completion in switch completion { @@ -35,56 +44,222 @@ public final class MainRepository: MainRepositoryProtocol { guard let self else { return } self.room.send(room) } - .store(in: &cancellables) + .store(in: &self.cancellables) } public func disconnectRoom() { - room.send(nil) - databaseManager.removeRoomListener() + self.room.send(nil) + self.databaseManager.removeRoomListener() } - - private func update( - _ keyPath: ReferenceWritableKeyPath>, - with newValue: Value? - ) { - let subject = self[keyPath: keyPath] - if subject.value != newValue { - subject.send(newValue) + + public func createRoom(nickname: String, avatar: URL) async throws -> String { + try await self.authManager.signIn(nickname: nickname, avatarURL: avatar) + let response: [String: String]? = try await self.sendRequest( + endpointPath: .createRoom, + requestBody: ["hostID": ASFirebaseAuth.myID] + ) + guard let roomNumber = response?["number"] as? String else { + throw ASNetworkErrors.responseError } + return roomNumber } - - public func postRecording(_ record: Data) async throws -> Bool { + + public func joinRoom(nickname: String, avatar: URL, roomNumber: String) async throws -> Bool { + let player = try await self.authManager.signIn(nickname: nickname, avatarURL: avatar) + let response: [String: String]? = try await self.sendRequest( + endpointPath: .joinRoom, + requestBody: ["roomNumber": roomNumber, "userId": ASFirebaseAuth.myID] + ) + guard let roomNumberResponse = response?["number"] as? String else { + throw ASNetworkErrors.responseError + } + return roomNumberResponse == roomNumber + } + + public func leaveRoom() async throws -> Bool { + self.disconnectRoom() + try await self.authManager.signOut() + return true + } + + public func startGame() async throws -> Bool { + let response: [String: Bool]? = try await self.sendRequest( + endpointPath: .gameStart, + requestBody: ["roomNumber": self.room.value?.number, "userId": self.myId] + ) + guard let response = response?["success"] as? Bool else { + throw ASNetworkErrors.responseError + } + return response + } + + public func changeMode(mode: ASEntity.Mode) async throws -> Bool { + let response: [String: Bool] = try await self.sendRequest( + endpointPath: .changeMode, + requestBody: ["roomNumber": self.room.value?.number, "userId": self.myId, "mode": mode.rawValue] + ) + guard let isSuccess = response["success"] as? Bool else { + throw ASNetworkErrors.responseError + } + return isSuccess + } + + public func changeRecordOrder() async throws -> Bool { + let response: [String: Bool] = try await self.sendRequest( + endpointPath: .changeRecordOrder, + requestBody: ["roomNumber": self.room.value?.number, "userId": self.myId] + ) + guard let isSuccess = response["success"] as? Bool else { + throw ASNetworkErrors.responseError + } + return isSuccess + } + + public func resetGame() async throws -> Bool { let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), - URLQueryItem(name: "roomNumber", value: room.value?.number)] - let endPoint = FirebaseEndpoint(path: .uploadRecording, method: .post) + URLQueryItem(name: "roomNumber", value: self.room.value?.number)] + let endPoint = FirebaseEndpoint(path: .resetGame, method: .post) .update(\.queryItems, with: queryItems) let response = try await networkManager.sendRequest( to: endPoint, - type: .multipart, - body: record, + type: .none, + body: nil, option: .none ) + let responseDict = try ASDecoder.decode([String: Bool].self, from: response) guard let success = responseDict["success"] else { return false } return success } + + public func submitMusic(answer: ASEntity.Music) async throws -> Bool { + let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), + URLQueryItem(name: "roomNumber", value: self.room.value?.number)] + let endPoint = FirebaseEndpoint(path: .submitMusic, method: .post) + .update(\.queryItems, with: queryItems) + + let body = try ASEncoder.encode(answer) + let response = try await networkManager.sendRequest(to: endPoint, type: .json, body: body, option: .none) + let responseDict = try ASDecoder.decode([String: String].self, from: response) + return !responseDict.isEmpty + } - public func postResetGame() async throws -> Bool { + public func getAvatarUrls() async throws -> [URL] { + try await self.storageManager.getAvatarUrls() + } + + public func getResource(url: URL) async throws -> Data { + do { + guard let endpoint = ResourceEndpoint(url: url) else { throw ASNetworkErrors.urlError } + let data = try await self.networkManager.sendRequest(to: endpoint, type: .json, body: nil, option: .both) + return data + } catch { + throw error + } + } + + public func submitAnswer(answer: ASEntity.Music) async throws -> Bool { let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), - URLQueryItem(name: "roomNumber", value: room.value?.number)] - let endPoint = FirebaseEndpoint(path: .resetGame, method: .post) + URLQueryItem(name: "roomNumber", value: self.room.value?.number)] + let endPoint = FirebaseEndpoint(path: .submitAnswer, method: .post) + .update(\.queryItems, with: queryItems) + .update(\.headers, with: ["Content-Type": "application/json"]) + + let body = try ASEncoder.encode(answer) + let response = try await networkManager.sendRequest(to: endPoint, type: .json, body: body, option: .none) + let responseDict = try ASDecoder.decode([String: String].self, from: response) + return !responseDict.isEmpty + } + + public func postRecording(_ record: Data) async throws -> Bool { + let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), + URLQueryItem(name: "roomNumber", value: self.room.value?.number)] + let endPoint = FirebaseEndpoint(path: .uploadRecording, method: .post) .update(\.queryItems, with: queryItems) let response = try await networkManager.sendRequest( to: endPoint, - type: .none, - body: nil, + type: .multipart, + body: record, option: .none ) - let responseDict = try ASDecoder.decode([String: Bool].self, from: response) guard let success = responseDict["success"] else { return false } return success } + + private func sendRequest(endpointPath: FirebaseEndpoint.Path, requestBody: [String: Any]) async throws -> T { + let endpoint = FirebaseEndpoint(path: endpointPath, method: .post) + let body = try JSONSerialization.data(withJSONObject: requestBody, options: []) + let data = try await networkManager.sendRequest(to: endpoint, type: .json, body: body, option: .none) + let response = try JSONDecoder().decode(T.self, from: data) + return response + } +} + + + + + +struct NetworkHelper { + let networkManager: ASNetworkManagerProtocol + + func sendRequest( + endpoint: FirebaseEndpoint, + requestBody: [String: Any]? = nil, + queryItems: [URLQueryItem]? = nil, + additionalHeaders: [String: String]? = nil, + contentType: HTTPContentType = .none + ) async throws -> T { + var updatedEndpoint = endpoint + + if let requestBody = requestBody { + let bodyData = try JSONSerialization.data(withJSONObject: requestBody, options: []) + updatedEndpoint = updatedEndpoint.update(\.body, with: bodyData) + } + + if let queryItems = queryItems { + updatedEndpoint = updatedEndpoint.update(\.queryItems, with: queryItems) + } + + let data = try await networkManager.sendRequest( + to: updatedEndpoint, + type: contentType, + body: updatedEndpoint.body, + option: .none + ) + + // 응답 디코딩 + let response = try JSONDecoder().decode(T.self, from: data) + return response + } +} + +protocol APIRequest { + associatedtype Response: Decodable + var endpoint: FirebaseEndpoint { get } + var requestBody: [String: Any]? { get } +} + +struct SuccessResponse: Decodable { + let success: Bool +} + +struct RoomResponse: Decodable { + let number: String +} + +struct CommonRequest: APIRequest { + + typealias Response = T + + let userId: String + let roomNumber: String + + var endpoint: FirebaseEndpoint + + var requestBody: [String: Any]? { + ["roomNumber": roomNumber, "userId": userId] + } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/MusicRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/MusicRepository.swift index 7592a0ca..d4f32a72 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/MusicRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/MusicRepository.swift @@ -1,28 +1,17 @@ -import ASNetworkKit +import ASRepositoryProtocol import Combine import Foundation -import ASRepositoryProtocol public final class MusicRepository: MusicRepositoryProtocol { - // TODO: - Container로 주입 - private let firebaseManager: ASFirebaseStorageProtocol - private let networkManager: ASNetworkManagerProtocol + private let mainRepository: MainRepositoryProtocol public init( - firebaseManager: ASFirebaseStorageProtocol, - networkManager: ASNetworkManagerProtocol + mainRepository: MainRepositoryProtocol ) { - self.firebaseManager = firebaseManager - self.networkManager = networkManager + self.mainRepository = mainRepository } - public func getMusicData(url: URL) async -> Data? { - do { - guard let endpoint = ResourceEndpoint(url: url) else { return nil } - let data = try await self.networkManager.sendRequest(to: endpoint, type: .json, body: nil, option: .both) - return data - } catch { - return nil - } + public func getMusicData(url: URL) async throws -> Data? { + try await mainRepository.getResource(url: url) } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/PlayersRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/PlayersRepository.swift index d40d789a..b669b866 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/PlayersRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/PlayersRepository.swift @@ -1,17 +1,13 @@ -import Foundation -import ASNetworkKit -import Combine import ASEntity import ASRepositoryProtocol +import Combine +import Foundation public final class PlayersRepository: PlayersRepositoryProtocol { private var mainRepository: MainRepositoryProtocol - private var firebaseAuthManager: ASFirebaseAuthProtocol - public init(mainRepository: MainRepositoryProtocol, - firebaseAuthManager: ASFirebaseAuthProtocol) { + public init(mainRepository: MainRepositoryProtocol) { self.mainRepository = mainRepository - self.firebaseAuthManager = firebaseAuthManager } public func getPlayers() -> AnyPublisher<[Player], Never> { @@ -23,8 +19,8 @@ public final class PlayersRepository: PlayersRepositoryProtocol { } public func getPlayersCount() -> AnyPublisher { - self.getPlayers() - .map { $0.count } + getPlayers() + .map(\.count) .eraseToAnyPublisher() } @@ -37,9 +33,8 @@ public final class PlayersRepository: PlayersRepositoryProtocol { } public func isHost() -> AnyPublisher { - self.getHost() - .map { $0.id == ASFirebaseAuth.myID } + getHost() + .map { $0.id == self.mainRepository.myId } .eraseToAnyPublisher() } } - diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/RecordsRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/RecordsRepository.swift index b129453d..b015c838 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/RecordsRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/RecordsRepository.swift @@ -1,8 +1,8 @@ import ASEntity -import Combine -import Foundation import ASLogKit import ASRepositoryProtocol +import Combine +import Foundation public final class RecordsRepository: RecordsRepositoryProtocol { private var mainRepository: MainRepositoryProtocol @@ -20,23 +20,20 @@ public final class RecordsRepository: RecordsRepositoryProtocol { } public func getRecordsCount(on recordOrder: UInt8) -> AnyPublisher { - return self.getRecords() + getRecords() .compactMap { $0 } .map { records in - return records.filter { Int($0.recordOrder ?? 0) == recordOrder } - } - .map { - return $0.count + records.filter { Int($0.recordOrder ?? 0) == recordOrder } } + .map(\.count) .eraseToAnyPublisher() } public func getHumming(on recordOrder: UInt8) -> AnyPublisher { - - return mainRepository.room + mainRepository.room .receive(on: DispatchQueue.main) .compactMap { room in - guard let room = room else { return nil } + guard let room else { return nil } return (room.records, room.players) } .map { [weak self] records, players -> ASEntity.Record? in @@ -51,12 +48,12 @@ public final class RecordsRepository: RecordsRepositoryProtocol { } public func uploadRecording(_ record: Data) async throws -> Bool { - return try await mainRepository.postRecording(record) + try await mainRepository.postRecording(record) } - + private func findRecord(records: [ASEntity.Record]?, - players: [Player]?, - recordOrder: UInt8) -> ASEntity.Record? + players: [Player]?, + recordOrder: UInt8) -> ASEntity.Record? { guard let records, let players, !players.isEmpty, @@ -78,10 +75,10 @@ public final class RecordsRepository: RecordsRepositoryProtocol { } private func findTargetOrder(for myOrder: Int, in playersCount: Int) -> Int { - return (myOrder - 1 + playersCount) % playersCount + (myOrder - 1 + playersCount) % playersCount } private func findHummings(for recordOrder: UInt8, in records: [ASEntity.Record]) -> [ASEntity.Record] { - return records.filter { $0.recordOrder == (recordOrder - 1) } + records.filter { $0.recordOrder == (recordOrder - 1) } } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/RoomActionRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/RoomActionRepository.swift index 49a33abe..d089cc04 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/RoomActionRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/RoomActionRepository.swift @@ -1,96 +1,42 @@ import ASEntity -import ASNetworkKit +import ASRepositoryProtocol import Combine import Foundation -import ASRepositoryProtocol public final class RoomActionRepository: RoomActionRepositoryProtocol { private let mainRepository: MainRepositoryProtocol - private let authManager: ASFirebaseAuthProtocol - private let networkManager: ASNetworkManagerProtocol public init( - mainRepository: MainRepositoryProtocol, - authManager: ASFirebaseAuthProtocol, - networkManager: ASNetworkManagerProtocol + mainRepository: MainRepositoryProtocol ) { self.mainRepository = mainRepository - self.authManager = authManager - self.networkManager = networkManager } public func createRoom(nickname: String, avatar: URL) async throws -> String { - try await self.authManager.signIn(nickname: nickname, avatarURL: avatar) - let response: [String: String]? = try await self.sendRequest( - endpointPath: .createRoom, - requestBody: ["hostID": ASFirebaseAuth.myID] - ) - guard let roomNumber = response?["number"] as? String else { - throw ASNetworkErrors.responseError - } - return roomNumber + try await mainRepository.createRoom(nickname: nickname, avatar: avatar) } public func joinRoom(nickname: String, avatar: URL, roomNumber: String) async throws -> Bool { - let player = try await self.authManager.signIn(nickname: nickname, avatarURL: avatar) - let response: [String: String]? = try await self.sendRequest( - endpointPath: .joinRoom, - requestBody: ["roomNumber": roomNumber, "userId": ASFirebaseAuth.myID] - ) - guard let roomNumberResponse = response?["number"] as? String else { - throw ASNetworkErrors.responseError - } - return roomNumberResponse == roomNumber + try await mainRepository.joinRoom(nickname: nickname, avatar: avatar, roomNumber: roomNumber) } public func leaveRoom() async throws -> Bool { - self.mainRepository.disconnectRoom() - try await self.authManager.signOut() - return true + try await mainRepository.leaveRoom() } - public func startGame(roomNumber: String) async throws -> Bool { - let response: [String: Bool]? = try await self.sendRequest( - endpointPath: .gameStart, - requestBody: ["roomNumber": roomNumber, "userId": ASFirebaseAuth.myID] - ) - guard let response = response?["success"] as? Bool else { - throw ASNetworkErrors.responseError - } - return response + public func startGame() async throws -> Bool { + try await mainRepository.startGame() } - public func changeMode(roomNumber: String, mode: Mode) async throws -> Bool { - let response: [String: Bool] = try await self.sendRequest( - endpointPath: .changeMode, - requestBody: ["roomNumber": roomNumber, "userId": ASFirebaseAuth.myID, "mode": mode.rawValue] - ) - guard let isSuccess = response["success"] as? Bool else { - throw ASNetworkErrors.responseError - } - return isSuccess + public func changeMode(mode: Mode) async throws -> Bool { + try await mainRepository.changeMode(mode: mode) } - public func changeRecordOrder(roomNumber: String) async throws -> Bool { - let response: [String: Bool] = try await self.sendRequest( - endpointPath: .changeRecordOrder, - requestBody: ["roomNumber": roomNumber, "userId": ASFirebaseAuth.myID] - ) - guard let isSuccess = response["success"] as? Bool else { - throw ASNetworkErrors.responseError - } - return isSuccess + public func changeRecordOrder() async throws -> Bool { + try await mainRepository.changeRecordOrder() } public func resetGame() async throws -> Bool { - return try await mainRepository.postResetGame() - } - - private func sendRequest(endpointPath: FirebaseEndpoint.Path, requestBody: [String: Any]) async throws -> T { - let endpoint = FirebaseEndpoint(path: endpointPath, method: .post) - let body = try JSONSerialization.data(withJSONObject: requestBody, options: []) - let data = try await networkManager.sendRequest(to: endpoint, type: .json, body: body, option: .none) - let response = try JSONDecoder().decode(T.self, from: data) - return response + try await mainRepository.resetGame() } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/RoomInfoRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/RoomInfoRepository.swift index 19ed0012..b01d9a99 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/RoomInfoRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/RoomInfoRepository.swift @@ -1,7 +1,7 @@ -import Foundation -import Combine import ASEntity import ASRepositoryProtocol +import Combine +import Foundation public final class RoomInfoRepository: RoomInfoRepositoryProtocol { private var mainRepository: MainRepositoryProtocol diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/SelectedRecordsRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/SelectedRecordsRepository.swift index 822b2f8b..261ecb12 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/SelectedRecordsRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/SelectedRecordsRepository.swift @@ -1,15 +1,15 @@ -import Foundation -import Combine import ASEntity import ASRepositoryProtocol +import Combine +import Foundation public final class SelectedRecordsRepository: SelectedRecordsRepositoryProtocol { private var mainRepository: MainRepositoryProtocol - + public init(mainRepository: MainRepositoryProtocol) { self.mainRepository = mainRepository } - + public func getSelectedRecords() -> AnyPublisher<[UInt8], Never> { mainRepository.room .receive(on: DispatchQueue.main) @@ -18,4 +18,3 @@ public final class SelectedRecordsRepository: SelectedRecordsRepositoryProtocol .eraseToAnyPublisher() } } - diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/SubmitsRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/SubmitsRepository.swift index b3ae3992..78e22f40 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/SubmitsRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/SubmitsRepository.swift @@ -1,18 +1,13 @@ -import ASDecoder -import ASEncoder import ASEntity -import ASNetworkKit +import ASRepositoryProtocol import Combine import Foundation -import ASRepositoryProtocol public final class SubmitsRepository: SubmitsRepositoryProtocol { private var mainRepository: MainRepositoryProtocol - private var networkManager: ASNetworkManagerProtocol - public init(mainRepository: MainRepositoryProtocol, networkManager: ASNetworkManagerProtocol) { + public init(mainRepository: MainRepositoryProtocol) { self.mainRepository = mainRepository - self.networkManager = networkManager } public func getSubmits() -> AnyPublisher<[Answer], Never> { @@ -22,23 +17,14 @@ public final class SubmitsRepository: SubmitsRepositoryProtocol { .removeDuplicates() .eraseToAnyPublisher() } - + public func getSubmitsCount() -> AnyPublisher { - self.getSubmits() - .map { $0.count } + getSubmits() + .map(\.count) .eraseToAnyPublisher() } - - public func submitAnswer(answer: Music) async throws -> Bool { - let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), - URLQueryItem(name: "roomNumber", value: mainRepository.room.value?.number)] - let endPoint = FirebaseEndpoint(path: .submitAnswer, method: .post) - .update(\.queryItems, with: queryItems) - .update(\.headers, with: ["Content-Type": "application/json"]) - let body = try ASEncoder.encode(answer) - let response = try await networkManager.sendRequest(to: endPoint, type: .json, body: body, option: .none) - let responseDict = try ASDecoder.decode([String: String].self, from: response) - return !responseDict.isEmpty + public func submitAnswer(answer: Music) async throws -> Bool { + try await mainRepository.submitMusic(answer: answer) } } diff --git a/alsongDalsong/ASRepository/ASRepository/RepsotioryAssembly.swift b/alsongDalsong/ASRepository/ASRepository/RepsotioryAssembly.swift index 8436cf5a..d2c7ce9d 100644 --- a/alsongDalsong/ASRepository/ASRepository/RepsotioryAssembly.swift +++ b/alsongDalsong/ASRepository/ASRepository/RepsotioryAssembly.swift @@ -8,28 +8,28 @@ public struct RepsotioryAssembly: Assembly { public func assemble(container: Registerable) { container.registerSingleton(MainRepositoryProtocol.self) { r in let databaseManager = r.resolve(ASFirebaseDatabaseProtocol.self) + let authManager = r.resolve(ASFirebaseAuthProtocol.self) let networkManager = r.resolve(ASNetworkManagerProtocol.self) + let storageManager = r.resolve(ASFirebaseStorageProtocol.self) return MainRepository( databaseManager: databaseManager, + authManager: authManager, + storageManager: storageManager, networkManager: networkManager ) } container.register(AnswersRepositoryProtocol.self) { r in let mainRepository = r.resolve(MainRepositoryProtocol.self) - let networkManager = r.resolve(ASNetworkManagerProtocol.self) return AnswersRepository( - mainRepository: mainRepository, - networkManager: networkManager + mainRepository: mainRepository ) } container.register(AvatarRepositoryProtocol.self) { r in - let storageManager = r.resolve(ASFirebaseStorageProtocol.self) - let networkManager = r.resolve(ASNetworkManagerProtocol.self) + let mainRepository = r.resolve(MainRepositoryProtocol.self) return AvatarRepository( - storageManager: storageManager, - networkManager: networkManager + mainRepository: mainRepository ) } @@ -41,20 +41,15 @@ public struct RepsotioryAssembly: Assembly { } container.register(MusicRepositoryProtocol.self) { r in - let firebaseManager = r.resolve(ASFirebaseStorageProtocol.self) - let networkManager = r.resolve(ASNetworkManagerProtocol.self) - return MusicRepository( - firebaseManager: firebaseManager, - networkManager: networkManager + let mainRepository = r.resolve(MainRepositoryProtocol.self) + return MusicRepository(mainRepository: mainRepository ) } container.register(PlayersRepositoryProtocol.self) { r in let mainRepository = r.resolve(MainRepositoryProtocol.self) - let firebaseAuthManager = r.resolve(ASFirebaseAuthProtocol.self) return PlayersRepository( - mainRepository: mainRepository, - firebaseAuthManager: firebaseAuthManager + mainRepository: mainRepository ) } @@ -67,12 +62,8 @@ public struct RepsotioryAssembly: Assembly { container.register(RoomActionRepositoryProtocol.self) { r in let mainRepository = r.resolve(MainRepositoryProtocol.self) - let authManager = r.resolve(ASFirebaseAuthProtocol.self) - let networkManager = r.resolve(ASNetworkManagerProtocol.self) return RoomActionRepository( - mainRepository: mainRepository, - authManager: authManager, - networkManager: networkManager + mainRepository: mainRepository ) } @@ -92,10 +83,8 @@ public struct RepsotioryAssembly: Assembly { container.register(SubmitsRepositoryProtocol.self) { r in let mainRepository = r.resolve(MainRepositoryProtocol.self) - let networkManager = r.resolve(ASNetworkManagerProtocol.self) return SubmitsRepository( - mainRepository: mainRepository, - networkManager: networkManager + mainRepository: mainRepository ) } @@ -106,11 +95,7 @@ public struct RepsotioryAssembly: Assembly { container.register(HummingResultRepositoryProtocol.self) { r in let mainRepository = r.resolve(MainRepositoryProtocol.self) - let storageManager = r.resolve(ASFirebaseStorageProtocol.self) - let networkManager = r.resolve(ASNetworkManagerProtocol.self) return HummingResultRepository( - storageManager: storageManager, - networkManager: networkManager, mainRepository: mainRepository ) } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift index 0754bcae..373ea88a 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift @@ -1,8 +1,8 @@ import ASEntity +import ASLogKit import ASRepositoryProtocol import Combine import Foundation -import ASLogKit final class LobbyViewModel: ObservableObject, @unchecked Sendable { private var playersRepository: PlayersRepositoryProtocol @@ -96,7 +96,7 @@ final class LobbyViewModel: ObservableObject, @unchecked Sendable { func gameStart() async throws { do { - _ = try await roomActionRepository.startGame(roomNumber: roomNumber) + _ = try await roomActionRepository.startGame() } catch { throw error } @@ -116,7 +116,7 @@ final class LobbyViewModel: ObservableObject, @unchecked Sendable { Task { do { if isHost { - _ = try await self.roomActionRepository.changeMode(roomNumber: roomNumber, mode: mode) + _ = try await self.roomActionRepository.changeMode(mode: mode) } } catch { Logger.error(error.localizedDescription) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/MusicPanel/MusicPanelViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/MusicPanel/MusicPanelViewModel.swift index 06fb04ca..7df6edc0 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/MusicPanel/MusicPanelViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/MusicPanel/MusicPanelViewModel.swift @@ -74,14 +74,14 @@ final class MusicPanelViewModel: @unchecked Sendable { private func getPreviewData() { guard let previewUrl = music?.previewUrl else { return } Task { @MainActor in - preview = await musicRepository?.getMusicData(url: previewUrl) + preview = try await musicRepository?.getMusicData(url: previewUrl) } } private func getArtworkData() { guard let artworkUrl = music?.artworkUrl else { return } Task { @MainActor in - artwork = await musicRepository?.getMusicData(url: artworkUrl) + artwork = try await musicRepository?.getMusicData(url: artworkUrl) } } } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewModel.swift index e6aed35b..3fcbee8c 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewModel.swift @@ -110,7 +110,7 @@ final class HummingResultViewModel: @unchecked Sendable { currentRecords.append(recordsResult.removeFirst()) guard let fileUrl = currentRecords.last?.fileUrl else { continue } do { - let data = try await fetchRecordData(url: fileUrl) + let data = try await getRecordData(url: fileUrl) await AudioHelper.shared.startPlaying(data) await waitForPlaybackToFinish() } catch { @@ -142,29 +142,12 @@ final class HummingResultViewModel: @unchecked Sendable { currentsubmit = nil } - private func getRecordData(url: URL?) -> AnyPublisher { - if let url { - hummingResultRepository.getRecordData(url: url) - .eraseToAnyPublisher() - } else { - Just(nil) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } - } - - private func fetchRecordData(url: URL) async throws -> Data { - try await withCheckedThrowingContinuation { continuation in - getRecordData(url: url) - .compactMap { $0 } - .sink(receiveCompletion: { completion in - if case .failure(let error) = completion { - continuation.resume(throwing: error) - } - }, receiveValue: { data in - continuation.resume(returning: data) - }) - .store(in: &cancellables) + private func getRecordData(url: URL?) async throws -> Data? { + guard let url else { return nil } + do { + return try await hummingResultRepository.getRecordData(url: url) + } catch { + return nil } } @@ -175,7 +158,7 @@ final class HummingResultViewModel: @unchecked Sendable { func changeRecordOrder() async throws { do { - try await roomActionRepository.changeRecordOrder(roomNumber: roomNumber) + try await roomActionRepository.changeRecordOrder() } catch { Logger.error(error.localizedDescription) throw error @@ -193,7 +176,11 @@ final class HummingResultViewModel: @unchecked Sendable { func getArtworkData(url: URL?) async -> Data? { guard let url else { return nil } - return await musicRepository.getMusicData(url: url) + do { + return try await musicRepository.getMusicData(url: url) + } catch { + return nil + } } public func cancelSubscriptions() { diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift index 54af7229..a6b3de07 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift @@ -88,7 +88,14 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { public func downloadArtwork(url: URL?) async -> Data? { guard let url else { return nil } - return await musicRepository.getMusicData(url: url) + do { + let musicData = try await musicRepository.getMusicData(url: url) + self.musicData = musicData + } + catch { + return nil + } + return nil } public func handleSelectedSong(with music: Music) { @@ -107,19 +114,15 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { public func searchMusic(text: String) async throws { do { if text.isEmpty { return } - let searchList = try await musicAPI.search(for: text) - await updateSearchList(with: searchList) + searchList = try await musicAPI.search(for: text) } catch { throw error } } public func downloadMusic(url: URL) { - Task { - guard let musicData = await musicRepository.getMusicData(url: url) else { - return - } - await updateMusicData(with: musicData) + Task { @MainActor in + musicData = try await musicRepository.getMusicData(url: url) } } @@ -128,21 +131,10 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { downloadMusic(url: url) } - @MainActor public func resetSearchList() { searchList = [] } - @MainActor - private func updateMusicData(with musicData: Data) { - self.musicData = musicData - } - - @MainActor - private func updateSearchList(with searchList: [Music]) { - self.searchList = searchList - } - public func cancelSubscriptions() { cancellables.removeAll() } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift index 031ad099..a284644a 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift @@ -104,12 +104,16 @@ final class SubmitAnswerViewModel: ObservableObject, @unchecked Sendable { public func downloadArtwork(url: URL?) async -> Data? { guard let url else { return nil } - return await musicRepository.getMusicData(url: url) + do { + return try await musicRepository.getMusicData(url: url) + } catch { + return nil + } } public func downloadMusic(url: URL) { Task { - guard let musicData = await musicRepository.getMusicData(url: url) else { + guard let musicData = try await musicRepository.getMusicData(url: url) else { return } await updateMusicData(with: musicData) diff --git a/firebase/functions/api/JoinRoom.js b/firebase/functions/api/JoinRoom.js index f0e28391..2d70357f 100644 --- a/firebase/functions/api/JoinRoom.js +++ b/firebase/functions/api/JoinRoom.js @@ -35,6 +35,7 @@ module.exports.joinRoom = onRequest({ region: 'asia-southeast1' }, async (req, r if (inGame) { return res.status(452).json({ error: 'Game has already started in this room' }); + } if (playerExists) { return res.status(400).json({ error: 'User already in the room' }); From 57be0b6179499ab9af71252f24ef831253e904c3 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:24:17 +0900 Subject: [PATCH 05/20] =?UTF-8?q?fix:=20=EC=9E=84=EC=8B=9C=20network=20hel?= =?UTF-8?q?per=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Repositories/MainRepository.swift | 69 +------------------ 1 file changed, 1 insertion(+), 68 deletions(-) diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift index 8e3cf6d1..7b7bf572 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift @@ -15,7 +15,6 @@ public final class MainRepository: MainRepositoryProtocol { private let authManager: ASFirebaseAuthProtocol private let storageManager: ASFirebaseStorageProtocol private let networkManager: ASNetworkManagerProtocol - private let networkHelper: NetworkHelper private var cancellables: Set = [] public init(databaseManager: ASFirebaseDatabaseProtocol, @@ -26,7 +25,7 @@ public final class MainRepository: MainRepositoryProtocol { self.databaseManager = databaseManager self.authManager = authManager self.storageManager = storageManager - self.networkHelper = NetworkHelper(networkManager: networkManager) + self.networkManager = networkManager } public func connectRoom(roomNumber: String) { @@ -197,69 +196,3 @@ public final class MainRepository: MainRepositoryProtocol { return response } } - - - - - -struct NetworkHelper { - let networkManager: ASNetworkManagerProtocol - - func sendRequest( - endpoint: FirebaseEndpoint, - requestBody: [String: Any]? = nil, - queryItems: [URLQueryItem]? = nil, - additionalHeaders: [String: String]? = nil, - contentType: HTTPContentType = .none - ) async throws -> T { - var updatedEndpoint = endpoint - - if let requestBody = requestBody { - let bodyData = try JSONSerialization.data(withJSONObject: requestBody, options: []) - updatedEndpoint = updatedEndpoint.update(\.body, with: bodyData) - } - - if let queryItems = queryItems { - updatedEndpoint = updatedEndpoint.update(\.queryItems, with: queryItems) - } - - let data = try await networkManager.sendRequest( - to: updatedEndpoint, - type: contentType, - body: updatedEndpoint.body, - option: .none - ) - - // 응답 디코딩 - let response = try JSONDecoder().decode(T.self, from: data) - return response - } -} - -protocol APIRequest { - associatedtype Response: Decodable - var endpoint: FirebaseEndpoint { get } - var requestBody: [String: Any]? { get } -} - -struct SuccessResponse: Decodable { - let success: Bool -} - -struct RoomResponse: Decodable { - let number: String -} - -struct CommonRequest: APIRequest { - - typealias Response = T - - let userId: String - let roomNumber: String - - var endpoint: FirebaseEndpoint - - var requestBody: [String: Any]? { - ["roomNumber": roomNumber, "userId": userId] - } -} From 7ede47ca3be7a70336a99cc65c3ac83a1817ef96 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:05:29 +0900 Subject: [PATCH 06/20] =?UTF-8?q?chore:=20myID=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ASNetworkKit/FirebaseEndpoint.swift | 11 --------- .../Repositories/MainRepository.swift | 24 +++++++++---------- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift b/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift index abe35869..b47baa86 100644 --- a/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift +++ b/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift @@ -69,15 +69,4 @@ public extension FirebaseEndpoint { Self(path: .auth, method: .get) .update(\.queryItems, with: [.init(name: "listAvatarUrls", value: "true")]) } - - func withCommonQueryItems(roomNumber: String?, userID: String?) -> Self { - var items = [URLQueryItem]() - if let userID { - items.append(URLQueryItem(name: "userId", value: userID)) - } - if let roomNumber { - items.append(URLQueryItem(name: "roomNumber", value: roomNumber)) - } - return self.update(\.queryItems, with: items) - } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift index dbddf6f8..3db1159e 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift @@ -55,7 +55,7 @@ public final class MainRepository: MainRepositoryProtocol { try await self.authManager.signIn(nickname: nickname, avatarURL: avatar) let response: [String: String]? = try await self.sendRequest( endpointPath: .createRoom, - requestBody: ["hostID": ASFirebaseAuth.myID] + requestBody: ["hostID": myId] ) guard let roomNumber = response?["number"] as? String else { throw ASNetworkErrors.responseError @@ -65,11 +65,11 @@ public final class MainRepository: MainRepositoryProtocol { public func joinRoom(nickname: String, avatar: URL, roomNumber: String) async throws -> Bool { let player = try await self.authManager.signIn(nickname: nickname, avatarURL: avatar) - let response: [String: String]? = try await self.sendRequest( + let response: [String: String] = try await self.sendRequest( endpointPath: .joinRoom, - requestBody: ["roomNumber": roomNumber, "userId": ASFirebaseAuth.myID] + requestBody: ["roomNumber": roomNumber, "userId": myId] ) - guard let roomNumberResponse = response?["number"] as? String else { + guard let roomNumberResponse = response["number"] else { throw ASNetworkErrors.responseError } return roomNumberResponse == roomNumber @@ -82,11 +82,11 @@ public final class MainRepository: MainRepositoryProtocol { } public func startGame() async throws -> Bool { - let response: [String: Bool]? = try await self.sendRequest( + let response: [String: Bool] = try await self.sendRequest( endpointPath: .gameStart, requestBody: ["roomNumber": self.room.value?.number, "userId": self.myId] ) - guard let response = response?["success"] as? Bool else { + guard let response = response["success"] else { throw ASNetworkErrors.responseError } return response @@ -97,7 +97,7 @@ public final class MainRepository: MainRepositoryProtocol { endpointPath: .changeMode, requestBody: ["roomNumber": self.room.value?.number, "userId": self.myId, "mode": mode.rawValue] ) - guard let isSuccess = response["success"] as? Bool else { + guard let isSuccess = response["success"] else { throw ASNetworkErrors.responseError } return isSuccess @@ -108,14 +108,14 @@ public final class MainRepository: MainRepositoryProtocol { endpointPath: .changeRecordOrder, requestBody: ["roomNumber": self.room.value?.number, "userId": self.myId] ) - guard let isSuccess = response["success"] as? Bool else { + guard let isSuccess = response["success"] else { throw ASNetworkErrors.responseError } return isSuccess } public func resetGame() async throws -> Bool { - let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), + let queryItems = [URLQueryItem(name: "userId", value: myId), URLQueryItem(name: "roomNumber", value: self.room.value?.number)] let endPoint = FirebaseEndpoint(path: .resetGame, method: .post) .update(\.queryItems, with: queryItems) @@ -133,7 +133,7 @@ public final class MainRepository: MainRepositoryProtocol { } public func submitMusic(answer: ASEntity.Music) async throws -> Bool { - let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), + let queryItems = [URLQueryItem(name: "userId", value: myId), URLQueryItem(name: "roomNumber", value: self.room.value?.number)] let endPoint = FirebaseEndpoint(path: .submitMusic, method: .post) .update(\.queryItems, with: queryItems) @@ -159,7 +159,7 @@ public final class MainRepository: MainRepositoryProtocol { } public func submitAnswer(answer: ASEntity.Music) async throws -> Bool { - let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), + let queryItems = [URLQueryItem(name: "userId", value: myId), URLQueryItem(name: "roomNumber", value: self.room.value?.number)] let endPoint = FirebaseEndpoint(path: .submitAnswer, method: .post) .update(\.queryItems, with: queryItems) @@ -172,7 +172,7 @@ public final class MainRepository: MainRepositoryProtocol { } public func postRecording(_ record: Data) async throws -> Bool { - let queryItems = [URLQueryItem(name: "userId", value: ASFirebaseAuth.myID), + let queryItems = [URLQueryItem(name: "userId", value: myId), URLQueryItem(name: "roomNumber", value: self.room.value?.number)] let endPoint = FirebaseEndpoint(path: .uploadRecording, method: .post) .update(\.queryItems, with: queryItems) From 82d36d68923733b8c30292b2b34da13feff4fec0 Mon Sep 17 00:00:00 2001 From: moral-life Date: Tue, 3 Dec 2024 00:14:11 +0900 Subject: [PATCH 07/20] =?UTF-8?q?feat:=20ASMusicKit=EC=97=90=20=20randomSo?= =?UTF-8?q?ng()=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80,=20search=20@Mai?= =?UTF-8?q?nActor=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ASMusicKit/ASMusicKit/ASMusicAPI.swift | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/alsongDalsong/ASMusicKit/ASMusicKit/ASMusicAPI.swift b/alsongDalsong/ASMusicKit/ASMusicKit/ASMusicAPI.swift index ab64d0ed..c2be1790 100644 --- a/alsongDalsong/ASMusicKit/ASMusicKit/ASMusicAPI.swift +++ b/alsongDalsong/ASMusicKit/ASMusicKit/ASMusicAPI.swift @@ -4,12 +4,12 @@ import MusicKit public struct ASMusicAPI { public init() {} + /// MusicKit을 통해 Apple Music의 노래를 검색합니다. /// - Parameters: /// - text: 검색 요청을 보낼 검색어 /// - maxCount: 검색해서 찾아올 음악의 갯수 기본값 설정은 25 /// - Returns: Music의 배열 - @MainActor public func search(for text: String, _ maxCount: Int = 10, _ offset: Int = 0) async throws -> [Music] { let status = await MusicAuthorization.request() switch status { @@ -61,11 +61,45 @@ public struct ASMusicAPI { throw ASMusicError.notAuthorized } } + + public func randomSong(from playlistId: String) async throws -> ASEntity.Music { + let status = await MusicAuthorization.request() + switch status { + case .authorized: + do { + let request = MusicCatalogResourceRequest(matching: \.id, equalTo: MusicItemID(rawValue: playlistId)) + let playlistResponse = try await request.response() + let playlist = playlistResponse.items.first! + + let playlistWithTrack = try await playlist.with([.tracks]) + guard let tracks = playlistWithTrack.tracks else { + throw ASMusicError.playListHasNoSongs + } + + if let song = tracks.randomElement() { + return ASEntity.Music( + id: song.id.rawValue, + title: song.title, + artist: song.artistName, + artworkUrl: song.artwork?.url(width: 300, height: 300), + previewUrl: song.previewAssets?.first?.url, + artworkBackgroundColor: song.artwork?.backgroundColor?.toHex() + ) + } + } catch { + throw ASMusicError.playListHasNoSongs + } + default: + throw ASMusicError.notAuthorized + } + return ASEntity.Music(id: "nil", title: nil, artist: nil, artworkUrl: nil, previewUrl: nil, artworkBackgroundColor: nil) + } } public enum ASMusicError: Error, LocalizedError { case notAuthorized case searchError + case playListHasNoSongs public var errorDescription: String? { switch self { @@ -73,6 +107,8 @@ public enum ASMusicError: Error, LocalizedError { "애플 뮤직에 접근하는 권한이 없습니다." case .searchError: "노래 검색 중 오류가 발생했습니다." + case .playListHasNoSongs: + "플레이리스트에 노래가 없습니다." } } } From 90036b9c86105fe5a7f5ac216bc0902ffbb1c61d Mon Sep 17 00:00:00 2001 From: moral-life Date: Tue, 3 Dec 2024 00:14:45 +0900 Subject: [PATCH 08/20] =?UTF-8?q?feat:=20SelectMusic=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=EC=84=9C,=20=ED=83=80=EC=9E=84=20=EC=95=84=EC=9B=83?= =?UTF-8?q?=20=EB=90=A0=20=EB=95=8C=EA=B9=8C=EC=A7=80=20=EC=95=84=EB=AC=B4?= =?UTF-8?q?=20=EB=85=B8=EB=9E=98=EB=8F=84=20=EA=B3=A0=EB=A5=B4=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9C=BC=EB=A9=B4=20=EB=9E=9C=EB=8D=A4=20=EA=B3=A1=20?= =?UTF-8?q?=EC=A0=9C=EC=B6=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Views/SelectMusic/SelectMusicViewController.swift | 9 +++++++++ .../Views/SelectMusic/SelectMusicViewModel.swift | 10 +++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewController.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewController.swift index 095f23ea..f9aa190c 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewController.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewController.swift @@ -86,6 +86,14 @@ class SelectMusicViewController: UIViewController { } } + private func pickRandomMusic() async throws { + do { + try await viewModel.randomMusic() + } catch { + throw error + } + } + private func submitMusic() async throws { do { viewModel.stopMusic() @@ -105,6 +113,7 @@ extension SelectMusicViewController { let alert = LoadingAlertController( progressText: .submitMusic, loadAction: { [weak self] in + try await self?.pickRandomMusic() try await self?.submitMusic() }, errorCompletion: { [weak self] error in diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift index eed64ed8..53ad3097 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift @@ -50,7 +50,7 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { } .store(in: &cancellables) } - + private func bindGameStatus() { gameStatusRepository.getDueTime() .receive(on: DispatchQueue.main) @@ -119,6 +119,14 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { } } + public func randomMusic() async throws { + do { + selectedMusic = try await musicAPI.randomSong(from: "pl.u-aZb00o7uPlzMZzr") + } catch { + throw error + } + } + public func downloadMusic(url: URL) { Task { guard let musicData = try await musicRepository.getMusicData(url: url) else { From 37d7f55e6995e62f10b0acdb168e0b283db25716 Mon Sep 17 00:00:00 2001 From: moral-life Date: Tue, 3 Dec 2024 00:35:01 +0900 Subject: [PATCH 09/20] =?UTF-8?q?fix:=20=EC=B9=98=EB=AA=85=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 모든 제출이 랜덤 제출로 될 뻔 했음. -> 음악을 한번이라도 선택한 적이 있는지, 없는지 분기 하여 있으면 마지막 선택을 제출, 없으면 랜덤 제출 하도록 수정 --- .../SelectMusic/SelectMusicViewController.swift | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewController.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewController.swift index f9aa190c..4e9607ab 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewController.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewController.swift @@ -82,6 +82,10 @@ class SelectMusicViewController: UIViewController { }, for: .touchUpInside) progressBar.setCompletionHandler { [weak self] in + guard let selectedMusic = self?.viewModel.selectedMusic else { + self?.showSubmitRandomMusicLoading() + return + } self?.showSubmitMusicLoading() } } @@ -110,6 +114,18 @@ class SelectMusicViewController: UIViewController { extension SelectMusicViewController { private func showSubmitMusicLoading() { + let alert = LoadingAlertController( + progressText: .submitMusic, + loadAction: { [weak self] in + try await self?.submitMusic() + }, + errorCompletion: { [weak self] error in + self?.showFailSubmitMusic(error) + }) + presentAlert(alert) + } + + private func showSubmitRandomMusicLoading() { let alert = LoadingAlertController( progressText: .submitMusic, loadAction: { [weak self] in From 815e159ae2866b2d3577a33601bd6d49cc09c542 Mon Sep 17 00:00:00 2001 From: moral-life Date: Mon, 2 Dec 2024 20:10:06 +0900 Subject: [PATCH 10/20] =?UTF-8?q?feat:=20onboarding=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20=ED=82=A4=EB=B3=B4=EB=93=9C=20=EC=98=A4=EB=A5=98=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UIViewController+Keyboard.swift | 38 +--------------- .../Onboarding/OnboardingViewController.swift | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Extensions/UIViewController+Keyboard.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Extensions/UIViewController+Keyboard.swift index 5d4a3952..706c8520 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Extensions/UIViewController+Keyboard.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Extensions/UIViewController+Keyboard.swift @@ -1,39 +1,3 @@ import UIKit -extension UIViewController { - func hideKeyboard() { - view.addGestureRecognizer( - UITapGestureRecognizer( - target: self, - action: #selector( - UIViewController.dismissKeyboard - ) - ) - ) - } - - @objc func dismissKeyboard() { - view.endEditing(true) - } - - @objc func keyboardWillShow(_ notification: NSNotification) { - if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue { - let keyboardRectangle = keyboardFrame.cgRectValue - let keyboardHeight = keyboardRectangle.height - - UIView.animate(withDuration: 0.3) { - self.view.transform = CGAffineTransform(translationX: 0, y: -keyboardHeight) - } - } - } - - @objc func keyboardWillHide(_ notification: NSNotification) { - UIView.animate(withDuration: 0.3) { - self.view.transform = .identity - } - } - - @objc func appDidEnterBackground(_ notification: NSNotification) { - view.endEditing(true) - } -} +extension UIViewController {} diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Onboarding/OnboardingViewController.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Onboarding/OnboardingViewController.swift index 4ac01865..f3ad4e31 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Onboarding/OnboardingViewController.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Onboarding/OnboardingViewController.swift @@ -14,6 +14,7 @@ final class OnboardingViewController: UIViewController { private var inviteCode: String private var gameNavigationController: GameNavigationController? private var cancellables = Set() + var shouldMoveKeyboard: Bool = true init(viewmodel: OnboardingViewModel, inviteCode: String) { viewModel = viewmodel @@ -45,6 +46,7 @@ final class OnboardingViewController: UIViewController { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + view.endEditing(true) NotificationCenter.default.removeObserver(self) } @@ -227,13 +229,18 @@ extension OnboardingViewController { extension OnboardingViewController { private func showRoomNumerInputAlert() { + shouldMoveKeyboard = false let alert = InputAlertController( titleText: .joinRoom, textFieldPlaceholder: .roomNumber, isUppercased: true ) { [weak self] roomNumber in self?.setNicknameAndJoinRoom(with: roomNumber) + self?.shouldMoveKeyboard = true + } secondaryButtonAction: { + self.shouldMoveKeyboard = true } + presentAlert(alert) } @@ -279,4 +286,41 @@ private extension OnboardingViewController { object: nil ) } + + func hideKeyboard() { + view.addGestureRecognizer( + UITapGestureRecognizer( + target: self, + action: #selector( + OnboardingViewController.dismissKeyboard + ) + ) + ) + } + + @objc func dismissKeyboard() { + view.endEditing(true) + } + + @objc func keyboardWillShow(_ notification: NSNotification) { + guard shouldMoveKeyboard else { return } + if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue { + let keyboardRectangle = keyboardFrame.cgRectValue + let keyboardHeight = keyboardRectangle.height + + UIView.animate(withDuration: 0.3) { + self.view.transform = CGAffineTransform(translationX: 0, y: -keyboardHeight) + } + } + } + + @objc func keyboardWillHide(_ notification: NSNotification) { + UIView.animate(withDuration: 0.3) { + self.view.transform = .identity + } + } + + @objc func appDidEnterBackground(_ notification: NSNotification) { + view.endEditing(true) + } } From a78b90994bca448e9f0bb3c98d4010e620704d38 Mon Sep 17 00:00:00 2001 From: moral-life Date: Tue, 3 Dec 2024 00:41:37 +0900 Subject: [PATCH 11/20] =?UTF-8?q?chore:=20UIViewController+Keyboard=20?= =?UTF-8?q?=EC=97=86=EC=95=A0=EA=B3=A0=20OnboardingViewController=20extens?= =?UTF-8?q?ion=EC=9C=BC=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Extensions/UIViewController+Keyboard.swift | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 alsongDalsong/alsongDalsong/alsongDalsong/Sources/Extensions/UIViewController+Keyboard.swift diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Extensions/UIViewController+Keyboard.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Extensions/UIViewController+Keyboard.swift deleted file mode 100644 index 706c8520..00000000 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Extensions/UIViewController+Keyboard.swift +++ /dev/null @@ -1,3 +0,0 @@ -import UIKit - -extension UIViewController {} From 0cfda736f8cb4beba64051c1bbcc8efad6f1ca20 Mon Sep 17 00:00:00 2001 From: moral-life Date: Tue, 3 Dec 2024 16:25:01 +0900 Subject: [PATCH 12/20] =?UTF-8?q?chore:=20=EB=8B=A8=EC=88=9C=20print=20Log?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift index 373ea88a..18d50e35 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Lobby/LobbyViewModel.swift @@ -89,7 +89,6 @@ final class LobbyViewModel: ObservableObject, @unchecked Sendable { .receive(on: DispatchQueue.main) .sink { [weak self] isHost, playerCount in self?.canBeginGame = isHost && playerCount > 1 - Logger.debug("현재 canBeginGame: \(self?.canBeginGame)") } .store(in: &cancellables) } From e33ec1501fdb3b5c2fe5214b38d38305380178e0 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:45:36 +0900 Subject: [PATCH 13/20] =?UTF-8?q?fix:=20=EB=85=B8=EB=9E=98=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=ED=95=A0=20=EB=95=8C=20=EC=A4=91=EC=A7=80=EA=B0=80=20?= =?UTF-8?q?=EC=95=84=EB=8B=88=EB=9D=BC=20=EC=A0=95=EC=A7=80=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Views/SelectMusic/SelectMusicView.swift | 4 ++-- .../Sources/Views/SubmitAnswer/SelectAnswerView.swift | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift index cbd83621..7e2d4992 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift @@ -17,11 +17,11 @@ struct SelectMusicView: View { viewModel.isPlaying.toggle() } label: { if #available(iOS 17.0, *) { - Image(systemName: viewModel.isPlaying ? "pause.fill" : "play.fill") + Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") .font(.largeTitle) .contentTransition(.symbolEffect(.replace.offUp)) } else { - Image(systemName: viewModel.isPlaying ? "pause.fill" : "play.fill") + Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") .font(.largeTitle) } } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift index 0a5d628b..487441d6 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift @@ -20,11 +20,11 @@ struct SelectAnswerView: View { viewModel.isPlaying.toggle() } label: { if #available(iOS 17.0, *) { - Image(systemName: viewModel.isPlaying ? "pause.fill" : "play.fill") + Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") .font(.largeTitle) .contentTransition(.symbolEffect(.replace.offUp)) } else { - Image(systemName: viewModel.isPlaying ? "pause.fill" : "play.fill") + Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") .font(.largeTitle) } } From 6219078d5fb2c024895c36d988fc8cb32a37a9a2 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:47:49 +0900 Subject: [PATCH 14/20] =?UTF-8?q?fix:=20=EB=85=B8=EB=9E=98=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=ED=95=A0=20=EB=95=8C=20=EC=84=A0=ED=83=9D=ED=95=9C=20?= =?UTF-8?q?=EB=85=B8=EB=9E=98=EA=B0=80=20=EC=97=86=EC=9C=BC=EB=A9=B4=20?= =?UTF-8?q?=EC=9E=AC=EC=83=9D=EB=B2=84=ED=8A=BC=20=EB=A7=89=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Views/SelectMusic/SelectMusicView.swift | 4 +++- .../Sources/Views/SubmitAnswer/SelectAnswerView.swift | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift index 7e2d4992..a5371c2c 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift @@ -14,7 +14,9 @@ struct SelectMusicView: View { .scaleEffect(1.1) Spacer() Button { - viewModel.isPlaying.toggle() + if viewModel.selectedMusic != nil { + viewModel.isPlaying.toggle() + } } label: { if #available(iOS 17.0, *) { Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift index 487441d6..ae679825 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift @@ -17,7 +17,9 @@ struct SelectAnswerView: View { .scaleEffect(1.1) Spacer() Button { - viewModel.isPlaying.toggle() + if viewModel.selectedMusic != nil { + viewModel.isPlaying.toggle() + } } label: { if #available(iOS 17.0, *) { Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") From de592a0b0ffbd9b75e60a468152c90749dbfb611 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Mon, 2 Dec 2024 21:26:30 +0900 Subject: [PATCH 15/20] =?UTF-8?q?fix:=20=EB=85=B8=EB=9E=98=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=A4=91=EC=9D=BC=EB=95=8C=EB=A7=8C=20progress=20?= =?UTF-8?q?=EB=8F=99=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Views/SelectMusic/SelectMusicView.swift | 4 ++- .../SelectMusic/SelectMusicViewModel.swift | 14 +++++--- .../Views/SubmitAnswer/SelectAnswerView.swift | 35 +++++++++++++------ .../SubmitAnswerViewController.swift | 20 ++++++----- .../SubmitAnswer/SubmitAnswerViewModel.swift | 3 ++ 5 files changed, 52 insertions(+), 24 deletions(-) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift index a5371c2c..f5a042df 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift @@ -51,7 +51,7 @@ struct SelectMusicView: View { Spacer() } } else { - if viewModel.searchList.isEmpty { + if viewModel.isSearching { VStack { Spacer() ProgressView() @@ -70,10 +70,12 @@ struct SelectMusicView: View { } } .listStyle(.plain) + .scrollDismissesKeyboard(.immediately) } } } .background(.asLightGray) + } } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift index 53ad3097..8d94fe20 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift @@ -7,6 +7,7 @@ import Foundation final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { @Published public private(set) var answers: [Answer] = [] @Published public private(set) var searchList: [Music] = [] + @Published public private(set) var isSearching: Bool = false @Published public private(set) var dueTime: Date? @Published public private(set) var selectedMusic: Music? @Published public private(set) var submissionStatus: (submits: String, total: String) = ("0", "0") @@ -101,19 +102,22 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { } public func submitMusic() async throws { - guard let selectedMusic else { return } - do { - _ = try await answersRepository.submitMusic(answer: selectedMusic) - } catch { - throw error + if let selectedMusic { + do { + _ = try await answersRepository.submitMusic(answer: selectedMusic) + } catch { + throw error + } } } public func searchMusic(text: String) async throws { do { if text.isEmpty { return } + isSearching = true let searchList = try await musicAPI.search(for: text) await updateSearchList(with: searchList) + isSearching = false } catch { throw error } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift index ae679825..716d1625 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift @@ -5,6 +5,7 @@ struct SelectAnswerView: View { @ObservedObject var viewModel: SubmitAnswerViewModel @State var searchTerm = "" @Environment(\.dismiss) private var dismiss + private let debouncer = Debouncer(delay: 0.5) var body: some View { @@ -34,7 +35,7 @@ struct SelectAnswerView: View { .frame(width: 60) } .padding(16) - + ASSearchBar(text: $searchTerm, placeHolder: "노래를 선택하세요") .onChange(of: searchTerm) { newValue in debouncer.debounce { @@ -44,17 +45,30 @@ struct SelectAnswerView: View { } } } - List(viewModel.searchList) { music in - Button { - viewModel.handleSelectedMusic(with: music) - } label: { - ASMusicItemCell(music: music, fetchArtwork: { url in - await viewModel.downloadArtwork(url: url) - }) - .tint(.black) + .onTapGesture { + viewModel.sheetDetent = .large + } + if viewModel.isSearching { + VStack { + Spacer() + ProgressView() + .scaleEffect(2.0) + Spacer() + } + } else { + List(viewModel.searchList) { music in + Button { + viewModel.handleSelectedMusic(with: music) + } label: { + ASMusicItemCell(music: music, fetchArtwork: { url in + await viewModel.downloadArtwork(url: url) + }) + .tint(.black) + } } + .listStyle(.plain) + .scrollDismissesKeyboard(.immediately) } - .listStyle(.plain) } .background(.asLightGray) .toolbar { @@ -66,6 +80,7 @@ struct SelectAnswerView: View { } } } + .presentationDragIndicator(.visible) // detents와 selection 연결 } } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift index 78c26154..166d499a 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift @@ -8,6 +8,7 @@ final class SubmitAnswerViewController: UIViewController { private var selectAnswerButton = ASButton() private var submitButton = ASButton() private var submissionStatus = SubmissionStatusView() + private var selectedAnswerView: UIHostingController? private var buttonStack = UIStackView() private let viewModel: SubmitAnswerViewModel private var cancellables: Set = [] @@ -28,8 +29,9 @@ final class SubmitAnswerViewController: UIViewController { setAction() bindToComponents() } - + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) viewModel.cancelSubscriptions() cancellables.removeAll() } @@ -71,7 +73,7 @@ final class SubmitAnswerViewController: UIViewController { progressBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), progressBar.trailingAnchor.constraint(equalTo: view.trailingAnchor), progressBar.heightAnchor.constraint(equalToConstant: 16), - + musicPanel.topAnchor.constraint(equalTo: progressBar.bottomAnchor, constant: 32), musicPanel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 32), musicPanel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -32), @@ -80,7 +82,7 @@ final class SubmitAnswerViewController: UIViewController { selectedMusicPanel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 24), selectedMusicPanel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -24), selectedMusicPanel.heightAnchor.constraint(equalToConstant: 100), - + submissionStatus.topAnchor.constraint(equalTo: buttonStack.topAnchor, constant: -16), submissionStatus.trailingAnchor.constraint(equalTo: buttonStack.trailingAnchor, constant: 16), @@ -106,15 +108,16 @@ final class SubmitAnswerViewController: UIViewController { private func setAction() { selectAnswerButton.addAction(UIAction { [weak self] _ in guard let self else { return } - let selectAnswerView = UIHostingController(rootView: SelectAnswerView(viewModel: viewModel)) - if let sheet = selectAnswerView.sheetPresentationController { + selectedAnswerView = UIHostingController(rootView: SelectAnswerView(viewModel: viewModel)) + if let sheet = selectedAnswerView?.sheetPresentationController { sheet.detents = [ .medium(), - .large() + .large(), ] sheet.prefersGrabberVisible = true } viewModel.stopMusic() + guard let selectAnswerView = selectedAnswerView else { return } present(selectAnswerView, animated: true) }, for: .touchUpInside) @@ -138,8 +141,9 @@ extension SubmitAnswerViewController { let alert = LoadingAlertController( progressText: .submitMusic, loadAction: { [weak self] in - try await self?.submitAnswer() - }) { [weak self] error in + try await self?.submitAnswer() + } + ) { [weak self] error in self?.showFailSubmitMusic(error) } presentAlert(alert) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift index 8afc6d37..baf46372 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift @@ -7,6 +7,7 @@ import Foundation final class SubmitAnswerViewModel: ObservableObject, @unchecked Sendable { @Published public private(set) var searchList: [Music] = [] @Published public private(set) var selectedMusic: Music? + @Published public private(set) var isSearching: Bool = false @Published public private(set) var dueTime: Date? @Published public private(set) var recordOrder: UInt8? @Published public private(set) var status: Status? @@ -123,8 +124,10 @@ final class SubmitAnswerViewModel: ObservableObject, @unchecked Sendable { public func searchMusic(text: String) async throws { do { if text.isEmpty { return } + isSearching = true let searchList = try await musicAPI.search(for: text) await updateSearchList(with: searchList) + isSearching = false } catch { throw error } From d1698405c514d631aad7741db855204802cfbb27 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Mon, 2 Dec 2024 22:53:55 +0900 Subject: [PATCH 16/20] =?UTF-8?q?fix:=20=EB=85=B8=EB=9E=98=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D,=20=ED=82=A4=EB=B3=B4=EB=93=9C=20=EC=95=88=EB=82=B4?= =?UTF-8?q?=EB=A0=A4=EA=B0=80=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Views/SelectMusic/SelectMusicView.swift | 2 +- .../SelectMusic/SelectMusicViewModel.swift | 9 +- .../Views/SubmitAnswer/SelectAnswerView.swift | 122 +++++++++--------- .../SubmitAnswer/SubmitAnswerViewModel.swift | 9 +- 4 files changed, 76 insertions(+), 66 deletions(-) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift index f5a042df..c1b05b2c 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicView.swift @@ -58,6 +58,7 @@ struct SelectMusicView: View { .scaleEffect(2.0) Spacer() } + .scrollDismissesKeyboard(.immediately) } else { List(viewModel.searchList) { music in Button { @@ -75,7 +76,6 @@ struct SelectMusicView: View { } } .background(.asLightGray) - } } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift index 8d94fe20..e4f1e039 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift @@ -114,10 +114,10 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { public func searchMusic(text: String) async throws { do { if text.isEmpty { return } - isSearching = true + await updateIsSearching(with: true) let searchList = try await musicAPI.search(for: text) await updateSearchList(with: searchList) - isSearching = false + await updateIsSearching(with: false) } catch { throw error } @@ -159,6 +159,11 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { self.searchList = searchList } + @MainActor + private func updateIsSearching(with isSearching: Bool) { + self.isSearching = isSearching + } + public func cancelSubscriptions() { cancellables.removeAll() } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift index 716d1625..f889315b 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift @@ -5,82 +5,82 @@ struct SelectAnswerView: View { @ObservedObject var viewModel: SubmitAnswerViewModel @State var searchTerm = "" @Environment(\.dismiss) private var dismiss + @FocusState private var isFocused: Bool private let debouncer = Debouncer(delay: 0.5) var body: some View { - NavigationStack { - VStack { - HStack { - ASMusicItemCell(music: viewModel.selectedMusic, fetchArtwork: { url in - await viewModel.downloadArtwork(url: url) - }) - .scaleEffect(1.1) - Spacer() - Button { - if viewModel.selectedMusic != nil { - viewModel.isPlaying.toggle() - } - } label: { - if #available(iOS 17.0, *) { - Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") - .font(.largeTitle) - .contentTransition(.symbolEffect(.replace.offUp)) - } else { - Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") - .font(.largeTitle) - } + VStack { + HStack { + ASMusicItemCell(music: viewModel.selectedMusic, fetchArtwork: { url in + await viewModel.downloadArtwork(url: url) + }) + .scaleEffect(1.1) + Spacer() + Button { + if viewModel.selectedMusic != nil { + viewModel.isPlaying.toggle() + } + } label: { + if #available(iOS 17.0, *) { + Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") + .font(.largeTitle) + .contentTransition(.symbolEffect(.replace.offUp)) + } else { + Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") + .font(.largeTitle) } - .tint(.primary) - .frame(width: 60) } - .padding(16) + .tint(.primary) + .frame(width: 60) + } + .padding(16) + .onTapGesture { + isFocused = false + } - ASSearchBar(text: $searchTerm, placeHolder: "노래를 선택하세요") - .onChange(of: searchTerm) { newValue in - debouncer.debounce { - Task { - if newValue.isEmpty { viewModel.resetSearchList() } - try await viewModel.searchMusic(text: newValue) - } + ASSearchBar(text: $searchTerm, placeHolder: "노래를 선택하세요") + .onChange(of: searchTerm) { newValue in + debouncer.debounce { + Task { + if newValue.isEmpty { viewModel.resetSearchList() } + try await viewModel.searchMusic(text: newValue) } } - .onTapGesture { - viewModel.sheetDetent = .large - } - if viewModel.isSearching { - VStack { - Spacer() - ProgressView() - .scaleEffect(2.0) - Spacer() - } - } else { - List(viewModel.searchList) { music in - Button { - viewModel.handleSelectedMusic(with: music) - } label: { - ASMusicItemCell(music: music, fetchArtwork: { url in - await viewModel.downloadArtwork(url: url) - }) - .tint(.black) - } + } + .focused($isFocused) + if viewModel.isSearching { + VStack { + Spacer() + ProgressView() + .scaleEffect(2.0) + Spacer() + } + } else { + List(viewModel.searchList) { music in + Button { + viewModel.handleSelectedMusic(with: music) + } label: { + ASMusicItemCell(music: music, fetchArtwork: { url in + await viewModel.downloadArtwork(url: url) + }) + .tint(.black) } - .listStyle(.plain) - .scrollDismissesKeyboard(.immediately) } + .listStyle(.plain) + .edgesIgnoringSafeArea(.bottom) + .scrollDismissesKeyboard(.immediately) } - .background(.asLightGray) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Button("완료") { - viewModel.stopMusic() - dismiss() - } + } + .background(.asLightGray) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + Button("완료") { + viewModel.stopMusic() + dismiss() } } } - .presentationDragIndicator(.visible) // detents와 selection 연결 } } diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift index baf46372..4dae9b4d 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewModel.swift @@ -124,10 +124,10 @@ final class SubmitAnswerViewModel: ObservableObject, @unchecked Sendable { public func searchMusic(text: String) async throws { do { if text.isEmpty { return } - isSearching = true + await updateIsSearching(with: true) let searchList = try await musicAPI.search(for: text) await updateSearchList(with: searchList) - isSearching = false + await updateIsSearching(with: false) } catch { throw error } @@ -185,6 +185,11 @@ final class SubmitAnswerViewModel: ObservableObject, @unchecked Sendable { self.searchList = searchList } + @MainActor + private func updateIsSearching(with isSearching: Bool) { + self.isSearching = isSearching + } + public func cancelSubscriptions() { cancellables.removeAll() } From 4f1d35730a3abb4974c2b8cc6b54418e91f130a0 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Mon, 2 Dec 2024 23:21:15 +0900 Subject: [PATCH 17/20] =?UTF-8?q?chore:=20=EC=99=84=EB=A3=8C=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=97=86=EC=96=B4=EC=A0=B8=EC=84=9C=20=EB=8B=A4?= =?UTF-8?q?=EC=8B=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Views/SubmitAnswer/SelectAnswerView.swift | 126 +++++++++--------- .../SubmitAnswerViewController.swift | 1 + 2 files changed, 63 insertions(+), 64 deletions(-) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift index f889315b..244e93b3 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift @@ -10,80 +10,78 @@ struct SelectAnswerView: View { private let debouncer = Debouncer(delay: 0.5) var body: some View { - VStack { - HStack { - ASMusicItemCell(music: viewModel.selectedMusic, fetchArtwork: { url in - await viewModel.downloadArtwork(url: url) - }) - .scaleEffect(1.1) - Spacer() - Button { - if viewModel.selectedMusic != nil { - viewModel.isPlaying.toggle() - } - } label: { - if #available(iOS 17.0, *) { - Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") - .font(.largeTitle) - .contentTransition(.symbolEffect(.replace.offUp)) - } else { - Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") - .font(.largeTitle) + NavigationStack { + VStack { + HStack { + ASMusicItemCell(music: viewModel.selectedMusic, fetchArtwork: { url in + await viewModel.downloadArtwork(url: url) + }) + .scaleEffect(1.1) + Spacer() + Button { + if viewModel.selectedMusic != nil { + viewModel.isPlaying.toggle() + } + } label: { + if #available(iOS 17.0, *) { + Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") + .font(.largeTitle) + .contentTransition(.symbolEffect(.replace.offUp)) + } else { + Image(systemName: viewModel.isPlaying ? "stop.fill" : "play.fill") + .font(.largeTitle) + } } + .tint(.primary) + .frame(width: 60) + } + .padding(16) + .onTapGesture { + isFocused = false } - .tint(.primary) - .frame(width: 60) - } - .padding(16) - .onTapGesture { - isFocused = false - } - ASSearchBar(text: $searchTerm, placeHolder: "노래를 선택하세요") - .onChange(of: searchTerm) { newValue in - debouncer.debounce { - Task { - if newValue.isEmpty { viewModel.resetSearchList() } - try await viewModel.searchMusic(text: newValue) + ASSearchBar(text: $searchTerm, placeHolder: "노래를 선택하세요") + .onChange(of: searchTerm) { newValue in + debouncer.debounce { + Task { + if newValue.isEmpty { viewModel.resetSearchList() } + try await viewModel.searchMusic(text: newValue) + } } } - } - .focused($isFocused) - if viewModel.isSearching { - VStack { - Spacer() - ProgressView() - .scaleEffect(2.0) - Spacer() - } - } else { - List(viewModel.searchList) { music in - Button { - viewModel.handleSelectedMusic(with: music) - } label: { - ASMusicItemCell(music: music, fetchArtwork: { url in - await viewModel.downloadArtwork(url: url) - }) - .tint(.black) + .focused($isFocused) + if viewModel.isSearching { + VStack { + Spacer() + ProgressView() + .scaleEffect(2.0) + Spacer() + } + } else { + List(viewModel.searchList) { music in + Button { + viewModel.handleSelectedMusic(with: music) + } label: { + ASMusicItemCell(music: music, fetchArtwork: { url in + await viewModel.downloadArtwork(url: url) + }) + .tint(.black) + } } + .listStyle(.plain) + .edgesIgnoringSafeArea(.bottom) + .scrollDismissesKeyboard(.immediately) } - .listStyle(.plain) - .edgesIgnoringSafeArea(.bottom) - .scrollDismissesKeyboard(.immediately) } - } - .background(.asLightGray) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - Button("완료") { - viewModel.stopMusic() - dismiss() + .background(.asLightGray) + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button("완료") { + viewModel.stopMusic() + dismiss() + } } } } } } - -#Preview { -// SelectMusicView(v) -} diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift index 166d499a..62077ec1 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SubmitAnswerViewController.swift @@ -42,6 +42,7 @@ final class SubmitAnswerViewController: UIViewController { musicPanel.bind(to: viewModel.$music) selectedMusicPanel.bind(to: viewModel.$selectedMusic) submitButton.bind(to: viewModel.$musicData) + } private func setupUI() { From 8b3098a8a7f0b0535692c1fd87b3df6464b68d61 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:40:19 +0900 Subject: [PATCH 18/20] =?UTF-8?q?chore:=20=EB=84=A4=EB=B9=84=EA=B2=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20Toolbar=20topBarTrailing=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Views/SubmitAnswer/SelectAnswerView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift index 244e93b3..d1d84b70 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SubmitAnswer/SelectAnswerView.swift @@ -75,7 +75,7 @@ struct SelectAnswerView: View { } .background(.asLightGray) .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { + ToolbarItem(placement: .topBarTrailing) { Button("완료") { viewModel.stopMusic() dismiss() From 2275d32291ff45fd46367c71a4b6cdd25283d7e3 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Sun, 1 Dec 2024 22:17:35 +0900 Subject: [PATCH 19/20] =?UTF-8?q?feat:=20Network=20Helper=20=EB=A7=8C?= =?UTF-8?q?=EB=93=9C=EB=8A=94=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift | 11 +++++++++++ .../ASRepository/Repositories/MainRepository.swift | 1 - .../Views/SelectMusic/SelectMusicViewModel.swift | 3 +-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift b/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift index b47baa86..abe35869 100644 --- a/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift +++ b/alsongDalsong/ASNetworkKit/ASNetworkKit/FirebaseEndpoint.swift @@ -69,4 +69,15 @@ public extension FirebaseEndpoint { Self(path: .auth, method: .get) .update(\.queryItems, with: [.init(name: "listAvatarUrls", value: "true")]) } + + func withCommonQueryItems(roomNumber: String?, userID: String?) -> Self { + var items = [URLQueryItem]() + if let userID { + items.append(URLQueryItem(name: "userId", value: userID)) + } + if let roomNumber { + items.append(URLQueryItem(name: "roomNumber", value: roomNumber)) + } + return self.update(\.queryItems, with: items) + } } diff --git a/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift b/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift index 3db1159e..b74acf07 100644 --- a/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift +++ b/alsongDalsong/ASRepository/ASRepository/Repositories/MainRepository.swift @@ -64,7 +64,6 @@ public final class MainRepository: MainRepositoryProtocol { } public func joinRoom(nickname: String, avatar: URL, roomNumber: String) async throws -> Bool { - let player = try await self.authManager.signIn(nickname: nickname, avatarURL: avatar) let response: [String: String] = try await self.sendRequest( endpointPath: .joinRoom, requestBody: ["roomNumber": roomNumber, "userId": myId] diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift index e4f1e039..56ae6b8e 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift @@ -115,8 +115,7 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { do { if text.isEmpty { return } await updateIsSearching(with: true) - let searchList = try await musicAPI.search(for: text) - await updateSearchList(with: searchList) + searchList = try await musicAPI.search(for: text) await updateIsSearching(with: false) } catch { throw error From a53414eff73c26b9e4c73805d27803ce179b47c0 Mon Sep 17 00:00:00 2001 From: SeungJae Son <46300191+Sonny-Kor@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:53:49 +0900 Subject: [PATCH 20/20] rebase: dev --- .../Views/Result/HummingResultViewModel.swift | 10 +++------- .../Views/SelectMusic/SelectMusicViewModel.swift | 14 +++++++++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewModel.swift index f51b45dd..f2c599d2 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/Result/HummingResultViewModel.swift @@ -123,13 +123,9 @@ final class HummingResultViewModel: @unchecked Sendable { private func startPlayingCurrentMusic() async -> Void { guard let fileUrl = currentResult?.music?.previewUrl else { return } - do { - let data = try await musicRepository.getMusicData(url: fileUrl) - await AudioHelper.shared.startPlaying(data, option: .partial(time: 10)) - await waitForPlaybackToFinish() - } catch { - return - } + let data = try? await musicRepository.getMusicData(url: fileUrl) + await AudioHelper.shared.startPlaying(data, option: .partial(time: 10)) + await waitForPlaybackToFinish() } private func waitForPlaybackToFinish() async { diff --git a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift index 56ae6b8e..07d15cb8 100644 --- a/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift +++ b/alsongDalsong/alsongDalsong/alsongDalsong/Sources/Views/SelectMusic/SelectMusicViewModel.swift @@ -91,7 +91,8 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { guard let url else { return nil } do { return try await musicRepository.getMusicData(url: url) - } catch { + } + catch { return nil } } @@ -115,7 +116,8 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { do { if text.isEmpty { return } await updateIsSearching(with: true) - searchList = try await musicAPI.search(for: text) + let searchList = try await musicAPI.search(for: text) + await updateSearchList(with: searchList) await updateIsSearching(with: false) } catch { throw error @@ -124,7 +126,8 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { public func randomMusic() async throws { do { - selectedMusic = try await musicAPI.randomSong(from: "pl.u-aZb00o7uPlzMZzr") + let selectedMusic = try await musicAPI.randomSong(from: "pl.u-aZb00o7uPlzMZzr") + await updateSelectedMusic(with: selectedMusic) } catch { throw error } @@ -163,6 +166,11 @@ final class SelectMusicViewModel: ObservableObject, @unchecked Sendable { self.isSearching = isSearching } + @MainActor + private func updateSelectedMusic(with music: Music) { + selectedMusic = music + } + public func cancelSubscriptions() { cancellables.removeAll() }