diff --git a/SodaLive/Sources/App/AppStep.swift b/SodaLive/Sources/App/AppStep.swift index 54b5e53..984b40c 100644 --- a/SodaLive/Sources/App/AppStep.swift +++ b/SodaLive/Sources/App/AppStep.swift @@ -129,4 +129,6 @@ enum AppStep { case seriesContentAll(seriesId: Int, seriesTitle: String) case tempCanPayment(orderType: OrderType, contentId: Int, title: String, can: Int) + + case blockList } diff --git a/SodaLive/Sources/ContentView.swift b/SodaLive/Sources/ContentView.swift index 6b6251c..f2b1e9a 100644 --- a/SodaLive/Sources/ContentView.swift +++ b/SodaLive/Sources/ContentView.swift @@ -189,7 +189,9 @@ struct ContentView: View { case .tempCanPayment(let orderType, let contentId, let title, let can): CanPaymentTempView(orderType: orderType, contentId: contentId, title: title, can: can) - + + case .blockList: + BlockMemberListView() default: EmptyView() diff --git a/SodaLive/Sources/MyPage/Block/BlockMemberListView.swift b/SodaLive/Sources/MyPage/Block/BlockMemberListView.swift new file mode 100644 index 0000000..a9da240 --- /dev/null +++ b/SodaLive/Sources/MyPage/Block/BlockMemberListView.swift @@ -0,0 +1,89 @@ +// +// BlockMemberListView.swift +// SodaLive +// +// Created by klaus on 9/4/24. +// + +import SwiftUI + +struct BlockMemberListView: View { + + @StateObject var viewModel = BlockMemberListViewModel() + + var body: some View { + BaseView(isLoading: $viewModel.isLoading) { + VStack(spacing: 0) { + DetailNavigationBar(title: "차단 리스트") + + HStack(spacing: 0) { + Text("총 ") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color.grayee) + + Text("\(viewModel.totalCount)") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color.mainRed3) + + Text(" 명") + .font(.custom(Font.medium.rawValue, size: 13.3)) + .foregroundColor(Color.grayee) + + Spacer() + } + .padding(.horizontal, 13.3) + .padding(.top, 6.7) + + if viewModel.totalCount > 0 { + ScrollView(.vertical, showsIndicators: false) { + VStack(spacing: 13.3) { + ForEach(0..() + + @Published var isLoading = false + @Published var errorMessage = "" + @Published var isShowPopup = false + + @Published var blockedMemberList = [GetBlockedMemberListItem]() + @Published var totalCount = 0 + + var page = 1 + var isLast = false + private let pageSize = 10 + + func getBlockedMemberList() { + if (!isLast && !isLoading) { + isLoading = true + + userRepository.getBlockedMemberList(page: page, size: pageSize) + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { [unowned self] response in + self.isLoading = false + let responseData = response.data + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponse.self, from: responseData) + self.isLoading = false + + if let data = decoded.data, decoded.success { + if page == 1 { + blockedMemberList.removeAll() + } + + self.totalCount = data.totalCount + + if !data.items.isEmpty { + page += 1 + self.blockedMemberList.append(contentsOf: data.items) + } else { + isLast = true + } + } else { + if let message = decoded.message { + self.errorMessage = message + } else { + self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + } + + self.isShowPopup = true + } + } catch { + self.errorMessage = "다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." + self.isShowPopup = true + self.isLoading = false + } + } + .store(in: &subscription) + } else { + isLoading = false + } + } + + func blockMember(userId: Int) { + userRepository.memberBlock(userId: userId) + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { _ in } + .store(in: &subscription) + } + + func unBlockMember(userId: Int) { + userRepository.memberUnBlock(userId: userId) + .sink { result in + switch result { + case .finished: + DEBUG_LOG("finish") + case .failure(let error): + ERROR_LOG(error.localizedDescription) + } + } receiveValue: { _ in } + .store(in: &subscription) + } +} diff --git a/SodaLive/Sources/MyPage/Block/BlockedMemberListItemView.swift b/SodaLive/Sources/MyPage/Block/BlockedMemberListItemView.swift new file mode 100644 index 0000000..af1324c --- /dev/null +++ b/SodaLive/Sources/MyPage/Block/BlockedMemberListItemView.swift @@ -0,0 +1,92 @@ +// +// BlockedMemberListItemView.swift +// SodaLive +// +// Created by klaus on 9/4/24. +// + +import SwiftUI +import Kingfisher + +struct BlockedMemberListItemView: View { + + let item: GetBlockedMemberListItem + let blockMember: (Int) -> Void + let unBlockMember: (Int) -> Void + + @State private var isBlocked = true + + var body: some View { + VStack(spacing: 13.3) { + HStack(spacing: 0) { + KFImage(URL(string: item.profileImageUrl)) + .resizable() + .frame(width: 60, height: 60) + .clipShape(Circle()) + + Text(item.nickname) + .font(.custom(Font.medium.rawValue, size: 16.7)) + .foregroundColor(Color.grayee) + .padding(.leading, 13.3) + + Spacer() + + Text(isBlocked ? "차단해제" : "차단") + .font(.custom(Font.medium.rawValue, size: 12)) + .foregroundColor(Color.button) + .frame(minWidth: 83) + .padding(.vertical, 7) + .background(Color.button.opacity(isBlocked ? 0.2 : 0.0)) + .cornerRadius(13.3) + .overlay( + RoundedRectangle(cornerRadius: 13.3) + .stroke(lineWidth: 1) + .foregroundColor(Color.button) + ) + .onTapGesture { + if isBlocked { + unBlockMember(item.memberId) + } else { + blockMember(item.memberId) + } + + isBlocked = !isBlocked + } + } + + Rectangle() + .foregroundColor(Color.gray59) + .frame(height: 0.5) + } + .onAppear { + isBlocked = item.isBlocked + } + } +} + +#Preview("차단됨") { + BlockedMemberListItemView( + item: GetBlockedMemberListItem( + memberId: 1, + nickname: "유저1", + profileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + isBlocked: true + ), + blockMember: { _ in }, + unBlockMember: { _ in } + ) +} + + +#Preview("차단해제됨") { + BlockedMemberListItemView( + item: GetBlockedMemberListItem( + memberId: 1, + nickname: "유저1", + profileImageUrl: "https://test-cf.sodalive.net/profile/default-profile.png", + isBlocked: false + ), + blockMember: { _ in }, + unBlockMember: { _ in } + ) +} diff --git a/SodaLive/Sources/MyPage/Block/GetBlockedMemberListResponse.swift b/SodaLive/Sources/MyPage/Block/GetBlockedMemberListResponse.swift new file mode 100644 index 0000000..5538def --- /dev/null +++ b/SodaLive/Sources/MyPage/Block/GetBlockedMemberListResponse.swift @@ -0,0 +1,18 @@ +// +// GetBlockedMemberListResponse.swift +// SodaLive +// +// Created by klaus on 9/4/24. +// + +struct GetBlockedMemberListResponse: Decodable { + let totalCount: Int + let items: [GetBlockedMemberListItem] +} + +struct GetBlockedMemberListItem: Decodable { + let memberId: Int + let nickname: String + let profileImageUrl: String + let isBlocked: Bool +} diff --git a/SodaLive/Sources/MyPage/MyPageView.swift b/SodaLive/Sources/MyPage/MyPageView.swift index 83e01a8..ab4aa75 100644 --- a/SodaLive/Sources/MyPage/MyPageView.swift +++ b/SodaLive/Sources/MyPage/MyPageView.swift @@ -87,11 +87,30 @@ struct MyPageView: View { .padding(.vertical, 13.3) .font(.custom(Font.bold.rawValue, size: 15.3)) .foregroundColor(Color.grayee) - .background(Color.button) .cornerRadius(6.7) + .overlay( + RoundedRectangle(cornerRadius: 6.7) + .stroke(Color.button, lineWidth: 1) + ) + .contentShape(Rectangle()) .onTapGesture { AppState.shared.setAppStep(step: .followingList) } + + Text("차단 리스트") + .frame(maxWidth: .infinity) + .padding(.vertical, 13.3) + .font(.custom(Font.bold.rawValue, size: 15.3)) + .foregroundColor(Color.grayee) + .cornerRadius(6.7) + .overlay( + RoundedRectangle(cornerRadius: 6.7) + .stroke(Color.button, lineWidth: 1) + ) + .contentShape(Rectangle()) + .onTapGesture { + AppState.shared.setAppStep(step: .blockList) + } } .padding(.top, 26.7) .padding(.horizontal, 13.3) diff --git a/SodaLive/Sources/User/UserApi.swift b/SodaLive/Sources/User/UserApi.swift index 9de0186..125fdd4 100644 --- a/SodaLive/Sources/User/UserApi.swift +++ b/SodaLive/Sources/User/UserApi.swift @@ -22,6 +22,7 @@ enum UserApi { case updatePushToken(request: PushTokenUpdateRequest) case creatorFollow(request: CreatorFollowRequest) case creatorUnFollow(request: CreatorFollowRequest) + case getBlockedMemberList(page: Int, size: Int) case memberBlock(request: MemberBlockRequest) case memberUnBlock(request: MemberBlockRequest) case getMyProfile @@ -79,7 +80,7 @@ extension UserApi: TargetType { case .creatorUnFollow: return "/member/creator/unfollow" - case .memberBlock: + case .getBlockedMemberList, .memberBlock: return "/member/block" case .memberUnBlock: @@ -111,7 +112,7 @@ extension UserApi: TargetType { .profileImageUpdate: return .post - case .searchUser, .getMypage, .getMemberInfo, .getMyProfile, .getChangeNicknamePrice, .checkNickname: + case .searchUser, .getMypage, .getMemberInfo, .getMyProfile, .getChangeNicknamePrice, .checkNickname, .getBlockedMemberList: return .get case .updatePushToken, .profileUpdate, .changeNickname, .updateIdfa: @@ -154,6 +155,10 @@ extension UserApi: TargetType { case .creatorUnFollow(let request): return .requestJSONEncodable(request) + case .getBlockedMemberList(let page, let size): + let parameters = ["page": page - 1, "size": size] as [String : Any] + return .requestParameters(parameters: parameters, encoding: URLEncoding.queryString) + case .memberBlock(let request): return .requestJSONEncodable(request) diff --git a/SodaLive/Sources/User/UserRepository.swift b/SodaLive/Sources/User/UserRepository.swift index 7854988..d59e3d7 100644 --- a/SodaLive/Sources/User/UserRepository.swift +++ b/SodaLive/Sources/User/UserRepository.swift @@ -77,6 +77,10 @@ final class UserRepository { return api.requestPublisher(.creatorUnFollow(request: CreatorFollowRequest(creatorId: creatorId))) } + func getBlockedMemberList(page: Int, size: Int) -> AnyPublisher { + return api.requestPublisher(.getBlockedMemberList(page: page, size: size)) + } + func memberBlock(userId: Int) -> AnyPublisher { return api.requestPublisher(.memberBlock(request: MemberBlockRequest(blockMemberId: userId))) }