diff --git a/SodaLive/Sources/Chat/Talk/TalkApi.swift b/SodaLive/Sources/Chat/Talk/TalkApi.swift new file mode 100644 index 0000000..501c920 --- /dev/null +++ b/SodaLive/Sources/Chat/Talk/TalkApi.swift @@ -0,0 +1,42 @@ +// +// TalkApi.swift +// SodaLive +// +// Created by klaus on 8/29/25. +// + +import Foundation +import Moya + +enum TalkApi { + case getTalkRooms +} + +extension TalkApi: TargetType { + var baseURL: URL { URL(string: BASE_URL)! } + + var path: String { + switch self { + case .getTalkRooms: + return "/api/chat/room/list" + } + } + + var method: Moya.Method { + switch self { + case .getTalkRooms: + return .get + } + } + + var task: Moya.Task { + switch self { + case .getTalkRooms: + return .requestPlain + } + } + + var headers: [String : String]? { + ["Authorization": "Bearer \(UserDefaults.string(forKey: UserDefaultsKey.token))"] + } +} diff --git a/SodaLive/Sources/Chat/Talk/TalkItemView.swift b/SodaLive/Sources/Chat/Talk/TalkItemView.swift new file mode 100644 index 0000000..051f7e7 --- /dev/null +++ b/SodaLive/Sources/Chat/Talk/TalkItemView.swift @@ -0,0 +1,87 @@ +// +// TalkItemView.swift +// SodaLive +// +// Created by klaus on 8/29/25. +// + +import SwiftUI +import Kingfisher + +struct TalkItemView: View { + + let item: TalkRoom + + @State private var backgroundColor = Color(hex: "009D68") + + var body: some View { + HStack(spacing: 13) { + KFImage(URL(string: item.imageUrl)) + .placeholder { Circle().fill(Color.gray.opacity(0.2)) } + .retry(maxCount: 2, interval: .seconds(1)) + .cancelOnDisappear(true) + .resizable() + .scaledToFill() + .frame(width: 76, height: 76) + .clipShape(Circle()) + + VStack(alignment: .leading, spacing: 6) { + HStack(spacing: 4) { + Text(item.title) + .font(.custom(Font.preBold.rawValue, size: 18)) + .foregroundColor(.white) + .lineLimit(1) + + Text(item.opponentType) + .font(.custom(Font.preRegular.rawValue, size: 12)) + .foregroundColor(Color(hex: "D9FCF4")) + .lineLimit(1) + .padding(.horizontal, 5) + .padding(.vertical, 1) + .background(backgroundColor) + .cornerRadius(6) + + Spacer() + + Text(item.lastMessageTimeLabel) + .font(.custom(Font.preRegular.rawValue, size: 12)) + .foregroundColor(Color(hex: "78909C")) + .lineLimit(1) + } + + if let message = item.lastMessagePreview { + Text(message) + .font(.custom(Font.preRegular.rawValue, size: 14)) + .foregroundColor(Color(hex: "b0bec5")) + .lineLimit(2) + .truncationMode(.tail) + } + } + } + .onAppear { + switch item.opponentType.lowercased() { + case "clone": + self.backgroundColor = Color(hex: "0020C9") + + case "creator": + self.backgroundColor = Color(hex: "F86660") + + default: + self.backgroundColor = Color(hex: "009D68") + } + } + } +} + +#Preview { + TalkItemView( + item: TalkRoom( + chatRoomId: 1, + title: "정인이", + imageUrl: "https://picsum.photos/200", + opponentType: "Character", + lastMessagePreview: "태풍온다잖아 ㅜㅜ\n조심해 어디 나가지 말고 집에만...", + lastMessageTimeLabel: "6월 15일" + ) + ) +} diff --git a/SodaLive/Sources/Chat/Talk/TalkRepository.swift b/SodaLive/Sources/Chat/Talk/TalkRepository.swift new file mode 100644 index 0000000..ab25a27 --- /dev/null +++ b/SodaLive/Sources/Chat/Talk/TalkRepository.swift @@ -0,0 +1,19 @@ +// +// TalkRepository.swift +// SodaLive +// +// Created by klaus on 8/29/25. +// + +import Foundation +import CombineMoya +import Combine +import Moya + +class TalkRepository { + private let api = MoyaProvider() + + func getTalkRooms() -> AnyPublisher { + return api.requestPublisher(.getTalkRooms) + } +} diff --git a/SodaLive/Sources/Chat/Talk/TalkRoom.swift b/SodaLive/Sources/Chat/Talk/TalkRoom.swift new file mode 100644 index 0000000..1ad897d --- /dev/null +++ b/SodaLive/Sources/Chat/Talk/TalkRoom.swift @@ -0,0 +1,15 @@ +// +// TalkRoom.swift +// SodaLive +// +// Created by klaus on 8/29/25. +// + +struct TalkRoom: Decodable { + let chatRoomId: Int + let title: String + let imageUrl: String + let opponentType: String + let lastMessagePreview: String? + let lastMessageTimeLabel: String +} diff --git a/SodaLive/Sources/Chat/Talk/TalkView.swift b/SodaLive/Sources/Chat/Talk/TalkView.swift index 1fcc8a6..cca8c34 100644 --- a/SodaLive/Sources/Chat/Talk/TalkView.swift +++ b/SodaLive/Sources/Chat/Talk/TalkView.swift @@ -8,17 +8,33 @@ import SwiftUI struct TalkView: View { - var body: some View { - VStack(spacing: 12) { - Spacer() - Text("톡 페이지 (준비중)") - .font(.custom(Font.preMedium.rawValue, size: 16)) - .multilineTextAlignment(.center) - Spacer() + + @StateObject var viewModel = TalkViewModel() + + var body: some View { + BaseView(isLoading: $viewModel.isLoading) { + if viewModel.talkRooms.isEmpty { + Text("대화 중인 톡이 없습니다") + .font(.custom(Font.preRegular.rawValue, size: 20)) + .foregroundColor(.white) + } else { + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 24) { + ForEach(0..() + + // MARK: - API + func getTalkRooms() { + isLoading = true + + repository.getTalkRooms() + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { response in + let responseData = response.data + self.isLoading = false + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponse<[TalkRoom]>.self, from: responseData) + + if let data = decoded.data, decoded.success { + self.talkRooms = 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) + } +} +