feat: 메인 라이브

- 여러개로 나눠져 있던 API 하나로 병합
This commit is contained in:
Yu Sung
2025-07-22 03:03:47 +09:00
parent 5d0b23d10d
commit 33195e5c8e
6 changed files with 97 additions and 251 deletions

View File

@@ -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
}

View File

@@ -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
)
}
}

View File

@@ -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]
}

View File

@@ -132,4 +132,13 @@ final class LiveRepository {
func heartStatus(roomId: Int) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.heartStatus(roomId: roomId))
}
func getLiveMain() -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(
.getLiveMain(
isAdultContentVisible: UserDefaults.isAdultContentVisible(),
contentType: ContentType(rawValue: UserDefaults.string(forKey: .contentPreference)) ?? ContentType.ALL
)
)
}
}

View File

@@ -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!)
}

View File

@@ -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<LiveMainResponse>.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<MakeLiveReservationResponse>.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)
}
}