diff --git a/SodaLive/Sources/Live/GetLatestFinishedLiveResponse.swift b/SodaLive/Sources/Live/GetLatestFinishedLiveResponse.swift new file mode 100644 index 0000000..99a81cb --- /dev/null +++ b/SodaLive/Sources/Live/GetLatestFinishedLiveResponse.swift @@ -0,0 +1,13 @@ +// +// GetLatestFinishedLiveResponse.swift +// SodaLive +// +// Created by klaus on 7/22/25. +// + +struct GetLatestFinishedLiveResponse: Decodable { + let memberId: Int + let nickname: String + let profileImageUrl: String + let timeAgo: String +} diff --git a/SodaLive/Sources/Live/LiveApi.swift b/SodaLive/Sources/Live/LiveApi.swift index 15f6e29..49470fd 100644 --- a/SodaLive/Sources/Live/LiveApi.swift +++ b/SodaLive/Sources/Live/LiveApi.swift @@ -40,6 +40,7 @@ enum LiveApi { case likeHeart(request: LiveRoomLikeHeartRequest) case getTotalHeartCount(roomId: Int) case heartStatus(roomId: Int) + case getLiveMain(isAdultContentVisible: Bool, contentType: ContentType) } extension LiveApi: TargetType { @@ -141,12 +142,15 @@ extension LiveApi: TargetType { case .heartStatus(let roomId): return "/live/room/\(roomId)/heart-list" + + case .getLiveMain: + return "/api/live" } } var method: Moya.Method { switch self { - case .roomList, .recentVisitRoomUsers, .getReservations, .getReservation, .getRoomDetail, .getTags, .getRecentRoomInfo, .getRoomInfo, .donationStatus, .donationTotal, .getDonationMessageList, .getUserProfile, .getAllMenuPreset, .getTotalHeartCount, .heartStatus: + case .roomList, .recentVisitRoomUsers, .getReservations, .getReservation, .getRoomDetail, .getTags, .getRecentRoomInfo, .getRoomInfo, .donationStatus, .donationTotal, .getDonationMessageList, .getUserProfile, .getAllMenuPreset, .getTotalHeartCount, .heartStatus, .getLiveMain: return .get case .makeReservation, .enterRoom, .createRoom, .quitRoom, .donation, .refundDonation, .kickOut, .likeHeart: @@ -248,6 +252,18 @@ extension LiveApi: TargetType { case .likeHeart(let request): return .requestJSONEncodable(request) + + case .getLiveMain(let isAdultContentVisible, let contentType): + let parameters = [ + "timezone": TimeZone.current.identifier, + "isAdultContentVisible": isAdultContentVisible, + "contentType": contentType + ] as [String: Any] + + return .requestParameters( + parameters: parameters, + encoding: URLEncoding.queryString + ) } } diff --git a/SodaLive/Sources/Live/LiveMainResponse.swift b/SodaLive/Sources/Live/LiveMainResponse.swift new file mode 100644 index 0000000..e54b097 --- /dev/null +++ b/SodaLive/Sources/Live/LiveMainResponse.swift @@ -0,0 +1,18 @@ +// +// LiveMainResponse.swift +// SodaLive +// +// Created by klaus on 7/22/25. +// + +import Foundation + +struct LiveMainResponse: Decodable { + let liveOnAirRoomList: [GetRoomListResponse] + let communityPostList: [GetCommunityPostListResponse] + let recommendLiveList: [GetRecommendLiveResponse] + let latestFinishedLiveList: [GetLatestFinishedLiveResponse] + let replayLive: [AudioContentMainItem] + let followingChannelList: [GetRecommendChannelResponse] + let liveReservationRoomList: [GetRoomListResponse] +} diff --git a/SodaLive/Sources/Live/LiveRepository.swift b/SodaLive/Sources/Live/LiveRepository.swift index 2c3dbc9..8dc046e 100644 --- a/SodaLive/Sources/Live/LiveRepository.swift +++ b/SodaLive/Sources/Live/LiveRepository.swift @@ -132,4 +132,13 @@ final class LiveRepository { func heartStatus(roomId: Int) -> AnyPublisher { return api.requestPublisher(.heartStatus(roomId: roomId)) } + + func getLiveMain() -> AnyPublisher { + return api.requestPublisher( + .getLiveMain( + isAdultContentVisible: UserDefaults.isAdultContentVisible(), + contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL + ) + ) + } } diff --git a/SodaLive/Sources/Live/LiveView.swift b/SodaLive/Sources/Live/LiveView.swift index d6aeb16..9e8eda9 100644 --- a/SodaLive/Sources/Live/LiveView.swift +++ b/SodaLive/Sources/Live/LiveView.swift @@ -69,7 +69,7 @@ struct LiveView: View { } }, onClickRefresh: { - viewModel.getSummary() + viewModel.getLiveMain() } ) @@ -92,7 +92,7 @@ struct LiveView: View { SectionLiveReservationView( items: viewModel.liveReservationItems, - onClickCancel: { viewModel.getSummary() }, + onClickCancel: { viewModel.getLiveMain() }, onClickStart: { roomId in if !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { processStart(roomId: roomId) @@ -119,7 +119,7 @@ struct LiveView: View { .padding(.vertical, 24) } .onAppear { - viewModel.getSummary() + viewModel.getLiveMain() } } @@ -159,10 +159,7 @@ struct LiveView: View { ) } - if viewModel.isFollowedChannelLoading || - viewModel.isRecommendChannelLoading || - viewModel.isRecommendLiveLoading || - viewModel.isLoading { + if viewModel.isLoading { LoadingView() } } @@ -188,7 +185,7 @@ struct LiveView: View { } private func onCreateSuccess(response: CreateLiveRoomResponse) { - viewModel.getSummary() + viewModel.getLiveMain() if let _ = response.channelName { viewModel.enterRoom(roomId: response.id!) } diff --git a/SodaLive/Sources/Live/LiveViewModel.swift b/SodaLive/Sources/Live/LiveViewModel.swift index 97c7e8e..40a822f 100644 --- a/SodaLive/Sources/Live/LiveViewModel.swift +++ b/SodaLive/Sources/Live/LiveViewModel.swift @@ -21,14 +21,13 @@ final class LiveViewModel: ObservableObject { @Published private(set) var recommendChannelItems: [GetRecommendChannelResponse] = [] @Published private(set) var followedChannelItems: [GetRecommendChannelResponse] = [] @Published private(set) var communityPostItems: [GetCommunityPostListResponse] = [] + @Published private(set) var replayLiveItems: [AudioContentMainItem] = [] + @Published private(set) var latestFinishedLiveItems: [GetLatestFinishedLiveResponse] = [] @Published var errorMessage = "" @Published var isShowPopup = false @Published var isRefresh = false @Published var isLoading = false - @Published var isRecommendLiveLoading = false - @Published var isRecommendChannelLoading = false - @Published var isFollowedChannelLoading = false @Published var paymentDialogTitle = "" @Published var paymentDialogDesc = "" @@ -81,47 +80,10 @@ final class LiveViewModel: ObservableObject { passwordDialogConfirmAction = { _ in } } - func getSummary() { - if !UserDefaults.string(forKey: UserDefaultsKey.token).trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - getFollowedChannelList() - } - - getRecommendChannelList() - getRecommendLive() - - if !UserDefaults.string(forKey: UserDefaultsKey.token).trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - getLatestPostListFromCreatorsYouFollow() - } - + func getLiveMain() { isLoading = true - liveNowItems.removeAll() - liveReservationItems.removeAll() - - let liveNow = repository.roomList( - request: GetRoomListRequest( - timezone: TimeZone.current.identifier, - dateString: nil, - status: .NOW, - isAdultContentVisible: UserDefaults.isAdultContentVisible(), - page: 1, - size: 10 - ) - ) - - let liveReservation = repository.roomList( - request: GetRoomListRequest( - timezone: TimeZone.current.identifier, - dateString: nil, - status: .RESERVATION, - isAdultContentVisible: UserDefaults.isAdultContentVisible(), - page: 1, - size: 10 - ) - ) - - Publishers - .CombineLatest(liveNow, liveReservation) + repository.getLiveMain() .sink { result in switch result { case .finished: @@ -129,52 +91,35 @@ final class LiveViewModel: ObservableObject { case .failure(let error): ERROR_LOG(error.localizedDescription) } - } receiveValue: { (now, reservation) in - let nowData = now.data - let reservationData = reservation.data - - let jsonDecoder = JSONDecoder() - - do { - let nowDecoded = try jsonDecoder.decode(ApiResponse<[GetRoomListResponse]>.self, from: nowData) - if let data = nowDecoded.data, nowDecoded.success { - self.liveNowItems.removeAll() - self.liveNowItems.append(contentsOf: data) - } else { - if let message = nowDecoded.message { - self.errorMessage = message - } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - } - - self.isShowPopup = true - } - } catch { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - self.isShowPopup = true - } - - do { - let reservationDecoded = try jsonDecoder.decode(ApiResponse<[GetRoomListResponse]>.self, from: reservationData) - if let data = reservationDecoded.data, reservationDecoded.success { - self.liveReservationItems.removeAll() - self.liveReservationItems.append(contentsOf: data) - } else { - if let message = reservationDecoded.message { - self.errorMessage = message - } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - } - - self.isShowPopup = true - } - } catch { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - self.isShowPopup = true - } - + } receiveValue: { [unowned self] response in self.isLoading = false - self.isRefresh = false + let responseData = response.data + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) + + if let data = decoded.data, decoded.success { + self.liveNowItems = data.liveOnAirRoomList + self.liveReservationItems = data.liveReservationRoomList + self.recommendLiveItems = data.recommendLiveList + self.followedChannelItems = data.followingChannelList + self.communityPostItems = data.communityPostList + self.replayLiveItems = data.replayLive + self.latestFinishedLiveItems = data.latestFinishedLiveList + } else { + if let message = decoded.message { + self.errorMessage = message + } else { + self.errorMessage = "라이브에 입장하지 못했습니다.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + } + + self.isShowPopup = true + } + } catch { + self.errorMessage = "라이브에 입장하지 못했습니다.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.isShowPopup = true + } } .store(in: &subscription) } @@ -245,7 +190,7 @@ final class LiveViewModel: ObservableObject { let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) if decoded.success { - getSummary() + getLiveMain() enterRoom(roomId: roomId) } else { if let message = decoded.message { @@ -463,7 +408,7 @@ final class LiveViewModel: ObservableObject { onClickParticipant: {}, onClickReservation: { self.reservationLiveRoom(roomId: roomId) }, onClickStart: { self.startLive(roomId: roomId) }, - onClickCancel: { self.getSummary() } + onClickCancel: { self.getLiveMain() } ) ) } @@ -528,7 +473,7 @@ final class LiveViewModel: ObservableObject { let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) if let response = decoded.data, decoded.success { - self.getSummary() + self.getLiveMain() AppState.shared.setAppStep(step: .liveReservationComplete(response: response)) } else { if let message = decoded.message { @@ -546,156 +491,4 @@ final class LiveViewModel: ObservableObject { } .store(in: &subscription) } - - private func getFollowedChannelList() { - followedChannelItems.removeAll() - isFollowedChannelLoading = true - - liveRecommendRepository.getFollowedChannelList() - .sink { result in - switch result { - case .finished: - DEBUG_LOG("finish") - case .failure(let error): - ERROR_LOG(error.localizedDescription) - } - } receiveValue: { [unowned self] response in - self.isFollowedChannelLoading = false - let responseData = response.data - - do { - let jsonDecoder = JSONDecoder() - let decoded = try jsonDecoder.decode(ApiResponse<[GetRecommendChannelResponse]>.self, from: responseData) - - if let data = decoded.data, decoded.success { - self.followedChannelItems.append(contentsOf: data) - } else { - if let message = decoded.message { - self.errorMessage = message - } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - } - self.isShowPopup = true - } - } catch { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - self.isShowPopup = true - } - } - .store(in: &subscription) - } - - private func getRecommendChannelList() { - recommendChannelItems.removeAll() - isRecommendChannelLoading = true - - liveRecommendRepository.getRecommendChannelList() - .sink { result in - switch result { - case .finished: - DEBUG_LOG("finish") - case .failure(let error): - ERROR_LOG(error.localizedDescription) - } - } receiveValue: { [unowned self] response in - self.isRecommendChannelLoading = false - let responseData = response.data - - do { - let jsonDecoder = JSONDecoder() - let decoded = try jsonDecoder.decode(ApiResponse<[GetRecommendChannelResponse]>.self, from: responseData) - - if let data = decoded.data, decoded.success { - self.recommendChannelItems.append(contentsOf: data) - } else { - if let message = decoded.message { - self.errorMessage = message - } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - } - self.isShowPopup = true - } - } catch { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - self.isShowPopup = true - } - } - .store(in: &subscription) - } - - private func getRecommendLive() { - recommendLiveItems.removeAll() - isRecommendLiveLoading = true - - liveRecommendRepository.getRecommendLive() - .sink { result in - switch result { - case .finished: - DEBUG_LOG("finish") - case .failure(let error): - ERROR_LOG(error.localizedDescription) - } - } receiveValue: { [unowned self] response in - self.isRecommendLiveLoading = false - let responseData = response.data - - do { - let jsonDecoder = JSONDecoder() - let decoded = try jsonDecoder.decode(ApiResponse<[GetRecommendLiveResponse]>.self, from: responseData) - - if let data = decoded.data, decoded.success { - self.recommendLiveItems.append(contentsOf: data) - } else { - if let message = decoded.message { - self.errorMessage = message - } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - } - self.isShowPopup = true - } - } catch { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - self.isShowPopup = true - } - } - .store(in: &subscription) - } - - private func getLatestPostListFromCreatorsYouFollow() { - communityPostItems.removeAll() - - creatorCommunityRepository.getLatestPostListFromCreatorsYouFollow() - .sink { result in - switch result { - case .finished: - DEBUG_LOG("finish") - case .failure(let error): - ERROR_LOG(error.localizedDescription) - } - } receiveValue: { [unowned self] response in - self.isRecommendLiveLoading = false - let responseData = response.data - - do { - let jsonDecoder = JSONDecoder() - let decoded = try jsonDecoder.decode(ApiResponse<[GetCommunityPostListResponse]>.self, from: responseData) - - if let data = decoded.data, decoded.success { - self.communityPostItems.append(contentsOf: data) - } else { - if let message = decoded.message { - self.errorMessage = message - } else { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - } - self.isShowPopup = true - } - } catch { - self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." - self.isShowPopup = true - } - } - .store(in: &subscription) - - } }