diff --git a/SodaLive/Resources/Localizable.xcstrings b/SodaLive/Resources/Localizable.xcstrings index 41e4ec0..04d97c0 100644 --- a/SodaLive/Resources/Localizable.xcstrings +++ b/SodaLive/Resources/Localizable.xcstrings @@ -2967,6 +2967,9 @@ } } } + }, + "누적" : { + }, "눌러서 잠금해제" : { "localizations" : { @@ -4136,22 +4139,6 @@ } } }, - "모집완료" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Recruitment closed" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "募集終了" - } - } - } - }, "목" : { "localizations" : { "en" : { @@ -4168,6 +4155,22 @@ } } }, + "모집완료" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Recruitment closed" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "募集終了" + } + } + } + }, "모집중" : { "localizations" : { "en" : { @@ -6888,54 +6891,6 @@ } } }, - "인기 캐릭터" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Popular" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "人気キャラ" - } - } - } - }, - "인기 캐릭터 채팅" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Top character" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "人気キャラチャット" - } - } - } - }, - "일" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sun" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "日" - } - } - } - }, "이메일" : { "localizations" : { "en" : { @@ -7000,6 +6955,22 @@ } } }, + "일" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Sun" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "日" + } + } + } + }, "이벤트" : { "localizations" : { "en" : { @@ -7096,6 +7067,38 @@ } } }, + "인기 캐릭터" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Popular" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "人気キャラ" + } + } + } + }, + "인기 캐릭터 채팅" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Top character" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "人気キャラチャット" + } + } + } + }, "인기 콘텐츠" : { "localizations" : { "en" : { @@ -7831,6 +7834,9 @@ } } } + }, + "주간" : { + }, "중복확인" : { "localizations" : { @@ -8632,22 +8638,6 @@ } } }, - "캔" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cans" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "CAN" - } - } - } - }, "캐릭터 정보" : { "localizations" : { "en" : { @@ -8664,6 +8654,22 @@ } } }, + "캔" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cans" + } + }, + "ja" : { + "stringUnit" : { + "state" : "translated", + "value" : "CAN" + } + } + } + }, "캔 충전" : { "localizations" : { "en" : { diff --git a/SodaLive/Sources/Explorer/ExplorerApi.swift b/SodaLive/Sources/Explorer/ExplorerApi.swift index a52c705..041ec05 100644 --- a/SodaLive/Sources/Explorer/ExplorerApi.swift +++ b/SodaLive/Sources/Explorer/ExplorerApi.swift @@ -18,7 +18,7 @@ enum ExplorerApi { case writeCheers(parentCheersId: Int?, creatorId: Int, content: String) case modifyCheers(request: PutModifyCheersRequest) case writeCreatorNotice(request: PostCreatorNoticeRequest) - case getCreatorProfileDonationRanking(userId: Int, page: Int, size: Int) + case getCreatorProfileDonationRanking(userId: Int, page: Int, size: Int, period: DonationRankingPeriod?) } extension ExplorerApi: TargetType { @@ -40,7 +40,7 @@ extension ExplorerApi: TargetType { case .getCreatorProfile(let userId, _): return "/explorer/profile/\(userId)" - case .getCreatorProfileDonationRanking(let userId, _, _): + case .getCreatorProfileDonationRanking(let userId, _, _, _): return "/explorer/profile/\(userId)/donation-rank" case .getFollowerList(let userId, _, _): @@ -112,12 +112,16 @@ extension ExplorerApi: TargetType { case .writeCreatorNotice(let request): return .requestJSONEncodable(request) - case .getCreatorProfileDonationRanking(_, let page, let size): - let parameters = [ + case .getCreatorProfileDonationRanking(_, let page, let size, let period): + var parameters = [ "page": page - 1, "size": size ] as [String : Any] + if let period { + parameters["period"] = period.rawValue + } + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) } } diff --git a/SodaLive/Sources/Explorer/ExplorerRepository.swift b/SodaLive/Sources/Explorer/ExplorerRepository.swift index a9ab384..37f910a 100644 --- a/SodaLive/Sources/Explorer/ExplorerRepository.swift +++ b/SodaLive/Sources/Explorer/ExplorerRepository.swift @@ -51,7 +51,19 @@ final class ExplorerRepository { return api.requestPublisher(.writeCreatorNotice(request: PostCreatorNoticeRequest(notice: notice))) } - func getCreatorProfileDonationRanking(userId: Int, page: Int, size: Int) -> AnyPublisher { - return api.requestPublisher(.getCreatorProfileDonationRanking(userId: userId, page: page, size: size)) + func getCreatorProfileDonationRanking( + userId: Int, + page: Int, + size: Int, + period: DonationRankingPeriod? + ) -> AnyPublisher { + return api.requestPublisher( + .getCreatorProfileDonationRanking( + userId: userId, + page: page, + size: size, + period: period + ) + ) } } diff --git a/SodaLive/Sources/Explorer/Profile/GetDonationAllResponse.swift b/SodaLive/Sources/Explorer/Profile/GetDonationAllResponse.swift index 5fc0348..9f7e5b8 100644 --- a/SodaLive/Sources/Explorer/Profile/GetDonationAllResponse.swift +++ b/SodaLive/Sources/Explorer/Profile/GetDonationAllResponse.swift @@ -12,6 +12,7 @@ struct GetDonationAllResponse: Decodable { let accumulatedCansLastWeek: Int let accumulatedCansThisMonth: Int let isVisibleDonationRank: Bool + let donationRankingPeriod: DonationRankingPeriod? let totalCount: Int let userDonationRanking: [UserDonationRankingResponse] } diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllView.swift index 56b311f..14e158a 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllView.swift @@ -47,6 +47,36 @@ struct UserProfileDonationAllView: View { .padding(.top, 13.3) .padding(.horizontal, 13.3) + HStack(spacing: 73) { + HStack(spacing: 8) { + Image(viewModel.donationRankingPeriod == .weekly ? "btn_square_select_checked" : "btn_square_select_normal") + .resizable() + .frame(width: 20, height: 20) + + Text(I18n.DonationRanking.weekly) + .appFont(size: 14, weight: .medium) + .foregroundColor(Color(hex: "eeeeee")) + } + .onTapGesture { + viewModel.selectDonationRankingPeriod(.weekly) + } + + HStack(spacing: 8) { + Image(viewModel.donationRankingPeriod == .cumulative ? "btn_square_select_checked" : "btn_square_select_normal") + .resizable() + .frame(width: 20, height: 20) + + Text(I18n.DonationRanking.cumulative) + .appFont(size: 14, weight: .medium) + .foregroundColor(Color(hex: "eeeeee")) + } + .onTapGesture { + viewModel.selectDonationRankingPeriod(.cumulative) + } + } + .padding(.top, 13.3) + .padding(.horizontal, 13.3) + VStack(spacing: 13.3) { HStack(spacing: 0) { Text("오늘") @@ -101,6 +131,25 @@ struct UserProfileDonationAllView: View { .cornerRadius(8) .padding(.top, 13.3) .padding(.horizontal, 13.3) + + HStack(spacing: 0) { + SeriesDetailTabView( + title: I18n.DonationRanking.weekly, + width: screenSize().width / 2, + isSelected: viewModel.selectedRankingPeriod == .weekly + ) { + viewModel.selectViewRankingPeriod(.weekly) + } + + SeriesDetailTabView( + title: I18n.DonationRanking.cumulative, + width: screenSize().width / 2, + isSelected: viewModel.selectedRankingPeriod == .cumulative + ) { + viewModel.selectViewRankingPeriod(.cumulative) + } + } + .padding(.top, 13.3) } HStack(alignment: .center, spacing: 0) { diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllViewModel.swift b/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllViewModel.swift index 5dee166..6a3027d 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllViewModel.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileDonationAllViewModel.swift @@ -23,17 +23,28 @@ final class UserProfileDonationAllViewModel: ObservableObject { @Published var accumulatedCansThisMonth = 0 @Published var isVisibleDonationRank = false @Published var userDonationRanking: [UserDonationRankingResponse] = [] + @Published var donationRankingPeriod: DonationRankingPeriod = .weekly + @Published var selectedRankingPeriod: DonationRankingPeriod? = nil var userId = 0 var page = 1 var isLast = false private let pageSize = 10 + private var hasLoadedDonationRankingPeriod = false func getCreatorProfileDonationRanking() { if (!isLast && !isLoading) { isLoading = true - repository.getCreatorProfileDonationRanking(userId: userId, page: page, size: pageSize) + let isCreator = userId == UserDefaults.int(forKey: .userId) + let period: DonationRankingPeriod? = isCreator ? selectedRankingPeriod : nil + + repository.getCreatorProfileDonationRanking( + userId: userId, + page: page, + size: pageSize, + period: period + ) .sink { result in switch result { case .finished: @@ -55,6 +66,15 @@ final class UserProfileDonationAllViewModel: ObservableObject { self.accumulatedCansThisMonth = data.accumulatedCansThisMonth self.isVisibleDonationRank = data.isVisibleDonationRank + if let period = data.donationRankingPeriod { + self.donationRankingPeriod = period + self.hasLoadedDonationRankingPeriod = true + + if self.selectedRankingPeriod == nil { + self.selectedRankingPeriod = period + } + } + if !data.userDonationRanking.isEmpty { page += 1 self.totalCount = data.totalCount @@ -79,6 +99,20 @@ final class UserProfileDonationAllViewModel: ObservableObject { .store(in: &subscription) } } + + func selectDonationRankingPeriod(_ period: DonationRankingPeriod) { + guard donationRankingPeriod != period else { return } + donationRankingPeriod = period + hasLoadedDonationRankingPeriod = true + updateDonationRankingPeriod(period) + } + + func selectViewRankingPeriod(_ period: DonationRankingPeriod) { + guard selectedRankingPeriod != period else { return } + selectedRankingPeriod = period + resetDonationRanking() + getCreatorProfileDonationRanking() + } func toggleVisibleDonationRank() { isLoading = true @@ -122,4 +156,49 @@ final class UserProfileDonationAllViewModel: ObservableObject { } .store(in: &subscription) } + + private func resetDonationRanking() { + totalCount = 0 + userDonationRanking = [] + page = 1 + isLast = false + } + + private func updateDonationRankingPeriod(_ period: DonationRankingPeriod) { + memberRepository.profileUpdate( + request: ProfileUpdateRequest( + email: UserDefaults.string(forKey: .email), + donationRankingPeriod: period + ) + ) + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { [unowned self] response in + let responseData = response.data + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) + + if !decoded.success { + 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) + } } diff --git a/SodaLive/Sources/I18n/I18n.swift b/SodaLive/Sources/I18n/I18n.swift index 3f06722..1160cb6 100644 --- a/SodaLive/Sources/I18n/I18n.swift +++ b/SodaLive/Sources/I18n/I18n.swift @@ -832,6 +832,11 @@ enum I18n { static var comments: String { pick(ko: "댓글", en: "Comments", ja: "コメント") } static var likes: String { pick(ko: "좋아요", en: "Likes", ja: "いいね") } } + + enum DonationRanking { + static var weekly: String { pick(ko: "주간", en: "Weekly", ja: "週間") } + static var cumulative: String { pick(ko: "누적", en: "Cumulative", ja: "累計") } + } enum Tab { // 탭/도메인 diff --git a/SodaLive/Sources/MyPage/Profile/ProfileUpdateRequest.swift b/SodaLive/Sources/MyPage/Profile/ProfileUpdateRequest.swift index 6923595..cf4e16b 100644 --- a/SodaLive/Sources/MyPage/Profile/ProfileUpdateRequest.swift +++ b/SodaLive/Sources/MyPage/Profile/ProfileUpdateRequest.swift @@ -19,6 +19,7 @@ struct ProfileUpdateRequest: Encodable { var websiteUrl: String? = nil var blogUrl: String? = nil var isVisibleDonationRank: Bool? = nil + var donationRankingPeriod: DonationRankingPeriod? = nil let container: String = "ios" var insertTags: [String]? = nil var removeTags: [String]? = nil diff --git a/SodaLive/Sources/User/DonationRankingPeriod.swift b/SodaLive/Sources/User/DonationRankingPeriod.swift new file mode 100644 index 0000000..2f8b49b --- /dev/null +++ b/SodaLive/Sources/User/DonationRankingPeriod.swift @@ -0,0 +1,13 @@ +// +// DonationRankingPeriod.swift +// SodaLive +// +// Created by Codex on 2026/02/03. +// + +import Foundation + +enum DonationRankingPeriod: String, Codable { + case weekly = "WEEKLY" + case cumulative = "CUMULATIVE" +}