// // UserProfileView.swift // SodaLive // // Created by klaus on 2023/08/11. // import SwiftUI import Kingfisher struct UserProfileView: View { let userId: Int @Environment(\.presentationMode) var presentationMode: Binding @StateObject var viewModel = UserProfileViewModel() @State private var memberId: Int = 0 @State private var isShowMemberProfilePopup: Bool = false @State private var isShowFollowNotifyDialog: Bool = false @State private var isShowRouletteSettings: Bool = false @State private var isShowMenuSettings: Bool = false @State private var maxCommunityPostHeight: CGFloat? = nil var body: some View { GeometryReader { proxy in BaseView(isLoading: $viewModel.isLoading) { ZStack(alignment: .top) { VStack(spacing: 0) { ScrollView(.vertical, showsIndicators: false) { LazyVStack(spacing: 48) { if let creatorProfile = viewModel.creatorProfile { ZStack(alignment: .bottomLeading) { KFImage(URL(string: creatorProfile.creator.profileUrl)) .cancelOnDisappear(true) .downsampling(size: CGSize(width: screenSize().width, height: screenSize().width)) .resizable() .aspectRatio(1, contentMode: .fill) .frame(maxWidth: screenSize().width) .clipped() } if let item = creatorProfile.latestContent { HStack(spacing: 16) { KFImage(URL(string: item.coverImageUrl)) .cancelOnDisappear(true) .downsampling(size: CGSize(width: 133, height: 133)) .resizable() .scaledToFill() .frame(width: 133, height: 133, alignment: .top) .clipped() .cornerRadius(12) VStack(alignment: .leading, spacing: 8) { Text("최신 콘텐츠") .font(.custom(Font.preMedium.rawValue, size: 12)) .foregroundColor(.button) .padding(.horizontal, 7) .padding(.vertical, 4) .background(Color(hex: "263238")) .cornerRadius(4) .overlay { RoundedRectangle(cornerRadius: 4) .strokeBorder(lineWidth: 1) .foregroundColor(.button) } HStack(spacing: 8) { if item.isScheduledToOpen { Text("오픈예정") .font(.custom(Font.preMedium.rawValue, size: 12)) .foregroundColor(Color(hex: "3bb9f1")) .padding(2.6) .background(Color(hex: "003851")) .cornerRadius(2.6) } Text(item.themeStr) .font(.custom(Font.preMedium.rawValue, size: 12)) .foregroundColor(Color(hex: "3bac6a")) .padding(2.6) .background(Color(hex: "28312b")) .cornerRadius(2.6) Text(item.duration!) .font(.custom(Font.preMedium.rawValue, size: 12)) .foregroundColor(Color(hex: "777777")) .padding(2.6) .background(Color(hex: "222222")) .cornerRadius(2.6) if item.isPointAvailable { Text("포인트") .font(.custom(Font.preMedium.rawValue, size: 12)) .foregroundColor(.white) .padding(2.6) .background(Color(hex: "7849bc")) .cornerRadius(2.6) } } Text(item.title) .font(.custom(Font.preMedium.rawValue, size: 18)) .foregroundColor(Color.white) .multilineTextAlignment(.leading) HStack(spacing: 14) { HStack(spacing: 6) { Image("ic_heart_777") .resizable() .frame(width: 24, height: 24) Text("\(item.likeCount)") .font(.custom(Font.preMedium.rawValue, size: 18)) .foregroundColor(Color(hex: "777777")) } HStack(spacing: 6) { Image("ic_message_square_777") .resizable() .frame(width: 24, height: 24) Text("\(item.commentCount)") .font(.custom(Font.preMedium.rawValue, size: 18)) .foregroundColor(Color(hex: "777777")) } } } Spacer() } .frame(maxWidth: .infinity) .frame(alignment: .leading) .padding(.horizontal, 24) .onTapGesture { AppState.shared .setAppStep(step: .contentDetail(contentId: item.contentId)) } } if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) || creatorProfile.liveRoomList.count > 0 { VStack(alignment: .leading, spacing: 14) { HStack(spacing: 0) { Text("라이브") .font(.custom(Font.preBold.rawValue, size: 26)) .foregroundColor(Color.white) Spacer() } if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) { HStack(spacing: 8) { Text("룰렛 설정") .font(.custom(Font.preBold.rawValue, size: 16)) .foregroundColor(Color.grayee) .padding(.vertical, 12) .frame(maxWidth: .infinity) .background(Color.button) .cornerRadius(12) .onTapGesture { isShowRouletteSettings = true } Text("메뉴 설정") .font(.custom(Font.preBold.rawValue, size: 16)) .foregroundColor(Color.grayee) .padding(.vertical, 12) .frame(maxWidth: .infinity) .background(Color.button) .cornerRadius(12) .onTapGesture { isShowMenuSettings = true } } } if creatorProfile.liveRoomList.count > 0 { UserProfileLiveView( userId: userId, liveRoomList: creatorProfile.liveRoomList, onClickParticipant: { liveRoom in if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) { viewModel.errorMessage = "현재 라이브 중입니다." viewModel.isShowPopup = true } else { AppState.shared.isShowPlayer = false DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { viewModel.enterLiveRoom(roomId: liveRoom.roomId) } } }, onClickReservation: { liveRoom in if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) { viewModel.errorMessage = "내가 만든 라이브는 예약할 수 없습니다." viewModel.isShowPopup = true } else { viewModel.reservationLiveRoom(roomId: liveRoom.roomId) } } ) } } .padding(.horizontal, 24) } if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) || creatorProfile.contentList.count > 0 { UserProfileContentView( userId: userId, items: creatorProfile.contentList ) } if viewModel.communityPostList.count > 0 { ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing: 13.3) { if UserDefaults.int(forKey: .userId) == creatorProfile.creator.creatorId { CreatorCommunityWriteItemView() .onTapGesture { AppState.shared.setAppStep( step: .creatorCommunityWrite( onSuccess: creatorCommunityWriteSuccess ) ) } } ForEach(0.. 0 { UserProfileDonationView(userId: userId, donationRankingResponse: creatorProfile.userDonationRanking) } UserProfileFanTalkView( userId: userId, cheers: creatorProfile.cheers, errorPopup: { message in viewModel.errorMessage = message viewModel.isShowPopup = true }, reportPopup: { cheerId in viewModel.cheersId = cheerId viewModel.isShowCheersReportView = true }, deletePopup: { cheerId in viewModel.cheersId = cheerId viewModel.isShowCheersDeleteView = true }, profilePopup: { self.memberId = $0 self.isShowMemberProfilePopup = true }, isLoading: $viewModel.isLoading ) } } } } HStack(spacing: 14) { Button { if presentationMode.wrappedValue.isPresented { presentationMode.wrappedValue.dismiss() } else { AppState.shared.back() } } label: { Image("ic_back") .resizable() .frame(width: 20, height: 20) Text(viewModel.navigationTitle) .font(.custom(Font.preBold.rawValue, size: 20)) .foregroundColor(Color.white) } Spacer() if let creatorProfile = viewModel.creatorProfile { Image("ic_new_share") .resizable() .frame(width: 24, height: 24) .onTapGesture { viewModel.shareChannel( userId: creatorProfile.creator.creatorId, nickname: creatorProfile.creator.nickname, profileImage: creatorProfile.creator.profileUrl ) } } if userId != UserDefaults.int(forKey: .userId) { Image("ic_seemore_vertical") .resizable() .frame(width: 24, height: 24) .onTapGesture { viewModel.isShowReportMenu = true } } } .padding(.top, proxy.safeAreaInsets.top) .padding(.horizontal, 24) .frame(height: proxy.safeAreaInsets.top + 50) } ZStack { if viewModel.isShowPaymentDialog { LivePaymentDialog( title: viewModel.paymentDialogTitle, desc: viewModel.paymentDialogDesc, desc2: viewModel.paymentDialogDesc2, confirmButtonTitle: viewModel.paymentDialogConfirmTitle, confirmButtonAction: viewModel.paymentDialogConfirmAction, cancelButtonTitle: viewModel.paymentDialogCancelTitle, cancelButtonAction: viewModel.hidePaymentPopup, startDateTime: viewModel.liveStartDate, nowDateTime: viewModel.nowDate ) } if viewModel.isShowPasswordDialog { LiveRoomPasswordDialog( isShowing: $viewModel.isShowPasswordDialog, can: viewModel.secretOrPasswordDialogCan, confirmAction: viewModel.passwordDialogConfirmAction ) } if viewModel.isShowCheersDeleteView { SodaDialog( title: "응원글 삭제", desc: "삭제하시겠습니까?", confirmButtonTitle: "삭제", confirmButtonAction: { viewModel.deleteCheers(creatorId: userId) viewModel.isShowCheersDeleteView = false }, cancelButtonTitle: "취소", cancelButtonAction: { viewModel.isShowCheersDeleteView = false } ) } if viewModel.isShowCheersReportView { CheersReportDialogView( isShowing: $viewModel.isShowCheersReportView, confirmAction: { reason in viewModel.report(type: .CHEERS, reason: reason) } ) } if let creatorProfile = viewModel.creatorProfile, viewModel.isShowReportMenu { VStack(spacing: 0) { ProfileReportMenuView( isShowing: $viewModel.isShowReportMenu, isBlockedUser: creatorProfile.isBlock, userBlockAction: { viewModel.isShowUesrBlockConfirm = true }, userUnBlockAction: { viewModel.userUnBlock(userId: userId) }, userReportAction: { viewModel.isShowUesrReportView = true }, profileReportAction: { viewModel.isShowProfileReportConfirm = true } ) if proxy.safeAreaInsets.bottom > 0 { Rectangle() .foregroundColor(Color(hex: "222222")) .frame(width: proxy.size.width, height: 15.3) } } .ignoresSafeArea() } if let creatorProfile = viewModel.creatorProfile, viewModel.isShowUesrBlockConfirm { UserBlockConfirmDialogView( isShowing: $viewModel.isShowUesrBlockConfirm, nickname: creatorProfile.creator.nickname, confirmAction: { viewModel.userBlock(userId: userId) } ) } if viewModel.isShowUesrReportView { UserReportDialogView( isShowing: $viewModel.isShowUesrReportView, confirmAction: { reason in viewModel.report(type: .USER, userId: userId, reason: reason) } ) } if viewModel.isShowProfileReportConfirm { ProfileReportDialogView( isShowing: $viewModel.isShowProfileReportConfirm, confirmAction: { viewModel.report(type: .PROFILE, userId: userId) } ) } if isShowMemberProfilePopup { MemberProfileDialog(isShowing: $isShowMemberProfilePopup, memberId: memberId) } } ZStack { if isShowFollowNotifyDialog { CreatorFollowNotifyDialog( isShowing: $isShowFollowNotifyDialog, onClickNotifyAll: { viewModel.creatorFollow(follow: true, notify: true) }, onClickNotifyNone: { viewModel.creatorFollow(follow: true, notify: false) }, onClickUnFollow: { viewModel.creatorFollow(follow: false, notify: false) } ) } if isShowRouletteSettings { RouletteSettingsView(isShowing: $isShowRouletteSettings, availableActive: false) { _, message in viewModel.errorMessage = message viewModel.isShowPopup = true } .padding(.top, proxy.safeAreaInsets.top) .padding(.bottom, proxy.safeAreaInsets.bottom) .padding(.trailing, proxy.safeAreaInsets.trailing) .padding(.leading, proxy.safeAreaInsets.leading) } if isShowMenuSettings { MenuSettingsView(isShowing: $isShowMenuSettings) .padding(.top, proxy.safeAreaInsets.top) .padding(.bottom, proxy.safeAreaInsets.bottom) .padding(.trailing, proxy.safeAreaInsets.trailing) .padding(.leading, proxy.safeAreaInsets.leading) } } } .navigationTitle("") .navigationBarBackButtonHidden() .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.preMedium.rawValue, size: 12)) .background(Color.button) .foregroundColor(Color.white) .multilineTextAlignment(.leading) .cornerRadius(20) .padding(.bottom, 66.7) Spacer() } } .ignoresSafeArea() .sheet( isPresented: $viewModel.isShowShareView, onDismiss: { viewModel.shareMessage = "" }, content: { ActivityViewController(activityItems: [viewModel.shareMessage]) } ) .onAppear { viewModel.getCreatorProfile(userId: userId) AppState.shared.pushChannelId = 0 } } } private func creatorCommunityWriteSuccess() { viewModel.getCreatorProfile(userId: userId) } } struct HeightPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { value = max(value, nextValue()) // 가장 큰 height로 갱신 } } struct UserProfileView_Previews: PreviewProvider { static var previews: some View { UserProfileView(userId: 0) } }