차단 유저 리스트 페이지 추가
This commit is contained in:
		| @@ -129,4 +129,6 @@ enum AppStep { | |||||||
|     case seriesContentAll(seriesId: Int, seriesTitle: String) |     case seriesContentAll(seriesId: Int, seriesTitle: String) | ||||||
|          |          | ||||||
|     case tempCanPayment(orderType: OrderType, contentId: Int, title: String, can: Int) |     case tempCanPayment(orderType: OrderType, contentId: Int, title: String, can: Int) | ||||||
|  |      | ||||||
|  |     case blockList | ||||||
| } | } | ||||||
|   | |||||||
| @@ -189,7 +189,9 @@ struct ContentView: View { | |||||||
|                  |                  | ||||||
|             case .tempCanPayment(let orderType, let contentId, let title, let can): |             case .tempCanPayment(let orderType, let contentId, let title, let can): | ||||||
|                 CanPaymentTempView(orderType: orderType, contentId: contentId, title: title, can: can) |                 CanPaymentTempView(orderType: orderType, contentId: contentId, title: title, can: can) | ||||||
|                  |              | ||||||
|  |             case .blockList: | ||||||
|  |                 BlockMemberListView() | ||||||
|                  |                  | ||||||
|             default: |             default: | ||||||
|                 EmptyView() |                 EmptyView() | ||||||
|   | |||||||
							
								
								
									
										89
									
								
								SodaLive/Sources/MyPage/Block/BlockMemberListView.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								SodaLive/Sources/MyPage/Block/BlockMemberListView.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -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..<viewModel.blockedMemberList.count, id: \.self) { index in | ||||||
|  |                                 let item = viewModel.blockedMemberList[index] | ||||||
|  |                                  | ||||||
|  |                                 BlockedMemberListItemView( | ||||||
|  |                                     item: item, | ||||||
|  |                                     blockMember: { viewModel.blockMember(userId: $0) }, | ||||||
|  |                                     unBlockMember: { viewModel.unBlockMember(userId: $0) } | ||||||
|  |                                 ) | ||||||
|  |                                 .padding(.horizontal, 13.3) | ||||||
|  |                                 .onAppear { | ||||||
|  |                                     if index == viewModel.blockedMemberList.count - 1 { | ||||||
|  |                                         viewModel.getBlockedMemberList() | ||||||
|  |                                     } | ||||||
|  |                                 } | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         .padding(.top, 26.7) | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     Text("차단한 유저가 없습니다.") | ||||||
|  |                         .font(.custom(Font.medium.rawValue, size: 13.3)) | ||||||
|  |                         .foregroundColor(Color.grayee) | ||||||
|  |                         .frame(maxHeight: .infinity) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             .onAppear { | ||||||
|  |                 viewModel.getBlockedMemberList() | ||||||
|  |             } | ||||||
|  |             .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) { | ||||||
|  |                 HStack { | ||||||
|  |                     Spacer() | ||||||
|  |                     Text(viewModel.errorMessage) | ||||||
|  |                         .padding(.vertical, 13.3) | ||||||
|  |                         .frame(width: screenSize().width - 66.7, alignment: .center) | ||||||
|  |                         .font(.custom(Font.medium.rawValue, size: 12)) | ||||||
|  |                         .background(Color.button) | ||||||
|  |                         .foregroundColor(Color.white) | ||||||
|  |                         .multilineTextAlignment(.leading) | ||||||
|  |                         .cornerRadius(20) | ||||||
|  |                         .padding(.bottom, 66.7) | ||||||
|  |                     Spacer() | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #Preview { | ||||||
|  |     BlockMemberListView() | ||||||
|  | } | ||||||
							
								
								
									
										107
									
								
								SodaLive/Sources/MyPage/Block/BlockMemberListViewModel.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								SodaLive/Sources/MyPage/Block/BlockMemberListViewModel.swift
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | // | ||||||
|  | //  BlockMemberListViewModel.swift | ||||||
|  | //  SodaLive | ||||||
|  | // | ||||||
|  | //  Created by klaus on 9/4/24. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | import Foundation | ||||||
|  | import Moya | ||||||
|  | import Combine | ||||||
|  |  | ||||||
|  | final class BlockMemberListViewModel: ObservableObject { | ||||||
|  |     private var userRepository = UserRepository() | ||||||
|  |     private var subscription = Set<AnyCancellable>() | ||||||
|  |      | ||||||
|  |     @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<GetBlockedMemberListResponse>.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) | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -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 } | ||||||
|  |     ) | ||||||
|  | } | ||||||
| @@ -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 | ||||||
|  | } | ||||||
| @@ -87,11 +87,30 @@ struct MyPageView: View { | |||||||
|                                             .padding(.vertical, 13.3) |                                             .padding(.vertical, 13.3) | ||||||
|                                             .font(.custom(Font.bold.rawValue, size: 15.3)) |                                             .font(.custom(Font.bold.rawValue, size: 15.3)) | ||||||
|                                             .foregroundColor(Color.grayee) |                                             .foregroundColor(Color.grayee) | ||||||
|                                             .background(Color.button) |  | ||||||
|                                             .cornerRadius(6.7) |                                             .cornerRadius(6.7) | ||||||
|  |                                             .overlay( | ||||||
|  |                                                 RoundedRectangle(cornerRadius: 6.7) | ||||||
|  |                                                     .stroke(Color.button, lineWidth: 1) | ||||||
|  |                                             ) | ||||||
|  |                                             .contentShape(Rectangle()) | ||||||
|                                             .onTapGesture { |                                             .onTapGesture { | ||||||
|                                                 AppState.shared.setAppStep(step: .followingList) |                                                 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(.top, 26.7) | ||||||
|                                     .padding(.horizontal, 13.3) |                                     .padding(.horizontal, 13.3) | ||||||
|   | |||||||
| @@ -22,6 +22,7 @@ enum UserApi { | |||||||
|     case updatePushToken(request: PushTokenUpdateRequest) |     case updatePushToken(request: PushTokenUpdateRequest) | ||||||
|     case creatorFollow(request: CreatorFollowRequest) |     case creatorFollow(request: CreatorFollowRequest) | ||||||
|     case creatorUnFollow(request: CreatorFollowRequest) |     case creatorUnFollow(request: CreatorFollowRequest) | ||||||
|  |     case getBlockedMemberList(page: Int, size: Int) | ||||||
|     case memberBlock(request: MemberBlockRequest) |     case memberBlock(request: MemberBlockRequest) | ||||||
|     case memberUnBlock(request: MemberBlockRequest) |     case memberUnBlock(request: MemberBlockRequest) | ||||||
|     case getMyProfile |     case getMyProfile | ||||||
| @@ -79,7 +80,7 @@ extension UserApi: TargetType { | |||||||
|         case .creatorUnFollow: |         case .creatorUnFollow: | ||||||
|             return "/member/creator/unfollow" |             return "/member/creator/unfollow" | ||||||
|              |              | ||||||
|         case .memberBlock: |         case .getBlockedMemberList, .memberBlock: | ||||||
|             return "/member/block" |             return "/member/block" | ||||||
|              |              | ||||||
|         case .memberUnBlock: |         case .memberUnBlock: | ||||||
| @@ -111,7 +112,7 @@ extension UserApi: TargetType { | |||||||
|                 .profileImageUpdate: |                 .profileImageUpdate: | ||||||
|             return .post |             return .post | ||||||
|              |              | ||||||
|         case .searchUser, .getMypage, .getMemberInfo, .getMyProfile, .getChangeNicknamePrice, .checkNickname: |         case .searchUser, .getMypage, .getMemberInfo, .getMyProfile, .getChangeNicknamePrice, .checkNickname, .getBlockedMemberList: | ||||||
|             return .get |             return .get | ||||||
|              |              | ||||||
|         case .updatePushToken, .profileUpdate, .changeNickname, .updateIdfa: |         case .updatePushToken, .profileUpdate, .changeNickname, .updateIdfa: | ||||||
| @@ -154,6 +155,10 @@ extension UserApi: TargetType { | |||||||
|         case .creatorUnFollow(let request): |         case .creatorUnFollow(let request): | ||||||
|             return .requestJSONEncodable(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): |         case .memberBlock(let request): | ||||||
|             return .requestJSONEncodable(request) |             return .requestJSONEncodable(request) | ||||||
|              |              | ||||||
|   | |||||||
| @@ -77,6 +77,10 @@ final class UserRepository { | |||||||
|         return api.requestPublisher(.creatorUnFollow(request: CreatorFollowRequest(creatorId: creatorId))) |         return api.requestPublisher(.creatorUnFollow(request: CreatorFollowRequest(creatorId: creatorId))) | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     func getBlockedMemberList(page: Int, size: Int) -> AnyPublisher<Response, MoyaError> { | ||||||
|  |         return api.requestPublisher(.getBlockedMemberList(page: page, size: size)) | ||||||
|  |     } | ||||||
|  |      | ||||||
|     func memberBlock(userId: Int) -> AnyPublisher<Response, MoyaError> { |     func memberBlock(userId: Int) -> AnyPublisher<Response, MoyaError> { | ||||||
|         return api.requestPublisher(.memberBlock(request: MemberBlockRequest(blockMemberId: userId))) |         return api.requestPublisher(.memberBlock(request: MemberBlockRequest(blockMemberId: userId))) | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung