diff --git a/KkuMulKum/Network/DTO/Model/Meetings/MeetingMembersResponseModel.swift b/KkuMulKum/Network/DTO/Model/Meetings/MeetingMembersResponseModel.swift index 22722df6..5a913d6b 100644 --- a/KkuMulKum/Network/DTO/Model/Meetings/MeetingMembersResponseModel.swift +++ b/KkuMulKum/Network/DTO/Model/Meetings/MeetingMembersResponseModel.swift @@ -15,7 +15,7 @@ struct MeetingMembersModel: ResponseModelType { struct Member: Codable { let memberID: Int - let name: String + let name: String? let profileImageURL: String? enum CodingKeys: String, CodingKey { diff --git a/KkuMulKum/Network/DTO/Model/Promises/MeetingPromisesModel.swift b/KkuMulKum/Network/DTO/Model/Promises/MeetingPromisesModel.swift index 3f13c222..1cfe97b9 100644 --- a/KkuMulKum/Network/DTO/Model/Promises/MeetingPromisesModel.swift +++ b/KkuMulKum/Network/DTO/Model/Promises/MeetingPromisesModel.swift @@ -13,10 +13,19 @@ struct MeetingPromisesModel: ResponseModelType { } struct MeetingPromise: Codable { - let id: Int + let promiseID: Int let name: String let dDay: Int let date: String let time: String let placeName: String + + enum CodingKeys: String, CodingKey { + case promiseID = "promiseId" + case name + case dDay + case date + case time + case placeName + } } diff --git a/KkuMulKum/Network/Service/HomeService.swift b/KkuMulKum/Network/Service/HomeService.swift index 92cdb4a7..c596e0fc 100644 --- a/KkuMulKum/Network/Service/HomeService.swift +++ b/KkuMulKum/Network/Service/HomeService.swift @@ -15,4 +15,27 @@ final class HomeService { init(provider: MoyaProvider = MoyaProvider(plugins: [MoyaLoggingPlugin()])) { self.provider = provider } + + func request( + with request: HomeTargetType + ) async throws -> ResponseBodyDTO? { + return try await withCheckedThrowingContinuation { continuation in + provider.request(request) { result in + switch result { + case .success(let response): + do { + let decodedData = try JSONDecoder().decode( + ResponseBodyDTO.self, + from: response.data + ) + continuation.resume(returning: decodedData) + } catch { + continuation.resume(throwing: error) + } + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } } diff --git a/KkuMulKum/Network/Service/MeetingService.swift b/KkuMulKum/Network/Service/MeetingService.swift index b18b1c9a..4adc82fc 100644 --- a/KkuMulKum/Network/Service/MeetingService.swift +++ b/KkuMulKum/Network/Service/MeetingService.swift @@ -15,4 +15,27 @@ final class MeetingService { init(provider: MoyaProvider = MoyaProvider(plugins: [MoyaLoggingPlugin()])) { self.provider = provider } + + func request( + with request: MeetingTargetType + ) async throws -> ResponseBodyDTO? { + return try await withCheckedThrowingContinuation { continuation in + provider.request(request) { result in + switch result { + case .success(let response): + do { + let decodedData = try JSONDecoder().decode( + ResponseBodyDTO.self, + from: response.data + ) + continuation.resume(returning: decodedData) + } catch { + continuation.resume(throwing: error) + } + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } } diff --git a/KkuMulKum/Network/Service/PromiseService.swift b/KkuMulKum/Network/Service/PromiseService.swift index af3a9aac..fe7d4410 100644 --- a/KkuMulKum/Network/Service/PromiseService.swift +++ b/KkuMulKum/Network/Service/PromiseService.swift @@ -12,8 +12,31 @@ import Moya final class PromiseService { let provider: MoyaProvider - init(provider: MoyaProvider = MoyaProvider(plugins: [MoyaLoggingPlugin()]) - ) { + init(provider: MoyaProvider = MoyaProvider(plugins: [MoyaLoggingPlugin()])) { self.provider = provider } + + func request( + with request: PromiseTargetType + ) async throws -> ResponseBodyDTO? { + return try await withCheckedThrowingContinuation { continuation in + provider.request(request) { result in + switch result { + case .success(let response): + do { + let decodedData = try JSONDecoder().decode( + ResponseBodyDTO.self, + from: response.data + ) + continuation.resume(returning: decodedData) + } catch { + continuation.resume(throwing: error) + } + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + } diff --git a/KkuMulKum/Network/Service/UtilService.swift b/KkuMulKum/Network/Service/UtilService.swift index dc5d3c7c..4d735a96 100644 --- a/KkuMulKum/Network/Service/UtilService.swift +++ b/KkuMulKum/Network/Service/UtilService.swift @@ -12,8 +12,30 @@ import Moya final class UtilService { let provider: MoyaProvider - init(provider: MoyaProvider = MoyaProvider(plugins: [MoyaLoggingPlugin()]) - ) { + init(provider: MoyaProvider = MoyaProvider(plugins: [MoyaLoggingPlugin()])) { self.provider = provider } + + func request( + with request: UtilTargetType + ) async throws -> ResponseBodyDTO? { + return try await withCheckedThrowingContinuation { continuation in + provider.request(request) { result in + switch result { + case .success(let response): + do { + let decodedData = try JSONDecoder().decode( + ResponseBodyDTO.self, + from: response.data + ) + continuation.resume(returning: decodedData) + } catch { + continuation.resume(throwing: error) + } + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } } diff --git a/KkuMulKum/Network/TargetType/MeetingTargetType.swift b/KkuMulKum/Network/TargetType/MeetingTargetType.swift index e298fd41..f651e644 100644 --- a/KkuMulKum/Network/TargetType/MeetingTargetType.swift +++ b/KkuMulKum/Network/TargetType/MeetingTargetType.swift @@ -15,6 +15,7 @@ enum MeetingTargetType { case fetchMeetingList case fetchMeetingInfo(meetingID: Int) case fetchMeetingMember(meetingID: Int) + case fetchMeetingPromiseList(meetingID: Int) } extension MeetingTargetType: TargetType { @@ -39,6 +40,8 @@ extension MeetingTargetType: TargetType { return "/api/v1/meetings/\(meetingID)" case .fetchMeetingMember(meetingID: let meetingID): return "/api/v1/meetings/\(meetingID)/members" + case .fetchMeetingPromiseList(let meetingID): + return "/api/v1/meetings/\(meetingID)/promises" } } @@ -46,7 +49,7 @@ extension MeetingTargetType: TargetType { switch self { case .createMeeting, .joinMeeting: return .post - case .fetchMeetingList, .fetchMeetingInfo, .fetchMeetingMember: + case .fetchMeetingList, .fetchMeetingInfo, .fetchMeetingMember, .fetchMeetingPromiseList: return .get } } @@ -57,7 +60,7 @@ extension MeetingTargetType: TargetType { return .requestJSONEncodable(request) case .joinMeeting(request: let request): return .requestJSONEncodable(request) - case .fetchMeetingList, .fetchMeetingInfo, .fetchMeetingMember: + case .fetchMeetingList, .fetchMeetingInfo, .fetchMeetingMember, .fetchMeetingPromiseList: return .requestPlain } } diff --git a/KkuMulKum/Network/TargetType/PromiseTargetType.swift b/KkuMulKum/Network/TargetType/PromiseTargetType.swift index e54ae245..c2bdab3c 100644 --- a/KkuMulKum/Network/TargetType/PromiseTargetType.swift +++ b/KkuMulKum/Network/TargetType/PromiseTargetType.swift @@ -15,7 +15,7 @@ enum PromiseTargetType { case updatePreparationStatus(promiseID: Int) case updateDepartureStatus(promiseID: Int) case updateArrivalStatus(promiseID: Int) - case fetchmeetingPromiseList(meetingID: Int, request: PromiseInfoModel) + case fetchMeetingPromiseList(meetingID: Int) case addPromise(meetingID: Int, request: AddPromiseRequestModel) case fetchPromiseInfo(promiseID: Int) case fetchMyReadyStatus(promiseID: Int) @@ -47,10 +47,10 @@ extension PromiseTargetType: TargetType { return "/api/v1/promises/\(promiseID)/departure" case .updateArrivalStatus(let promiseID): return "/api/v1/promises/\(promiseID)/arrival" - case .fetchmeetingPromiseList(let meetingID, _): - return "/api/v1/promises/\(meetingID)/promises" + case .fetchMeetingPromiseList(let meetingID): + return "/api/v1/meetings/\(meetingID)/promises" case .addPromise(let meetingID, _): - return "/api/v1/promises/\(meetingID)/promises" + return "/api/v1/meetings/\(meetingID)/promises" case .fetchPromiseInfo(let promiseID): return "/api/v1/promises/\(promiseID)" case .fetchMyReadyStatus(let promiseID): @@ -68,7 +68,7 @@ extension PromiseTargetType: TargetType { var method: Moya.Method { switch self { - case .fetchTodayNextPromise, .fetchUpcomingPromiseList, .fetchmeetingPromiseList, + case .fetchTodayNextPromise, .fetchUpcomingPromiseList, .fetchMeetingPromiseList, .fetchPromiseInfo, .fetchMyReadyStatus, .fetchPromiseParticipantList, .fetchTardyInfo: return .get @@ -85,10 +85,8 @@ extension PromiseTargetType: TargetType { case .fetchTodayNextPromise, .fetchUpcomingPromiseList, .updatePreparationStatus, .updateDepartureStatus, .updateArrivalStatus, .fetchPromiseInfo, .fetchMyReadyStatus, .fetchPromiseParticipantList, .updateMyPromiseReadyStatus, - .fetchTardyInfo, .updatePromiseCompletion: + .fetchTardyInfo, .updatePromiseCompletion, .fetchMeetingPromiseList: return .requestPlain - case .fetchmeetingPromiseList(_, let request): - return .requestJSONEncodable(request) case .addPromise(_, let request): return .requestJSONEncodable(request) } diff --git a/KkuMulKum/Source/AddPromise/Cell/SelectMemberCell.swift b/KkuMulKum/Source/AddPromise/Cell/SelectMemberCell.swift index 115ff2e1..0b5f4066 100644 --- a/KkuMulKum/Source/AddPromise/Cell/SelectMemberCell.swift +++ b/KkuMulKum/Source/AddPromise/Cell/SelectMemberCell.swift @@ -63,7 +63,7 @@ extension SelectMemberCell { func configure(with member: Member) { self.member = member - nameLabel.setText(member.name, style: .body06, color: .gray6) + nameLabel.setText(member.name ?? " ", style: .body06, color: .gray6) profileImageView.image = .imgProfile.withRenderingMode(.alwaysOriginal) guard let imageURL = URL(string: member.profileImageURL ?? "") else { return } profileImageView.kf.setImage(with: imageURL) diff --git a/KkuMulKum/Source/MeetingInfo/Cell/MeetingMemberCell.swift b/KkuMulKum/Source/MeetingInfo/Cell/MeetingMemberCell.swift index 5540b6e0..5ac2480b 100644 --- a/KkuMulKum/Source/MeetingInfo/Cell/MeetingMemberCell.swift +++ b/KkuMulKum/Source/MeetingInfo/Cell/MeetingMemberCell.swift @@ -102,7 +102,7 @@ private extension MeetingMemberCell { let name = member.name let imageURL = member.profileImageURL - nameLabel.setText(name, style: .caption02, color: .gray6) + nameLabel.setText(name ?? " ", style: .caption02, color: .gray6) profileImageButton.setImage( .imgProfile.withRenderingMode(.alwaysOriginal), for: .normal diff --git a/KkuMulKum/Source/MeetingInfo/Service/MeetingInfoService.swift b/KkuMulKum/Source/MeetingInfo/Service/MeetingInfoService.swift index bbd2f3d4..ba6de5d0 100644 --- a/KkuMulKum/Source/MeetingInfo/Service/MeetingInfoService.swift +++ b/KkuMulKum/Source/MeetingInfo/Service/MeetingInfoService.swift @@ -8,13 +8,35 @@ import Foundation protocol MeetingInfoServiceType { - func fetchMeetingInfo(with meetingID: Int) -> MeetingInfoModel? - func fetchMeetingMemberList(with meetingID: Int) -> MeetingMembersModel? - func fetchMeetingPromiseList(with meetingID: Int) -> MeetingPromisesModel? + func fetchMeetingInfo(with meetingID: Int) async throws -> ResponseBodyDTO? + func fetchMeetingMemberList( + with meetingID: Int + ) async throws -> ResponseBodyDTO? + func fetchMeetingPromiseList( + with meetingID: Int + ) async throws -> ResponseBodyDTO? +} + +extension MeetingService: MeetingInfoServiceType { + func fetchMeetingInfo(with meetingID: Int) async throws -> ResponseBodyDTO? { + return try await request(with: .fetchMeetingInfo(meetingID: meetingID)) + } + + func fetchMeetingMemberList( + with meetingID: Int + ) async throws -> ResponseBodyDTO? { + return try await request(with: .fetchMeetingMember(meetingID: meetingID)) + } + + func fetchMeetingPromiseList( + with meetingID: Int + ) async throws -> ResponseBodyDTO? { + return try await request(with: .fetchMeetingPromiseList(meetingID: meetingID)) + } } final class MockMeetingInfoService: MeetingInfoServiceType { - func fetchMeetingInfo(with meetingID: Int) -> MeetingInfoModel? { + func fetchMeetingInfo(with meetingID: Int) -> ResponseBodyDTO? { let mockData = MeetingInfoModel( meetingID: 1, name: "웅웅난진웅", @@ -23,10 +45,10 @@ final class MockMeetingInfoService: MeetingInfoServiceType { invitationCode: "WD56CQ" ) - return mockData + return ResponseBodyDTO(success: true, data: mockData, error: nil) } - func fetchMeetingMemberList(with meetingID: Int) -> MeetingMembersModel? { + func fetchMeetingMemberList(with meetingID: Int) -> ResponseBodyDTO? { let mockData = MeetingMembersModel( memberCount: 14, members: [ @@ -103,14 +125,14 @@ final class MockMeetingInfoService: MeetingInfoServiceType { ] ) - return mockData + return ResponseBodyDTO(success: true, data: mockData, error: nil) } - func fetchMeetingPromiseList(with meetingID: Int) -> MeetingPromisesModel? { + func fetchMeetingPromiseList(with meetingID: Int) -> ResponseBodyDTO? { let mockData = MeetingPromisesModel( promises: [ MeetingPromise( - id: 1, + promiseID: 1, name: "꾸물 리프레시 데이", dDay: 0, date: "2024.07.20", @@ -118,7 +140,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "DMC역" ), MeetingPromise( - id: 2, + promiseID: 2, name: "꾸물 잼얘 나이트", dDay: 10, date: "2024.07.30", @@ -126,7 +148,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "홍대입구" ), MeetingPromise( - id: 3, + promiseID: 3, name: "친구 생일 파티", dDay: 5, date: "2024.07.25", @@ -134,7 +156,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "강남역" ), MeetingPromise( - id: 4, + promiseID: 4, name: "주말 산책", dDay: 3, date: "2024.07.23", @@ -142,7 +164,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "서울숲" ), MeetingPromise( - id: 5, + promiseID: 5, name: "프로젝트 미팅", dDay: 1, date: "2024.07.21", @@ -150,7 +172,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "삼성역" ), MeetingPromise( - id: 6, + promiseID: 6, name: "독서 모임", dDay: 7, date: "2024.07.27", @@ -158,7 +180,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "합정역" ), MeetingPromise( - id: 7, + promiseID: 7, name: "헬스클럽 모임", dDay: 2, date: "2024.07.22", @@ -166,7 +188,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "신촌역" ), MeetingPromise( - id: 8, + promiseID: 8, name: "영화 관람", dDay: 4, date: "2024.07.24", @@ -174,7 +196,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "잠실역" ), MeetingPromise( - id: 9, + promiseID: 9, name: "저녁 식사", dDay: 6, date: "2024.07.26", @@ -182,7 +204,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "이태원역" ), MeetingPromise( - id: 10, + promiseID: 10, name: "아침 조깅", dDay: 14, date: "2024.08.03", @@ -190,7 +212,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "한강공원" ), MeetingPromise( - id: 11, + promiseID: 11, name: "커피 브레이크", dDay: 8, date: "2024.07.28", @@ -198,7 +220,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "을지로입구" ), MeetingPromise( - id: 12, + promiseID: 12, name: "스터디 그룹", dDay: 12, date: "2024.08.01", @@ -206,7 +228,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "강남역" ), MeetingPromise( - id: 13, + promiseID: 13, name: "뮤직 페스티벌", dDay: 9, date: "2024.07.29", @@ -214,7 +236,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "난지공원" ), MeetingPromise( - id: 14, + promiseID: 14, name: "낚시 여행", dDay: 11, date: "2024.07.31", @@ -222,7 +244,7 @@ final class MockMeetingInfoService: MeetingInfoServiceType { placeName: "속초항" ), MeetingPromise( - id: 15, + promiseID: 15, name: "가족 모임", dDay: 13, date: "2024.08.02", @@ -232,6 +254,6 @@ final class MockMeetingInfoService: MeetingInfoServiceType { ] ) - return mockData + return ResponseBodyDTO(success: true, data: mockData, error: nil) } } diff --git a/KkuMulKum/Source/MeetingInfo/ViewController/MeetingInfoViewController.swift b/KkuMulKum/Source/MeetingInfo/ViewController/MeetingInfoViewController.swift index bf65812a..5961163f 100644 --- a/KkuMulKum/Source/MeetingInfo/ViewController/MeetingInfoViewController.swift +++ b/KkuMulKum/Source/MeetingInfo/ViewController/MeetingInfoViewController.swift @@ -79,7 +79,6 @@ private extension MeetingInfoViewController { output.info .drive(with: self) { owner, meetingInfo in guard let info = meetingInfo else { return } - owner.title = info.name owner.rootView.configureInfo( createdAt: info.createdAt, @@ -109,6 +108,10 @@ private extension MeetingInfoViewController { .disposed(by: disposeBag) output.promises + .map { [weak self] promises in + self?.rootView.configureEmptyView(with: !promises.isEmpty) + return promises + } .drive(rootView.promiseListView.rx.items( cellIdentifier: MeetingPromiseCell.reuseIdentifier, cellType: MeetingPromiseCell.self @@ -135,18 +138,14 @@ private extension MeetingInfoViewController { ) return } - owner.navigateToAddPromise() } .disposed(by: disposeBag) - - rootView.configureEmptyView(with: viewModel.meetingPromises.count == 0) } func navigateToAddPromise() { let viewModel = AddPromiseViewModel(meetingID: viewModel.meetingID) let viewController = AddPromiseViewController(viewModel: viewModel) - navigationController?.pushViewController(viewController, animated: true) } } diff --git a/KkuMulKum/Source/MeetingInfo/ViewModel/MeetingInfoViewModel.swift b/KkuMulKum/Source/MeetingInfo/ViewModel/MeetingInfoViewModel.swift index 916832d7..713fb10f 100644 --- a/KkuMulKum/Source/MeetingInfo/ViewModel/MeetingInfoViewModel.swift +++ b/KkuMulKum/Source/MeetingInfo/ViewModel/MeetingInfoViewModel.swift @@ -42,27 +42,12 @@ extension MeetingInfoViewModel: ViewModelType { } func transform(input: Input, disposeBag: DisposeBag) -> Output { - let meetingID = self.meetingID - - input.viewWillAppear - .map { [weak self] _ in - self?.service.fetchMeetingInfo(with: meetingID) - } - .bind(to: infoRelay) - .disposed(by: disposeBag) - input.viewWillAppear - .map { [weak self] _ in - self?.service.fetchMeetingMemberList(with: meetingID) - } - .bind(to: meetingMemberModelRelay) - .disposed(by: disposeBag) - - input.viewWillAppear - .map { [weak self] _ in - self?.service.fetchMeetingPromiseList(with: meetingID) + .subscribe(with: self) { owner, _ in + owner.fetchMeetingInfo() + owner.fetchMeetingMembers() + owner.fetchMeetingPromises() } - .bind(to: meetingPromisesModelRelay) .disposed(by: disposeBag) let info = infoRelay.asDriver(onErrorJustReturn: nil) @@ -72,18 +57,22 @@ extension MeetingInfoViewModel: ViewModelType { .asDriver(onErrorJustReturn: 0) let members = meetingMemberModelRelay - .compactMap { $0?.members } - .map { members -> [Member] in + .map { model -> [Member] in let mockData = Member(memberID: 0, name: "", profileImageURL: "") - var newMembers = members + + guard let model else { return [mockData] } + + var newMembers = model.members newMembers.insert(mockData, at: 0) return newMembers } .asDriver(onErrorJustReturn: []) let promises = meetingPromisesModelRelay - .compactMap { $0?.promises } - .map { $0.sorted { $0.dDay < $1.dDay }} + .map { model -> [MeetingPromise] in + guard let model else { return [] } + return model.promises + } .asDriver(onErrorJustReturn: []) let isPossibleToCreatePromise = input.createPromiseButtonDidTap @@ -109,3 +98,38 @@ extension MeetingInfoViewModel: ViewModelType { return output } } + +private extension MeetingInfoViewModel { + func fetchMeetingInfo() { + Task { + do { + let responseBody = try await service.fetchMeetingInfo(with: meetingID) + infoRelay.accept(responseBody?.data) + } catch { + print(">>> \(error.localizedDescription) : \(#function)") + } + } + } + + func fetchMeetingMembers() { + Task { + do { + let responseBody = try await service.fetchMeetingMemberList(with: meetingID) + meetingMemberModelRelay.accept(responseBody?.data) + } catch { + print(">>> \(error.localizedDescription) : \(#function)") + } + } + } + + func fetchMeetingPromises() { + Task { + do { + let responseBody = try await service.fetchMeetingPromiseList(with: meetingID) + meetingPromisesModelRelay.accept(responseBody?.data) + } catch { + print(">>> \(error.localizedDescription) : \(#function)") + } + } + } +} diff --git a/KkuMulKum/Source/MeetingList/ViewController/MeetingListViewController.swift b/KkuMulKum/Source/MeetingList/ViewController/MeetingListViewController.swift index 064f85bd..43a343e0 100644 --- a/KkuMulKum/Source/MeetingList/ViewController/MeetingListViewController.swift +++ b/KkuMulKum/Source/MeetingList/ViewController/MeetingListViewController.swift @@ -85,13 +85,13 @@ extension MeetingListViewController: UITableViewDelegate { } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + // TODO: MeetingID를 넘겨받기 let viewController = MeetingInfoViewController( viewModel: MeetingInfoViewModel( - meetingID: 1, - service: MockMeetingInfoService() + meetingID: 8, + service: MeetingService() ) ) - tabBarController?.navigationController?.pushViewController(viewController, animated: true) } }