From 7c5b30335e06d43e20ba8b866237a0a0d60d59b6 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Sat, 7 Sep 2024 03:58:52 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BB=A4=EB=AE=A4=EB=8B=88=ED=8B=B0=20?= =?UTF-8?q?=EB=8C=93=EA=B8=80,=20=ED=8C=AC=ED=86=A0=ED=81=AC,=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=20=EB=8C=93=EA=B8=80=20-=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=ED=84=B0?= =?UTF-8?q?=EC=B9=98=EC=8B=9C=20=EC=B0=A8=EB=8B=A8,=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=EA=B0=80=20=EA=B0=80=EB=8A=A5=ED=95=9C=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Comment/AudioContentCommentItemView.swift | 20 +- .../Comment/AudioContentCommentListView.swift | 11 + .../Comment/AudioContentListReplyView.swift | 17 +- .../Sources/Dialog/MemberProfileDialog.swift | 174 ++++++++++++++++ .../CreatorCommunityCommentItemView.swift | 12 +- .../CreatorCommunityCommentListView.swift | 12 ++ .../CreatorCommunityCommentReplyView.swift | 17 +- .../FanTalk/UserProfileFanTalkAllView.swift | 31 ++- .../UserProfileFanTalkCheersItemView.swift | 38 ++-- .../FanTalk/UserProfileFanTalkView.swift | 26 ++- .../Explorer/Profile/UserProfileView.swift | 11 + .../User/GetMemberProfileResponse.swift | 13 ++ SodaLive/Sources/User/UserApi.swift | 12 +- SodaLive/Sources/User/UserRepository.swift | 4 + SodaLive/Sources/User/UserViewModel.swift | 192 ++++++++++++++++++ 15 files changed, 537 insertions(+), 53 deletions(-) create mode 100644 SodaLive/Sources/Dialog/MemberProfileDialog.swift create mode 100644 SodaLive/Sources/User/GetMemberProfileResponse.swift create mode 100644 SodaLive/Sources/User/UserViewModel.swift diff --git a/SodaLive/Sources/Content/Detail/Comment/AudioContentCommentItemView.swift b/SodaLive/Sources/Content/Detail/Comment/AudioContentCommentItemView.swift index 5285145..30c8309 100644 --- a/SodaLive/Sources/Content/Detail/Comment/AudioContentCommentItemView.swift +++ b/SodaLive/Sources/Content/Detail/Comment/AudioContentCommentItemView.swift @@ -17,6 +17,7 @@ struct AudioContentCommentItemView: View { let modifyComment: (Int, String) -> Void let onClickDelete: (Int) -> Void + let onClickProfile: (Int) -> Void @State var isShowPopupMenu: Bool = false @State var isModeModify: Bool = false @@ -30,6 +31,11 @@ struct AudioContentCommentItemView: View { .resizable() .frame(width: 40, height: 40) .clipShape(Circle()) + .onTapGesture { + if UserDefaults.int(forKey: .userId) != commentItem.writerId { + onClickProfile(commentItem.writerId) + } + } VStack(alignment: .leading, spacing: 0) { HStack(spacing: 6.7) { @@ -95,8 +101,8 @@ struct AudioContentCommentItemView: View { .autocapitalization(.none) .disableAutocorrection(true) .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) - .accentColor(Color(hex: "3bb9f1")) + .foregroundColor(Color.grayee) + .accentColor(Color.button) .keyboardType(.default) .padding(.horizontal, 13.3) @@ -114,7 +120,7 @@ struct AudioContentCommentItemView: View { isModeModify = false } } - .background(Color(hex: "232323")) + .background(Color.gray23) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) @@ -151,7 +157,7 @@ struct AudioContentCommentItemView: View { .padding(.leading, 46.7) Rectangle() - .foregroundColor(Color(hex: "595959")) + .foregroundColor(Color.gray59) .frame(height: 0.5) .padding(.top, 16.7) } @@ -161,7 +167,7 @@ struct AudioContentCommentItemView: View { if commentItem.writerId == UserDefaults.int(forKey: .userId) { Text("수정") .font(.custom(Font.medium.rawValue, size: 14)) - .foregroundColor(Color(hex: "777777")) + .foregroundColor(Color.gray77) .onTapGesture { isModeModify = true isShowPopupMenu = false @@ -173,7 +179,7 @@ struct AudioContentCommentItemView: View { { Text("삭제") .font(.custom(Font.medium.rawValue, size: 14)) - .foregroundColor(Color(hex: "777777")) + .foregroundColor(Color.gray77) .onTapGesture { onClickDelete(commentItem.id) isShowPopupMenu = false @@ -181,7 +187,7 @@ struct AudioContentCommentItemView: View { } } .padding(10) - .background(Color(hex: "222222")) + .background(Color.gray22) } } .onAppear { comment = commentItem.comment } diff --git a/SodaLive/Sources/Content/Detail/Comment/AudioContentCommentListView.swift b/SodaLive/Sources/Content/Detail/Comment/AudioContentCommentListView.swift index 44e0aef..0ba52ec 100644 --- a/SodaLive/Sources/Content/Detail/Comment/AudioContentCommentListView.swift +++ b/SodaLive/Sources/Content/Detail/Comment/AudioContentCommentListView.swift @@ -21,6 +21,9 @@ struct AudioContentCommentListView: View { @State private var commentId: Int = 0 @State private var isShowDeletePopup: Bool = false + @State private var memberId: Int = 0 + @State private var isShowMemberProfilePopup: Bool = false + var body: some View { NavigationView { ZStack { @@ -137,6 +140,10 @@ struct AudioContentCommentListView: View { onClickDelete: { commentId = $0 isShowDeletePopup = true + }, + onClickProfile: { + memberId = $0 + isShowMemberProfilePopup = true } ) .padding(.horizontal, 26.7) @@ -169,6 +176,10 @@ struct AudioContentCommentListView: View { ) } + if isShowMemberProfilePopup { + MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId) + } + if viewModel.isLoading { LoadingView() } diff --git a/SodaLive/Sources/Content/Detail/Comment/AudioContentListReplyView.swift b/SodaLive/Sources/Content/Detail/Comment/AudioContentListReplyView.swift index f4ce582..d6abf5e 100644 --- a/SodaLive/Sources/Content/Detail/Comment/AudioContentListReplyView.swift +++ b/SodaLive/Sources/Content/Detail/Comment/AudioContentListReplyView.swift @@ -20,6 +20,9 @@ struct AudioContentListReplyView: View { @State private var commentId: Int = 0 @State private var isShowDeletePopup: Bool = false + @State private var memberId: Int = 0 + @State private var isShowMemberProfilePopup: Bool = false + var body: some View { ZStack { VStack(spacing: 0) { @@ -98,7 +101,11 @@ struct AudioContentListReplyView: View { isReplyComment: true, isShowPopupMenuButton: false, modifyComment: { _, _ in }, - onClickDelete: { _ in } + onClickDelete: { _ in }, + onClickProfile: { + memberId = $0 + isShowMemberProfilePopup = true + } ) .padding(.horizontal, 26.7) .padding(.bottom, 13.3) @@ -120,6 +127,10 @@ struct AudioContentListReplyView: View { onClickDelete: { commentId = $0 isShowDeletePopup = true + }, + onClickProfile: { + memberId = $0 + isShowMemberProfilePopup = true } ) .padding(.horizontal, 40) @@ -153,6 +164,10 @@ struct AudioContentListReplyView: View { ) } + if isShowMemberProfilePopup { + MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId) + } + if viewModel.isLoading { LoadingView() } diff --git a/SodaLive/Sources/Dialog/MemberProfileDialog.swift b/SodaLive/Sources/Dialog/MemberProfileDialog.swift new file mode 100644 index 0000000..e1ddba4 --- /dev/null +++ b/SodaLive/Sources/Dialog/MemberProfileDialog.swift @@ -0,0 +1,174 @@ +// +// MemberProfileDialog.swift +// SodaLive +// +// Created by klaus on 9/7/24. +// + +import SwiftUI +import Kingfisher + +struct MemberProfileDialog: View { + + @StateObject var viewModel = UserViewModel() + + @Binding var isShowing: Bool + let memberId: Int + + var body: some View { + ZStack { + Color.black.opacity(0.7).ignoresSafeArea() + .onTapGesture { + isShowing = false + } + + VStack(alignment: .leading, spacing: 21) { + HStack(spacing: 0) { + Text("프로필") + .font(.custom(Font.bold.rawValue, size: 15)) + .foregroundColor(Color.grayee) + + Spacer() + + Image("ic_close_white") + .onTapGesture { + isShowing = false + } + } + + if let profile = viewModel.memberProfile { + Text(profile.nickname) + .font(.custom(Font.bold.rawValue, size: 18.3)) + .foregroundColor(Color.grayee) + + KFImage(URL(string: profile.profileImageUrl)) + .resizable() + .frame(maxWidth: screenSize().width - 66.7, maxHeight: screenSize().width - 66.7) + .aspectRatio(CGSize(width: 1, height: 1), contentMode: .fit) + .cornerRadius(8) + + HStack(spacing: 8) { + Text(profile.isBlocked ? "차단 해제" : "차단") + .font(.custom(Font.bold.rawValue, size: 15)) + .foregroundColor(Color.button) + .frame(maxWidth: .infinity) + .padding(.vertical, 13) + .cornerRadius(8) + .contentShape(Rectangle()) + .overlay( + RoundedRectangle(cornerRadius: 8) + .strokeBorder(lineWidth: 1) + .foregroundColor(Color.button) + ) + .onTapGesture { + if profile.isBlocked { + viewModel.memberUnBlock() + } else { + viewModel.isShowUesrBlockConfirm = true + } + } + + Text("사용자 신고") + .font(.custom(Font.bold.rawValue, size: 15)) + .foregroundColor(Color.button) + .frame(maxWidth: .infinity) + .padding(.vertical, 13) + .cornerRadius(8) + .contentShape(Rectangle()) + .overlay( + RoundedRectangle(cornerRadius: 8) + .strokeBorder(lineWidth: 1) + .foregroundColor(Color.button) + ) + .onTapGesture { viewModel.isShowUesrReportView = true } + + Text("프로필 신고") + .font(.custom(Font.bold.rawValue, size: 15)) + .foregroundColor(Color.button) + .frame(maxWidth: .infinity) + .padding(.vertical, 13) + .cornerRadius(8) + .contentShape(Rectangle()) + .overlay( + RoundedRectangle(cornerRadius: 8) + .strokeBorder(lineWidth: 1) + .foregroundColor(Color.button) + ) + .onTapGesture { viewModel.isShowProfileReportConfirm = true } + } + } + } + .padding(.horizontal, 13.3) + .padding(.top, 13.3) + .padding(.bottom, 20) + .background(Color.gray22) + .cornerRadius(8) + .padding(.horizontal, 13.3) + .frame(maxWidth: screenSize().width - 33.3) + .onAppear { + if memberId <= 1 { + viewModel.errorMessage = "잘못된 요청입니다." + viewModel.isShowPopup = true + } else { + viewModel.getMemberProfile(memberId: memberId) + } + } + .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() + } + .onDisappear { + if viewModel.dismissDialog { + isShowing = false + } + } + } + + if viewModel.isShowUesrBlockConfirm { + UserBlockConfirmDialogView( + isShowing: $viewModel.isShowUesrBlockConfirm, + nickname: viewModel.nickname, + confirmAction: { + viewModel.memberBlock() + } + ) + } + + if viewModel.isShowUesrReportView { + UserReportDialogView( + isShowing: $viewModel.isShowUesrReportView, + confirmAction: { reason in + viewModel.report(type: .USER, reason: reason) + } + ) + } + + if viewModel.isShowProfileReportConfirm { + ProfileReportDialogView( + isShowing: $viewModel.isShowProfileReportConfirm, + confirmAction: { + viewModel.report(type: .PROFILE) + } + ) + } + + if viewModel.isLoading { + LoadingView() + } + } + } +} + +#Preview { + MemberProfileDialog(isShowing: .constant(true), memberId: 1) +} diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentItemView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentItemView.swift index 18712ee..d76857f 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentItemView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentItemView.swift @@ -17,6 +17,7 @@ struct CreatorCommunityCommentItemView: View { let modifyComment: (Int, String) -> Void let onClickDelete: (Int) -> Void + let onClickProfile: (Int) -> Void @State var isShowPopupMenu: Bool = false @State var isModeModify: Bool = false @@ -30,6 +31,11 @@ struct CreatorCommunityCommentItemView: View { .resizable() .frame(width: 40, height: 40) .clipShape(Circle()) + .onTapGesture { + if UserDefaults.int(forKey: .userId) != commentItem.writerId { + onClickProfile(commentItem.writerId) + } + } VStack(alignment: .leading, spacing: 0) { Text(commentItem.nickname) @@ -38,7 +44,7 @@ struct CreatorCommunityCommentItemView: View { Text(commentItem.date) .font(.custom(Font.medium.rawValue, size: 10.3)) - .foregroundColor(Color(hex: "525252")) + .foregroundColor(Color.gray52) .padding(.top, 4) } @@ -57,8 +63,8 @@ struct CreatorCommunityCommentItemView: View { .autocapitalization(.none) .disableAutocorrection(true) .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) - .accentColor(Color(hex: "3bb9f1")) + .foregroundColor(Color.grayee) + .accentColor(Color.button) .keyboardType(.default) .padding(.horizontal, 13.3) diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift index 4db30df..a7475e4 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentListView.swift @@ -17,6 +17,10 @@ struct CreatorCommunityCommentListView: View { @State private var commentId: Int = 0 @State private var isShowDeletePopup: Bool = false + + @State private var memberId: Int = 0 + @State private var isShowMemberProfilePopup: Bool = false + @StateObject var viewModel = CreatorCommunityCommentListViewModel() var body: some View { @@ -115,6 +119,10 @@ struct CreatorCommunityCommentListView: View { onClickDelete: { commentId = $0 isShowDeletePopup = true + }, + onClickProfile: { + memberId = $0 + isShowMemberProfilePopup = true } ) .padding(.horizontal, 26.7) @@ -147,6 +155,10 @@ struct CreatorCommunityCommentListView: View { ) } + if isShowMemberProfilePopup { + MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId) + } + if viewModel.isLoading { LoadingView() } diff --git a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyView.swift b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyView.swift index 0ef150d..889729d 100644 --- a/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyView.swift +++ b/SodaLive/Sources/Explorer/Profile/CreatorCommunity/All/Comment/CreatorCommunityCommentReplyView.swift @@ -20,6 +20,9 @@ struct CreatorCommunityCommentReplyView: View { @State private var commentId: Int = 0 @State private var isShowDeletePopup: Bool = false + @State private var memberId: Int = 0 + @State private var isShowMemberProfilePopup: Bool = false + var body: some View { ZStack { VStack(spacing: 0) { @@ -98,7 +101,11 @@ struct CreatorCommunityCommentReplyView: View { isReplyComment: true, isShowPopupMenuButton: false, modifyComment: { _, _ in }, - onClickDelete: { _ in } + onClickDelete: { _ in }, + onClickProfile: { + memberId = $0 + isShowMemberProfilePopup = true + } ) .padding(.horizontal, 26.7) .padding(.bottom, 13.3) @@ -120,6 +127,10 @@ struct CreatorCommunityCommentReplyView: View { onClickDelete: { commentId = $0 isShowDeletePopup = true + }, + onClickProfile: { + memberId = $0 + isShowMemberProfilePopup = true } ) .padding(.horizontal, 26.7) @@ -154,6 +165,10 @@ struct CreatorCommunityCommentReplyView: View { ) } + if isShowMemberProfilePopup { + MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId) + } + if viewModel.isLoading { LoadingView() } diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift index 13cc39e..df16ea3 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkAllView.swift @@ -16,6 +16,9 @@ struct UserProfileFanTalkAllView: View { @State private var cheersContent: String = "" @State private var cheersId: Int = 0 + @State private var memberId: Int = 0 + @State private var isShowMemberProfilePopup: Bool = false + var body: some View { GeometryReader { proxy in BaseView(isLoading: $viewModel.isLoading) { @@ -26,17 +29,17 @@ struct UserProfileFanTalkAllView: View { HStack(spacing: 6.7) { Text("응원") .font(.custom(Font.medium.rawValue, size: 14.7)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Text("\(viewModel.cheersTotalCount)") .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "777777")) + .foregroundColor(Color.gray77) } .padding(.top, 20) Rectangle() .frame(height: 1) - .foregroundColor(Color(hex: "909090").opacity(0.5)) + .foregroundColor(Color.gray90.opacity(0.5)) .padding(.top, 13.3) HStack(spacing: 0) { @@ -44,8 +47,8 @@ struct UserProfileFanTalkAllView: View { .autocapitalization(.none) .disableAutocorrection(true) .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) - .accentColor(Color(hex: "3bb9f1")) + .foregroundColor(Color.grayee) + .accentColor(Color.button) .keyboardType(.default) .padding(.horizontal, 13.3) @@ -61,18 +64,18 @@ struct UserProfileFanTalkAllView: View { cheersContent = "" } } - .background(Color(hex: "232323")) + .background(Color.gray23) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "3bb9f1")) + .foregroundColor(Color.button) ) .padding(.top, 13.3) Rectangle() .frame(height: 1) - .foregroundColor(Color(hex: "909090").opacity(0.5)) + .foregroundColor(Color.gray90.opacity(0.5)) .padding(.top, 13.3) ScrollView(.vertical, showsIndicators: false) { @@ -96,6 +99,10 @@ struct UserProfileFanTalkAllView: View { onClickDelete: { cheersId in self.cheersId = cheersId viewModel.isShowCheersDeleteView = true + }, + onClickProfile: { + self.memberId = $0 + self.isShowMemberProfilePopup = true } ) .onAppear { @@ -110,7 +117,7 @@ struct UserProfileFanTalkAllView: View { } else { Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!") .font(.custom(Font.light.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "bbbbbb")) + .foregroundColor(Color.graybb) .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) .padding(.vertical, 60) @@ -136,7 +143,7 @@ struct UserProfileFanTalkAllView: View { .padding(.vertical, 13.3) .frame(width: screenSize().width - 66.7, alignment: .center) .font(.custom(Font.medium.rawValue, size: 12)) - .background(Color(hex: "9970ff")) + .background(Color.button) .foregroundColor(Color.white) .multilineTextAlignment(.leading) .cornerRadius(20) @@ -171,6 +178,10 @@ struct UserProfileFanTalkAllView: View { ) } } + + if isShowMemberProfilePopup { + MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId) + } } } } diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift index ec780d6..7bb0776 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkCheersItemView.swift @@ -16,6 +16,7 @@ struct UserProfileFanTalkCheersItemView: View { let modifyCheer: (Int, String) -> Void let reportPopup: (Int) -> Void let onClickDelete: (Int) -> Void + let onClickProfile: (Int) -> Void @State var replyContent: String = "" @State var isShowInputReply = false @@ -34,6 +35,11 @@ struct UserProfileFanTalkCheersItemView: View { .resizable() .frame(width: 33.3, height: 33.3) .clipShape(Circle()) + .onTapGesture { + if UserDefaults.int(forKey: .userId) != cheersItem.memberId { + onClickProfile(cheersItem.memberId) + } + } VStack(alignment: .leading, spacing: 0) { Text("\(cheersItem.nickname)") @@ -51,10 +57,10 @@ struct UserProfileFanTalkCheersItemView: View { .autocapitalization(.none) .disableAutocorrection(true) .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) .padding(13.3) - .background(Color(hex: "232323")) - .accentColor(Color(hex: "3bb9f1")) + .background(Color.gray23) + .accentColor(Color.button) .keyboardType(.default) .cornerRadius(10) .overlay( @@ -65,7 +71,7 @@ struct UserProfileFanTalkCheersItemView: View { Text("수정") .font(.custom(Font.bold.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "ffffff")) + .foregroundColor(Color.white) .padding(13.3) .background(Color.button) .cornerRadius(6.7) @@ -78,7 +84,7 @@ struct UserProfileFanTalkCheersItemView: View { .font(.custom(Font.bold.rawValue, size: 13.3)) .foregroundColor(Color.button) .padding(13.3) - .background(Color(hex: "222222")) + .background(Color.gray22) .cornerRadius(6.7) .onTapGesture { isModeModify = false @@ -100,23 +106,23 @@ struct UserProfileFanTalkCheersItemView: View { .autocapitalization(.none) .disableAutocorrection(true) .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) .padding(13.3) - .background(Color(hex: "232323")) - .accentColor(Color(hex: "3bb9f1")) + .background(Color.gray23) + .accentColor(Color.button) .keyboardType(.default) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "3bb9f1")) + .foregroundColor(Color.button) ) Text("등록") .font(.custom(Font.bold.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "ffffff")) + .foregroundColor(Color.white) .padding(13.3) - .background(Color(hex: "3bb9f1")) + .background(Color.button) .cornerRadius(6.7) .onTapGesture { if cheersItem.replyList.count > 0 { @@ -182,7 +188,7 @@ struct UserProfileFanTalkCheersItemView: View { Rectangle() .frame(height: 1) - .foregroundColor(Color(hex: "909090").opacity(0.5)) + .foregroundColor(Color.gray90.opacity(0.5)) .padding(.top, 13.3) } .frame(width: screenSize().width - 26.7) @@ -192,7 +198,7 @@ struct UserProfileFanTalkCheersItemView: View { if cheersItem.memberId != UserDefaults.int(forKey: .userId) { Text("신고하기") .font(.custom(Font.medium.rawValue, size: 14)) - .foregroundColor(Color(hex: "777777")) + .foregroundColor(Color.gray77) .onTapGesture { reportPopup(cheersItem.cheersId) isShowPopupMenu = false @@ -202,7 +208,7 @@ struct UserProfileFanTalkCheersItemView: View { if cheersItem.memberId == UserDefaults.int(forKey: .userId) { Text("수정") .font(.custom(Font.medium.rawValue, size: 14)) - .foregroundColor(Color(hex: "777777")) + .foregroundColor(Color.gray77) .onTapGesture { isModeModify = true isShowPopupMenu = false @@ -215,7 +221,7 @@ struct UserProfileFanTalkCheersItemView: View { { Text("삭제") .font(.custom(Font.medium.rawValue, size: 14)) - .foregroundColor(Color(hex: "777777")) + .foregroundColor(Color.gray77) .onTapGesture { onClickDelete(cheersItem.cheersId) isShowPopupMenu = false @@ -223,7 +229,7 @@ struct UserProfileFanTalkCheersItemView: View { } } .padding(10) - .background(Color(hex: "222222")) + .background(Color.gray22) } } .contentShape(Rectangle()) diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift index 1dd822f..9c5da64 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift @@ -17,6 +17,7 @@ struct UserProfileFanTalkView: View { let errorPopup: (String) -> Void let reportPopup: (Int) -> Void let deletePopup: (Int) -> Void + let profilePopup: (Int) -> Void @Binding var isLoading: Bool @State private var cheersContent: String = "" @@ -26,13 +27,13 @@ struct UserProfileFanTalkView: View { HStack(spacing: 0) { Text("팬 Talk") .font(.custom(Font.bold.rawValue, size: 16.7)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Spacer() Text("전체보기") .font(.custom(Font.light.rawValue, size: 11.3)) - .foregroundColor(Color(hex: "bbbbbb")) + .foregroundColor(Color.graybb) .onTapGesture { AppState.shared.setAppStep(step: .userProfileFanTalkAll(userId: userId)) } @@ -43,17 +44,17 @@ struct UserProfileFanTalkView: View { HStack(spacing: 6.7) { Text("응원") .font(.custom(Font.medium.rawValue, size: 14.7)) - .foregroundColor(Color(hex: "eeeeee")) + .foregroundColor(Color.grayee) Text("\(cheers.totalCount)") .font(.custom(Font.medium.rawValue, size: 12)) - .foregroundColor(Color(hex: "777777")) + .foregroundColor(Color.gray77) } .padding(.top, 20) Rectangle() .frame(height: 1) - .foregroundColor(Color(hex: "909090").opacity(0.5)) + .foregroundColor(Color.gray90.opacity(0.5)) .padding(.top, 13.3) HStack(spacing: 0) { @@ -61,8 +62,8 @@ struct UserProfileFanTalkView: View { .autocapitalization(.none) .disableAutocorrection(true) .font(.custom(Font.medium.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "eeeeee")) - .accentColor(Color(hex: "3bb9f1")) + .foregroundColor(Color.grayee) + .accentColor(Color.button) .keyboardType(.default) .padding(.horizontal, 13.3) @@ -78,18 +79,18 @@ struct UserProfileFanTalkView: View { cheersContent = "" } } - .background(Color(hex: "232323")) + .background(Color.gray23) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) .strokeBorder(lineWidth: 1) - .foregroundColor(Color(hex: "9970ff")) + .foregroundColor(Color.button) ) .padding(.top, 13.3) Rectangle() .frame(height: 1) - .foregroundColor(Color(hex: "909090").opacity(0.5)) + .foregroundColor(Color.gray90.opacity(0.5)) .padding(.top, 13.3) VStack(spacing: 20) { @@ -110,6 +111,9 @@ struct UserProfileFanTalkView: View { }, onClickDelete: { cheersId in deletePopup(cheersId) + }, + onClickProfile: { + profilePopup($0) } ) .onTapGesture { @@ -119,7 +123,7 @@ struct UserProfileFanTalkView: View { } else { Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!") .font(.custom(Font.light.rawValue, size: 13.3)) - .foregroundColor(Color(hex: "bbbbbb")) + .foregroundColor(Color.graybb) .multilineTextAlignment(.center) .fixedSize(horizontal: false, vertical: true) .padding(.vertical, 60) diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileView.swift index 8fc4426..c1b303b 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileView.swift @@ -12,6 +12,9 @@ struct UserProfileView: View { let userId: Int @StateObject var viewModel = UserProfileViewModel() + @State private var memberId: Int = 0 + @State private var isShowMemberProfilePopup: Bool = false + var body: some View { GeometryReader { proxy in BaseView(isLoading: $viewModel.isLoading) { @@ -196,6 +199,10 @@ struct UserProfileView: View { viewModel.cheersId = cheerId viewModel.isShowCheersDeleteView = true }, + profilePopup: { + self.memberId = $0 + self.isShowMemberProfilePopup = true + }, isLoading: $viewModel.isLoading ) .padding(.top, 26.7) @@ -311,6 +318,10 @@ struct UserProfileView: View { } ) } + + if isShowMemberProfilePopup { + MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId) + } } } .sheet( diff --git a/SodaLive/Sources/User/GetMemberProfileResponse.swift b/SodaLive/Sources/User/GetMemberProfileResponse.swift new file mode 100644 index 0000000..2f7816d --- /dev/null +++ b/SodaLive/Sources/User/GetMemberProfileResponse.swift @@ -0,0 +1,13 @@ +// +// GetMemberProfileResponse.swift +// SodaLive +// +// Created by klaus on 9/7/24. +// + +struct GetMemberProfileResponse: Decodable { + let memberId: Int + let nickname: String + let profileImageUrl: String + let isBlocked: Bool +} diff --git a/SodaLive/Sources/User/UserApi.swift b/SodaLive/Sources/User/UserApi.swift index 010a91a..be6434d 100644 --- a/SodaLive/Sources/User/UserApi.swift +++ b/SodaLive/Sources/User/UserApi.swift @@ -33,6 +33,7 @@ enum UserApi { case checkNickname(nickname: String) case changeNickname(request: ProfileUpdateRequest) case updateIdfa(request: IdfaUpdateRequest) + case getMemberProfile(memberId: Int) } extension UserApi: TargetType { @@ -80,10 +81,10 @@ extension UserApi: TargetType { case .creatorUnFollow: return "/member/creator/unfollow" - + case .getBlockedMemberIdList: return "/member/block/id" - + case .getBlockedMemberList, .memberBlock: return "/member/block" @@ -107,6 +108,9 @@ extension UserApi: TargetType { case .updateIdfa: return "/member/adid/update" + + case .getMemberProfile(let memberId): + return "/member/profile/\(memberId)" } } @@ -116,7 +120,7 @@ extension UserApi: TargetType { .profileImageUpdate: return .post - case .searchUser, .getMypage, .getMemberInfo, .getMyProfile, .getChangeNicknamePrice, .checkNickname, .getBlockedMemberList, .getBlockedMemberIdList: + case .searchUser, .getMypage, .getMemberInfo, .getMyProfile, .getChangeNicknamePrice, .checkNickname, .getBlockedMemberList, .getBlockedMemberIdList, .getMemberProfile: return .get case .updatePushToken, .profileUpdate, .changeNickname, .updateIdfa: @@ -141,7 +145,7 @@ extension UserApi: TargetType { case .getMypage, .getMyProfile, .getMemberInfo: return .requestParameters(parameters: ["container" : "ios"], encoding: URLEncoding.queryString) - case .logout, .logoutAllDevice, .getChangeNicknamePrice, .getBlockedMemberIdList: + case .logout, .logoutAllDevice, .getChangeNicknamePrice, .getBlockedMemberIdList, .getMemberProfile: return .requestPlain case .notification(let request): diff --git a/SodaLive/Sources/User/UserRepository.swift b/SodaLive/Sources/User/UserRepository.swift index 4fcb9a0..db00866 100644 --- a/SodaLive/Sources/User/UserRepository.swift +++ b/SodaLive/Sources/User/UserRepository.swift @@ -120,4 +120,8 @@ final class UserRepository { func updateIdfa(request: IdfaUpdateRequest) -> AnyPublisher { return api.requestPublisher(.updateIdfa(request: request)) } + + func getMemberProfile(memberId: Int) -> AnyPublisher { + return api.requestPublisher(.getMemberProfile(memberId: memberId)) + } } diff --git a/SodaLive/Sources/User/UserViewModel.swift b/SodaLive/Sources/User/UserViewModel.swift new file mode 100644 index 0000000..e944b2d --- /dev/null +++ b/SodaLive/Sources/User/UserViewModel.swift @@ -0,0 +1,192 @@ +// +// UserViewModel.swift +// SodaLive +// +// Created by klaus on 9/7/24. +// + +import Foundation +import Moya +import Combine + +final class UserViewModel: ObservableObject { + private let repository = UserRepository() + private let reportRepository = ReportRepository() + private var subscription = Set() + + @Published var isLoading = false + @Published var errorMessage = "" + @Published var isShowPopup = false + + @Published var memberId = 0 + @Published var nickname = "" + @Published var dismissDialog = false + + @Published var isShowUesrBlockConfirm = false + @Published var isShowUesrReportView = false + @Published var isShowProfileReportConfirm = false + + @Published var memberProfile: GetMemberProfileResponse? + + func getMemberProfile(memberId: Int) { + isLoading = true + + repository.getMemberProfile(memberId: memberId) + .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 let data = decoded.data, decoded.success { + self.memberProfile = data + self.memberId = data.memberId + self.nickname = data.nickname + } 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) + } + + func memberBlock() { + isLoading = true + repository.memberBlock(userId: memberId) + .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(ApiResponseWithoutData.self, from: responseData) + + if decoded.success { + self.errorMessage = "차단하였습니다." + self.dismissDialog = true + + self.memberId = 0 + self.nickname = "" + } else { + 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) + } + + func memberUnBlock() { + isLoading = true + repository.memberUnBlock(userId: memberId) + .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(ApiResponseWithoutData.self, from: responseData) + + if decoded.success { + self.errorMessage = "차단이 해제 되었습니다." + self.dismissDialog = true + + self.memberId = 0 + self.nickname = "" + } else { + 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) + } + + func report(type: ReportType, reason: String = "프로필 신고") { + isLoading = true + + let request = ReportRequest(type: type, reason: reason, reportedMemberId: memberId, cheersId: nil, audioContentId: nil) + reportRepository.report(request: request) + .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 + + self.memberId = 0 + self.nickname = "" + self.dismissDialog = true + + do { + let jsonDecoder = JSONDecoder() + let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData) + + 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) + } +}