커뮤니티 댓글, 팬토크, 콘텐츠 댓글

- 프로필 이미지 터치시 차단, 신고가 가능한 유저 프로필 표시
This commit is contained in:
Yu Sung 2024-09-07 03:58:52 +09:00
parent 17ead38524
commit 7c5b30335e
15 changed files with 537 additions and 53 deletions

View File

@ -17,6 +17,7 @@ struct AudioContentCommentItemView: View {
let modifyComment: (Int, String) -> Void let modifyComment: (Int, String) -> Void
let onClickDelete: (Int) -> Void let onClickDelete: (Int) -> Void
let onClickProfile: (Int) -> Void
@State var isShowPopupMenu: Bool = false @State var isShowPopupMenu: Bool = false
@State var isModeModify: Bool = false @State var isModeModify: Bool = false
@ -30,6 +31,11 @@ struct AudioContentCommentItemView: View {
.resizable() .resizable()
.frame(width: 40, height: 40) .frame(width: 40, height: 40)
.clipShape(Circle()) .clipShape(Circle())
.onTapGesture {
if UserDefaults.int(forKey: .userId) != commentItem.writerId {
onClickProfile(commentItem.writerId)
}
}
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 6.7) { HStack(spacing: 6.7) {
@ -95,8 +101,8 @@ struct AudioContentCommentItemView: View {
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.font(.custom(Font.medium.rawValue, size: 13.3)) .font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color.grayee)
.accentColor(Color(hex: "3bb9f1")) .accentColor(Color.button)
.keyboardType(.default) .keyboardType(.default)
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
@ -114,7 +120,7 @@ struct AudioContentCommentItemView: View {
isModeModify = false isModeModify = false
} }
} }
.background(Color(hex: "232323")) .background(Color.gray23)
.cornerRadius(10) .cornerRadius(10)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: 10)
@ -151,7 +157,7 @@ struct AudioContentCommentItemView: View {
.padding(.leading, 46.7) .padding(.leading, 46.7)
Rectangle() Rectangle()
.foregroundColor(Color(hex: "595959")) .foregroundColor(Color.gray59)
.frame(height: 0.5) .frame(height: 0.5)
.padding(.top, 16.7) .padding(.top, 16.7)
} }
@ -161,7 +167,7 @@ struct AudioContentCommentItemView: View {
if commentItem.writerId == UserDefaults.int(forKey: .userId) { if commentItem.writerId == UserDefaults.int(forKey: .userId) {
Text("수정") Text("수정")
.font(.custom(Font.medium.rawValue, size: 14)) .font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "777777")) .foregroundColor(Color.gray77)
.onTapGesture { .onTapGesture {
isModeModify = true isModeModify = true
isShowPopupMenu = false isShowPopupMenu = false
@ -173,7 +179,7 @@ struct AudioContentCommentItemView: View {
{ {
Text("삭제") Text("삭제")
.font(.custom(Font.medium.rawValue, size: 14)) .font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "777777")) .foregroundColor(Color.gray77)
.onTapGesture { .onTapGesture {
onClickDelete(commentItem.id) onClickDelete(commentItem.id)
isShowPopupMenu = false isShowPopupMenu = false
@ -181,7 +187,7 @@ struct AudioContentCommentItemView: View {
} }
} }
.padding(10) .padding(10)
.background(Color(hex: "222222")) .background(Color.gray22)
} }
} }
.onAppear { comment = commentItem.comment } .onAppear { comment = commentItem.comment }

View File

@ -21,6 +21,9 @@ struct AudioContentCommentListView: View {
@State private var commentId: Int = 0 @State private var commentId: Int = 0
@State private var isShowDeletePopup: Bool = false @State private var isShowDeletePopup: Bool = false
@State private var memberId: Int = 0
@State private var isShowMemberProfilePopup: Bool = false
var body: some View { var body: some View {
NavigationView { NavigationView {
ZStack { ZStack {
@ -137,6 +140,10 @@ struct AudioContentCommentListView: View {
onClickDelete: { onClickDelete: {
commentId = $0 commentId = $0
isShowDeletePopup = true isShowDeletePopup = true
},
onClickProfile: {
memberId = $0
isShowMemberProfilePopup = true
} }
) )
.padding(.horizontal, 26.7) .padding(.horizontal, 26.7)
@ -169,6 +176,10 @@ struct AudioContentCommentListView: View {
) )
} }
if isShowMemberProfilePopup {
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
}
if viewModel.isLoading { if viewModel.isLoading {
LoadingView() LoadingView()
} }

View File

@ -20,6 +20,9 @@ struct AudioContentListReplyView: View {
@State private var commentId: Int = 0 @State private var commentId: Int = 0
@State private var isShowDeletePopup: Bool = false @State private var isShowDeletePopup: Bool = false
@State private var memberId: Int = 0
@State private var isShowMemberProfilePopup: Bool = false
var body: some View { var body: some View {
ZStack { ZStack {
VStack(spacing: 0) { VStack(spacing: 0) {
@ -98,7 +101,11 @@ struct AudioContentListReplyView: View {
isReplyComment: true, isReplyComment: true,
isShowPopupMenuButton: false, isShowPopupMenuButton: false,
modifyComment: { _, _ in }, modifyComment: { _, _ in },
onClickDelete: { _ in } onClickDelete: { _ in },
onClickProfile: {
memberId = $0
isShowMemberProfilePopup = true
}
) )
.padding(.horizontal, 26.7) .padding(.horizontal, 26.7)
.padding(.bottom, 13.3) .padding(.bottom, 13.3)
@ -120,6 +127,10 @@ struct AudioContentListReplyView: View {
onClickDelete: { onClickDelete: {
commentId = $0 commentId = $0
isShowDeletePopup = true isShowDeletePopup = true
},
onClickProfile: {
memberId = $0
isShowMemberProfilePopup = true
} }
) )
.padding(.horizontal, 40) .padding(.horizontal, 40)
@ -153,6 +164,10 @@ struct AudioContentListReplyView: View {
) )
} }
if isShowMemberProfilePopup {
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
}
if viewModel.isLoading { if viewModel.isLoading {
LoadingView() LoadingView()
} }

View File

@ -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)
}

View File

@ -17,6 +17,7 @@ struct CreatorCommunityCommentItemView: View {
let modifyComment: (Int, String) -> Void let modifyComment: (Int, String) -> Void
let onClickDelete: (Int) -> Void let onClickDelete: (Int) -> Void
let onClickProfile: (Int) -> Void
@State var isShowPopupMenu: Bool = false @State var isShowPopupMenu: Bool = false
@State var isModeModify: Bool = false @State var isModeModify: Bool = false
@ -30,6 +31,11 @@ struct CreatorCommunityCommentItemView: View {
.resizable() .resizable()
.frame(width: 40, height: 40) .frame(width: 40, height: 40)
.clipShape(Circle()) .clipShape(Circle())
.onTapGesture {
if UserDefaults.int(forKey: .userId) != commentItem.writerId {
onClickProfile(commentItem.writerId)
}
}
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
Text(commentItem.nickname) Text(commentItem.nickname)
@ -38,7 +44,7 @@ struct CreatorCommunityCommentItemView: View {
Text(commentItem.date) Text(commentItem.date)
.font(.custom(Font.medium.rawValue, size: 10.3)) .font(.custom(Font.medium.rawValue, size: 10.3))
.foregroundColor(Color(hex: "525252")) .foregroundColor(Color.gray52)
.padding(.top, 4) .padding(.top, 4)
} }
@ -57,8 +63,8 @@ struct CreatorCommunityCommentItemView: View {
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.font(.custom(Font.medium.rawValue, size: 13.3)) .font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color.grayee)
.accentColor(Color(hex: "3bb9f1")) .accentColor(Color.button)
.keyboardType(.default) .keyboardType(.default)
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)

View File

@ -17,6 +17,10 @@ struct CreatorCommunityCommentListView: View {
@State private var commentId: Int = 0 @State private var commentId: Int = 0
@State private var isShowDeletePopup: Bool = false @State private var isShowDeletePopup: Bool = false
@State private var memberId: Int = 0
@State private var isShowMemberProfilePopup: Bool = false
@StateObject var viewModel = CreatorCommunityCommentListViewModel() @StateObject var viewModel = CreatorCommunityCommentListViewModel()
var body: some View { var body: some View {
@ -115,6 +119,10 @@ struct CreatorCommunityCommentListView: View {
onClickDelete: { onClickDelete: {
commentId = $0 commentId = $0
isShowDeletePopup = true isShowDeletePopup = true
},
onClickProfile: {
memberId = $0
isShowMemberProfilePopup = true
} }
) )
.padding(.horizontal, 26.7) .padding(.horizontal, 26.7)
@ -147,6 +155,10 @@ struct CreatorCommunityCommentListView: View {
) )
} }
if isShowMemberProfilePopup {
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
}
if viewModel.isLoading { if viewModel.isLoading {
LoadingView() LoadingView()
} }

View File

@ -20,6 +20,9 @@ struct CreatorCommunityCommentReplyView: View {
@State private var commentId: Int = 0 @State private var commentId: Int = 0
@State private var isShowDeletePopup: Bool = false @State private var isShowDeletePopup: Bool = false
@State private var memberId: Int = 0
@State private var isShowMemberProfilePopup: Bool = false
var body: some View { var body: some View {
ZStack { ZStack {
VStack(spacing: 0) { VStack(spacing: 0) {
@ -98,7 +101,11 @@ struct CreatorCommunityCommentReplyView: View {
isReplyComment: true, isReplyComment: true,
isShowPopupMenuButton: false, isShowPopupMenuButton: false,
modifyComment: { _, _ in }, modifyComment: { _, _ in },
onClickDelete: { _ in } onClickDelete: { _ in },
onClickProfile: {
memberId = $0
isShowMemberProfilePopup = true
}
) )
.padding(.horizontal, 26.7) .padding(.horizontal, 26.7)
.padding(.bottom, 13.3) .padding(.bottom, 13.3)
@ -120,6 +127,10 @@ struct CreatorCommunityCommentReplyView: View {
onClickDelete: { onClickDelete: {
commentId = $0 commentId = $0
isShowDeletePopup = true isShowDeletePopup = true
},
onClickProfile: {
memberId = $0
isShowMemberProfilePopup = true
} }
) )
.padding(.horizontal, 26.7) .padding(.horizontal, 26.7)
@ -154,6 +165,10 @@ struct CreatorCommunityCommentReplyView: View {
) )
} }
if isShowMemberProfilePopup {
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
}
if viewModel.isLoading { if viewModel.isLoading {
LoadingView() LoadingView()
} }

View File

@ -16,6 +16,9 @@ struct UserProfileFanTalkAllView: View {
@State private var cheersContent: String = "" @State private var cheersContent: String = ""
@State private var cheersId: Int = 0 @State private var cheersId: Int = 0
@State private var memberId: Int = 0
@State private var isShowMemberProfilePopup: Bool = false
var body: some View { var body: some View {
GeometryReader { proxy in GeometryReader { proxy in
BaseView(isLoading: $viewModel.isLoading) { BaseView(isLoading: $viewModel.isLoading) {
@ -26,17 +29,17 @@ struct UserProfileFanTalkAllView: View {
HStack(spacing: 6.7) { HStack(spacing: 6.7) {
Text("응원") Text("응원")
.font(.custom(Font.medium.rawValue, size: 14.7)) .font(.custom(Font.medium.rawValue, size: 14.7))
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color.grayee)
Text("\(viewModel.cheersTotalCount)") Text("\(viewModel.cheersTotalCount)")
.font(.custom(Font.medium.rawValue, size: 12)) .font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "777777")) .foregroundColor(Color.gray77)
} }
.padding(.top, 20) .padding(.top, 20)
Rectangle() Rectangle()
.frame(height: 1) .frame(height: 1)
.foregroundColor(Color(hex: "909090").opacity(0.5)) .foregroundColor(Color.gray90.opacity(0.5))
.padding(.top, 13.3) .padding(.top, 13.3)
HStack(spacing: 0) { HStack(spacing: 0) {
@ -44,8 +47,8 @@ struct UserProfileFanTalkAllView: View {
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.font(.custom(Font.medium.rawValue, size: 13.3)) .font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color.grayee)
.accentColor(Color(hex: "3bb9f1")) .accentColor(Color.button)
.keyboardType(.default) .keyboardType(.default)
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
@ -61,18 +64,18 @@ struct UserProfileFanTalkAllView: View {
cheersContent = "" cheersContent = ""
} }
} }
.background(Color(hex: "232323")) .background(Color.gray23)
.cornerRadius(10) .cornerRadius(10)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: 10)
.strokeBorder(lineWidth: 1) .strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "3bb9f1")) .foregroundColor(Color.button)
) )
.padding(.top, 13.3) .padding(.top, 13.3)
Rectangle() Rectangle()
.frame(height: 1) .frame(height: 1)
.foregroundColor(Color(hex: "909090").opacity(0.5)) .foregroundColor(Color.gray90.opacity(0.5))
.padding(.top, 13.3) .padding(.top, 13.3)
ScrollView(.vertical, showsIndicators: false) { ScrollView(.vertical, showsIndicators: false) {
@ -96,6 +99,10 @@ struct UserProfileFanTalkAllView: View {
onClickDelete: { cheersId in onClickDelete: { cheersId in
self.cheersId = cheersId self.cheersId = cheersId
viewModel.isShowCheersDeleteView = true viewModel.isShowCheersDeleteView = true
},
onClickProfile: {
self.memberId = $0
self.isShowMemberProfilePopup = true
} }
) )
.onAppear { .onAppear {
@ -110,7 +117,7 @@ struct UserProfileFanTalkAllView: View {
} else { } else {
Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!") Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!")
.font(.custom(Font.light.rawValue, size: 13.3)) .font(.custom(Font.light.rawValue, size: 13.3))
.foregroundColor(Color(hex: "bbbbbb")) .foregroundColor(Color.graybb)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.padding(.vertical, 60) .padding(.vertical, 60)
@ -136,7 +143,7 @@ struct UserProfileFanTalkAllView: View {
.padding(.vertical, 13.3) .padding(.vertical, 13.3)
.frame(width: screenSize().width - 66.7, alignment: .center) .frame(width: screenSize().width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12)) .font(.custom(Font.medium.rawValue, size: 12))
.background(Color(hex: "9970ff")) .background(Color.button)
.foregroundColor(Color.white) .foregroundColor(Color.white)
.multilineTextAlignment(.leading) .multilineTextAlignment(.leading)
.cornerRadius(20) .cornerRadius(20)
@ -171,6 +178,10 @@ struct UserProfileFanTalkAllView: View {
) )
} }
} }
if isShowMemberProfilePopup {
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
}
} }
} }
} }

View File

@ -16,6 +16,7 @@ struct UserProfileFanTalkCheersItemView: View {
let modifyCheer: (Int, String) -> Void let modifyCheer: (Int, String) -> Void
let reportPopup: (Int) -> Void let reportPopup: (Int) -> Void
let onClickDelete: (Int) -> Void let onClickDelete: (Int) -> Void
let onClickProfile: (Int) -> Void
@State var replyContent: String = "" @State var replyContent: String = ""
@State var isShowInputReply = false @State var isShowInputReply = false
@ -34,6 +35,11 @@ struct UserProfileFanTalkCheersItemView: View {
.resizable() .resizable()
.frame(width: 33.3, height: 33.3) .frame(width: 33.3, height: 33.3)
.clipShape(Circle()) .clipShape(Circle())
.onTapGesture {
if UserDefaults.int(forKey: .userId) != cheersItem.memberId {
onClickProfile(cheersItem.memberId)
}
}
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
Text("\(cheersItem.nickname)") Text("\(cheersItem.nickname)")
@ -51,10 +57,10 @@ struct UserProfileFanTalkCheersItemView: View {
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.font(.custom(Font.medium.rawValue, size: 13.3)) .font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color.grayee)
.padding(13.3) .padding(13.3)
.background(Color(hex: "232323")) .background(Color.gray23)
.accentColor(Color(hex: "3bb9f1")) .accentColor(Color.button)
.keyboardType(.default) .keyboardType(.default)
.cornerRadius(10) .cornerRadius(10)
.overlay( .overlay(
@ -65,7 +71,7 @@ struct UserProfileFanTalkCheersItemView: View {
Text("수정") Text("수정")
.font(.custom(Font.bold.rawValue, size: 13.3)) .font(.custom(Font.bold.rawValue, size: 13.3))
.foregroundColor(Color(hex: "ffffff")) .foregroundColor(Color.white)
.padding(13.3) .padding(13.3)
.background(Color.button) .background(Color.button)
.cornerRadius(6.7) .cornerRadius(6.7)
@ -78,7 +84,7 @@ struct UserProfileFanTalkCheersItemView: View {
.font(.custom(Font.bold.rawValue, size: 13.3)) .font(.custom(Font.bold.rawValue, size: 13.3))
.foregroundColor(Color.button) .foregroundColor(Color.button)
.padding(13.3) .padding(13.3)
.background(Color(hex: "222222")) .background(Color.gray22)
.cornerRadius(6.7) .cornerRadius(6.7)
.onTapGesture { .onTapGesture {
isModeModify = false isModeModify = false
@ -100,23 +106,23 @@ struct UserProfileFanTalkCheersItemView: View {
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.font(.custom(Font.medium.rawValue, size: 13.3)) .font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color.grayee)
.padding(13.3) .padding(13.3)
.background(Color(hex: "232323")) .background(Color.gray23)
.accentColor(Color(hex: "3bb9f1")) .accentColor(Color.button)
.keyboardType(.default) .keyboardType(.default)
.cornerRadius(10) .cornerRadius(10)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: 10)
.strokeBorder(lineWidth: 1) .strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "3bb9f1")) .foregroundColor(Color.button)
) )
Text("등록") Text("등록")
.font(.custom(Font.bold.rawValue, size: 13.3)) .font(.custom(Font.bold.rawValue, size: 13.3))
.foregroundColor(Color(hex: "ffffff")) .foregroundColor(Color.white)
.padding(13.3) .padding(13.3)
.background(Color(hex: "3bb9f1")) .background(Color.button)
.cornerRadius(6.7) .cornerRadius(6.7)
.onTapGesture { .onTapGesture {
if cheersItem.replyList.count > 0 { if cheersItem.replyList.count > 0 {
@ -182,7 +188,7 @@ struct UserProfileFanTalkCheersItemView: View {
Rectangle() Rectangle()
.frame(height: 1) .frame(height: 1)
.foregroundColor(Color(hex: "909090").opacity(0.5)) .foregroundColor(Color.gray90.opacity(0.5))
.padding(.top, 13.3) .padding(.top, 13.3)
} }
.frame(width: screenSize().width - 26.7) .frame(width: screenSize().width - 26.7)
@ -192,7 +198,7 @@ struct UserProfileFanTalkCheersItemView: View {
if cheersItem.memberId != UserDefaults.int(forKey: .userId) { if cheersItem.memberId != UserDefaults.int(forKey: .userId) {
Text("신고하기") Text("신고하기")
.font(.custom(Font.medium.rawValue, size: 14)) .font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "777777")) .foregroundColor(Color.gray77)
.onTapGesture { .onTapGesture {
reportPopup(cheersItem.cheersId) reportPopup(cheersItem.cheersId)
isShowPopupMenu = false isShowPopupMenu = false
@ -202,7 +208,7 @@ struct UserProfileFanTalkCheersItemView: View {
if cheersItem.memberId == UserDefaults.int(forKey: .userId) { if cheersItem.memberId == UserDefaults.int(forKey: .userId) {
Text("수정") Text("수정")
.font(.custom(Font.medium.rawValue, size: 14)) .font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "777777")) .foregroundColor(Color.gray77)
.onTapGesture { .onTapGesture {
isModeModify = true isModeModify = true
isShowPopupMenu = false isShowPopupMenu = false
@ -215,7 +221,7 @@ struct UserProfileFanTalkCheersItemView: View {
{ {
Text("삭제") Text("삭제")
.font(.custom(Font.medium.rawValue, size: 14)) .font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "777777")) .foregroundColor(Color.gray77)
.onTapGesture { .onTapGesture {
onClickDelete(cheersItem.cheersId) onClickDelete(cheersItem.cheersId)
isShowPopupMenu = false isShowPopupMenu = false
@ -223,7 +229,7 @@ struct UserProfileFanTalkCheersItemView: View {
} }
} }
.padding(10) .padding(10)
.background(Color(hex: "222222")) .background(Color.gray22)
} }
} }
.contentShape(Rectangle()) .contentShape(Rectangle())

View File

@ -17,6 +17,7 @@ struct UserProfileFanTalkView: View {
let errorPopup: (String) -> Void let errorPopup: (String) -> Void
let reportPopup: (Int) -> Void let reportPopup: (Int) -> Void
let deletePopup: (Int) -> Void let deletePopup: (Int) -> Void
let profilePopup: (Int) -> Void
@Binding var isLoading: Bool @Binding var isLoading: Bool
@State private var cheersContent: String = "" @State private var cheersContent: String = ""
@ -26,13 +27,13 @@ struct UserProfileFanTalkView: View {
HStack(spacing: 0) { HStack(spacing: 0) {
Text("팬 Talk") Text("팬 Talk")
.font(.custom(Font.bold.rawValue, size: 16.7)) .font(.custom(Font.bold.rawValue, size: 16.7))
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color.grayee)
Spacer() Spacer()
Text("전체보기") Text("전체보기")
.font(.custom(Font.light.rawValue, size: 11.3)) .font(.custom(Font.light.rawValue, size: 11.3))
.foregroundColor(Color(hex: "bbbbbb")) .foregroundColor(Color.graybb)
.onTapGesture { .onTapGesture {
AppState.shared.setAppStep(step: .userProfileFanTalkAll(userId: userId)) AppState.shared.setAppStep(step: .userProfileFanTalkAll(userId: userId))
} }
@ -43,17 +44,17 @@ struct UserProfileFanTalkView: View {
HStack(spacing: 6.7) { HStack(spacing: 6.7) {
Text("응원") Text("응원")
.font(.custom(Font.medium.rawValue, size: 14.7)) .font(.custom(Font.medium.rawValue, size: 14.7))
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color.grayee)
Text("\(cheers.totalCount)") Text("\(cheers.totalCount)")
.font(.custom(Font.medium.rawValue, size: 12)) .font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "777777")) .foregroundColor(Color.gray77)
} }
.padding(.top, 20) .padding(.top, 20)
Rectangle() Rectangle()
.frame(height: 1) .frame(height: 1)
.foregroundColor(Color(hex: "909090").opacity(0.5)) .foregroundColor(Color.gray90.opacity(0.5))
.padding(.top, 13.3) .padding(.top, 13.3)
HStack(spacing: 0) { HStack(spacing: 0) {
@ -61,8 +62,8 @@ struct UserProfileFanTalkView: View {
.autocapitalization(.none) .autocapitalization(.none)
.disableAutocorrection(true) .disableAutocorrection(true)
.font(.custom(Font.medium.rawValue, size: 13.3)) .font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee")) .foregroundColor(Color.grayee)
.accentColor(Color(hex: "3bb9f1")) .accentColor(Color.button)
.keyboardType(.default) .keyboardType(.default)
.padding(.horizontal, 13.3) .padding(.horizontal, 13.3)
@ -78,18 +79,18 @@ struct UserProfileFanTalkView: View {
cheersContent = "" cheersContent = ""
} }
} }
.background(Color(hex: "232323")) .background(Color.gray23)
.cornerRadius(10) .cornerRadius(10)
.overlay( .overlay(
RoundedRectangle(cornerRadius: 10) RoundedRectangle(cornerRadius: 10)
.strokeBorder(lineWidth: 1) .strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "9970ff")) .foregroundColor(Color.button)
) )
.padding(.top, 13.3) .padding(.top, 13.3)
Rectangle() Rectangle()
.frame(height: 1) .frame(height: 1)
.foregroundColor(Color(hex: "909090").opacity(0.5)) .foregroundColor(Color.gray90.opacity(0.5))
.padding(.top, 13.3) .padding(.top, 13.3)
VStack(spacing: 20) { VStack(spacing: 20) {
@ -110,6 +111,9 @@ struct UserProfileFanTalkView: View {
}, },
onClickDelete: { cheersId in onClickDelete: { cheersId in
deletePopup(cheersId) deletePopup(cheersId)
},
onClickProfile: {
profilePopup($0)
} }
) )
.onTapGesture { .onTapGesture {
@ -119,7 +123,7 @@ struct UserProfileFanTalkView: View {
} else { } else {
Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!") Text("응원이 없습니다.\n\n처음으로 응원을 해보세요!")
.font(.custom(Font.light.rawValue, size: 13.3)) .font(.custom(Font.light.rawValue, size: 13.3))
.foregroundColor(Color(hex: "bbbbbb")) .foregroundColor(Color.graybb)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
.fixedSize(horizontal: false, vertical: true) .fixedSize(horizontal: false, vertical: true)
.padding(.vertical, 60) .padding(.vertical, 60)

View File

@ -12,6 +12,9 @@ struct UserProfileView: View {
let userId: Int let userId: Int
@StateObject var viewModel = UserProfileViewModel() @StateObject var viewModel = UserProfileViewModel()
@State private var memberId: Int = 0
@State private var isShowMemberProfilePopup: Bool = false
var body: some View { var body: some View {
GeometryReader { proxy in GeometryReader { proxy in
BaseView(isLoading: $viewModel.isLoading) { BaseView(isLoading: $viewModel.isLoading) {
@ -196,6 +199,10 @@ struct UserProfileView: View {
viewModel.cheersId = cheerId viewModel.cheersId = cheerId
viewModel.isShowCheersDeleteView = true viewModel.isShowCheersDeleteView = true
}, },
profilePopup: {
self.memberId = $0
self.isShowMemberProfilePopup = true
},
isLoading: $viewModel.isLoading isLoading: $viewModel.isLoading
) )
.padding(.top, 26.7) .padding(.top, 26.7)
@ -311,6 +318,10 @@ struct UserProfileView: View {
} }
) )
} }
if isShowMemberProfilePopup {
MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId)
}
} }
} }
.sheet( .sheet(

View File

@ -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
}

View File

@ -33,6 +33,7 @@ enum UserApi {
case checkNickname(nickname: String) case checkNickname(nickname: String)
case changeNickname(request: ProfileUpdateRequest) case changeNickname(request: ProfileUpdateRequest)
case updateIdfa(request: IdfaUpdateRequest) case updateIdfa(request: IdfaUpdateRequest)
case getMemberProfile(memberId: Int)
} }
extension UserApi: TargetType { extension UserApi: TargetType {
@ -107,6 +108,9 @@ extension UserApi: TargetType {
case .updateIdfa: case .updateIdfa:
return "/member/adid/update" return "/member/adid/update"
case .getMemberProfile(let memberId):
return "/member/profile/\(memberId)"
} }
} }
@ -116,7 +120,7 @@ extension UserApi: TargetType {
.profileImageUpdate: .profileImageUpdate:
return .post return .post
case .searchUser, .getMypage, .getMemberInfo, .getMyProfile, .getChangeNicknamePrice, .checkNickname, .getBlockedMemberList, .getBlockedMemberIdList: case .searchUser, .getMypage, .getMemberInfo, .getMyProfile, .getChangeNicknamePrice, .checkNickname, .getBlockedMemberList, .getBlockedMemberIdList, .getMemberProfile:
return .get return .get
case .updatePushToken, .profileUpdate, .changeNickname, .updateIdfa: case .updatePushToken, .profileUpdate, .changeNickname, .updateIdfa:
@ -141,7 +145,7 @@ extension UserApi: TargetType {
case .getMypage, .getMyProfile, .getMemberInfo: case .getMypage, .getMyProfile, .getMemberInfo:
return .requestParameters(parameters: ["container" : "ios"], encoding: URLEncoding.queryString) return .requestParameters(parameters: ["container" : "ios"], encoding: URLEncoding.queryString)
case .logout, .logoutAllDevice, .getChangeNicknamePrice, .getBlockedMemberIdList: case .logout, .logoutAllDevice, .getChangeNicknamePrice, .getBlockedMemberIdList, .getMemberProfile:
return .requestPlain return .requestPlain
case .notification(let request): case .notification(let request):

View File

@ -120,4 +120,8 @@ final class UserRepository {
func updateIdfa(request: IdfaUpdateRequest) -> AnyPublisher<Response, MoyaError> { func updateIdfa(request: IdfaUpdateRequest) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.updateIdfa(request: request)) return api.requestPublisher(.updateIdfa(request: request))
} }
func getMemberProfile(memberId: Int) -> AnyPublisher<Response, MoyaError> {
return api.requestPublisher(.getMemberProfile(memberId: memberId))
}
} }

View File

@ -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<AnyCancellable>()
@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<GetMemberProfileResponse>.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)
}
}