feat(chat-character): 추천 캐릭터 섹션 추가 및 새로고침 API 반영

This commit is contained in:
Yu Sung
2025-11-14 01:46:07 +09:00
parent 2c74bb743b
commit 74212405a4
5 changed files with 96 additions and 1 deletions

View File

@@ -15,6 +15,7 @@ enum CharacterApi {
case getMyCharacterImageList(characterId: Int64, page: Int, size: Int)
case purchaseCharacterImage(imageId: Int)
case getRecentCharacters(page: Int, size: Int)
case refreshRecommendCharacters
}
extension CharacterApi: TargetType {
@@ -39,6 +40,9 @@ extension CharacterApi: TargetType {
case .getRecentCharacters:
return "/api/chat/character/recent"
case .refreshRecommendCharacters:
return "/api/chat/character/recommend"
}
}
@@ -54,7 +58,7 @@ extension CharacterApi: TargetType {
var task: Moya.Task {
switch self {
case .getCharacterHome, .getCharacterDetail:
case .getCharacterHome, .getCharacterDetail, .refreshRecommendCharacters:
return .requestPlain
case .getRecentCharacters(let page, let size):

View File

@@ -10,4 +10,5 @@ struct CharacterHomeResponse: Decodable {
let recentCharacters: [RecentCharacter]
let popularCharacters: [Character]
let newCharacters: [Character]
let recommendCharacters: [Character]
}

View File

@@ -16,4 +16,8 @@ class CharacterRepository {
func getCharacterMain() -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.getCharacterHome)
}
func refreshRecommendCharacters() -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.refreshRecommendCharacters)
}
}

View File

@@ -63,6 +63,51 @@ struct CharacterView: View {
}
)
}
if !viewModel.recommendCharacters.isEmpty {
VStack(alignment: .leading, spacing: 16) {
HStack {
Text("추천 캐릭터")
.font(.custom(Font.preBold.rawValue, size: 24))
.foregroundColor(.white)
Spacer()
Image("ic_refresh")
.onTapGesture {
viewModel.refreshRecommendCharacters()
}
}
.padding(.horizontal, 24)
let horizontalPadding: CGFloat = 24
let gridSpacing: CGFloat = 16
let width = (screenSize().width - (horizontalPadding * 2) - gridSpacing) / 2
LazyVGrid(
columns: Array(
repeating: GridItem(
.flexible(),
spacing: gridSpacing,
alignment: .topLeading
),
count: 2
),
alignment: .leading,
spacing: gridSpacing
) {
ForEach(viewModel.recommendCharacters.indices, id: \.self) { idx in
CharacterItemView(
character: viewModel.recommendCharacters[idx],
size: width,
rank: idx + 1,
isShowRank: false
)
}
}
.padding(.horizontal, horizontalPadding)
}
}
}
.padding(.bottom, 24)
}

View File

@@ -15,6 +15,7 @@ final class CharacterViewModel: ObservableObject {
@Published private(set) var recentCharacters: [RecentCharacter] = []
@Published private(set) var popularCharacters: [Character] = []
@Published private(set) var newCharacters: [Character] = []
@Published private(set) var recommendCharacters: [Character] = []
@Published var isLoading: Bool = false
@Published var errorMessage: String = ""
@@ -48,6 +49,46 @@ final class CharacterViewModel: ObservableObject {
self.recentCharacters = data.recentCharacters
self.popularCharacters = data.popularCharacters
self.newCharacters = data.newCharacters
self.recommendCharacters = data.recommendCharacters
} else {
if let message = decoded.message {
self.errorMessage = message
} else {
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
}
self.isShowPopup = true
}
self.isLoading = false
} catch {
self.isLoading = false
self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다."
self.isShowPopup = true
}
}
.store(in: &subscription)
}
func refreshRecommendCharacters() {
isLoading = true
repository.refreshRecommendCharacters()
.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
do {
let jsonDecoder = JSONDecoder()
let decoded = try jsonDecoder.decode(ApiResponse<[Character]>.self, from: responseData)
if let data = decoded.data, decoded.success {
self.recommendCharacters = data
} else {
if let message = decoded.message {
self.errorMessage = message