차단 유저 리스트 페이지 추가
This commit is contained in:
		| @@ -129,4 +129,6 @@ enum AppStep { | ||||
|     case seriesContentAll(seriesId: Int, seriesTitle: String) | ||||
|          | ||||
|     case tempCanPayment(orderType: OrderType, contentId: Int, title: String, can: Int) | ||||
|      | ||||
|     case blockList | ||||
| } | ||||
|   | ||||
| @@ -190,6 +190,8 @@ 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() | ||||
|   | ||||
							
								
								
									
										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) | ||||
|                                             .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) | ||||
|   | ||||
| @@ -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) | ||||
|              | ||||
|   | ||||
| @@ -77,6 +77,10 @@ final class UserRepository { | ||||
|         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> { | ||||
|         return api.requestPublisher(.memberBlock(request: MemberBlockRequest(blockMemberId: userId))) | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Yu Sung
					Yu Sung