From 3456510eec41c210a8cb9d1dfec19d8c64562a74 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Wed, 4 Feb 2026 16:12:37 +0900 Subject: [PATCH] =?UTF-8?q?=ED=8C=AC=ED=86=A1=20TextField=EA=B0=80=20Focus?= =?UTF-8?q?=20=EB=90=98=EC=97=88=EC=9D=84=20=EB=95=8C=20=EC=9E=90=ED=8C=90?= =?UTF-8?q?=EC=9D=B4=20=ED=95=B4=EB=8B=B9=20=EC=98=81=EC=97=AD=EC=9D=84=20?= =?UTF-8?q?=EA=B0=80=EB=A6=AC=EB=8A=94=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FanTalk/UserProfileFanTalkView.swift | 2 + .../Explorer/Profile/UserProfileView.swift | 621 +++++++++--------- 2 files changed, 319 insertions(+), 304 deletions(-) diff --git a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift index 6c39e99..c1392e8 100644 --- a/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift +++ b/SodaLive/Sources/Explorer/Profile/FanTalk/UserProfileFanTalkView.swift @@ -18,6 +18,7 @@ struct UserProfileFanTalkView: View { let reportPopup: (Int) -> Void let deletePopup: (Int) -> Void let profilePopup: (Int) -> Void + let fanTalkInputId: String @Binding var isLoading: Bool @State private var cheersContent: String = "" @@ -73,6 +74,7 @@ struct UserProfileFanTalkView: View { cheersContent = "" } } + .id(fanTalkInputId) .background(Color.gray23) .cornerRadius(10) .overlay( diff --git a/SodaLive/Sources/Explorer/Profile/UserProfileView.swift b/SodaLive/Sources/Explorer/Profile/UserProfileView.swift index 581d7f5..865dcce 100644 --- a/SodaLive/Sources/Explorer/Profile/UserProfileView.swift +++ b/SodaLive/Sources/Explorer/Profile/UserProfileView.swift @@ -14,6 +14,7 @@ struct UserProfileView: View { @Environment(\.presentationMode) var presentationMode: Binding @StateObject var viewModel = UserProfileViewModel() + @StateObject private var keyboardHandler = KeyboardHandler() @State private var memberId: Int = 0 @State private var isShowMemberProfilePopup: Bool = false @@ -23,342 +24,354 @@ struct UserProfileView: View { @State private var maxCommunityPostHeight: CGFloat? = nil + private let fanTalkInputId = "fanTalkInput" + var body: some View { GeometryReader { proxy in BaseView(isLoading: $viewModel.isLoading) { ZStack(alignment: .top) { - 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() - .scaledToFill() - .aspectRatio(1, contentMode: .fill) - .frame(width: screenSize().width, height: screenSize().width) - .clipped() - - VStack(alignment: .leading, spacing: 8) { - HStack(spacing: 8) { - Text(creatorProfile.creator.nickname) - .appFont(size: 32, weight: .bold) - .foregroundColor(.white) - - if creatorProfile.creator.creatorId != UserDefaults.int(forKey: .userId) { - let asset = FollowButtonImageAsset( - type: creatorProfile.creator.isFollow - ? (creatorProfile.creator.isNotify ? .following : .followingNoAlarm) - : .follow - ) - asset.imageView(defaultSize: CGSize(width: 83.3, height: 26.7)) - .onTapGesture { - if creatorProfile.creator.isFollow { - isShowFollowNotifyDialog = true - } else { - viewModel.creatorFollow() - } - } - } - } - - if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) { - Text(I18n.MemberChannel.followersList) - .appFont(size: 16, weight: .bold) - .foregroundColor(Color.black) - .padding(.vertical, 8) - .frame(maxWidth: .infinity) - .background(Color.white) - .cornerRadius(999) - .onTapGesture { - AppState.shared.setAppStep(step: .followerList(userId: creatorProfile.creator.creatorId)) - } - } else { - VStack(alignment: .leading, spacing: 9.3) { - Text(I18n.MemberChannel.followerCount(creatorProfile.creator.notificationRecipientCount.comma())) - .appFont(size: 16, weight: .medium) - .foregroundColor(Color.white) - } - } - } - .padding(24) - } - - if let item = creatorProfile.latestContent { - HStack(spacing: 16) { - KFImage(URL(string: item.coverImageUrl)) + ScrollViewReader { scrollProxy in + 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: 133, height: 133)) + .downsampling(size: CGSize(width: screenSize().width, height: screenSize().width)) .resizable() .scaledToFill() - .frame(width: 133, height: 133, alignment: .top) + .aspectRatio(1, contentMode: .fill) + .frame(width: screenSize().width, height: screenSize().width) .clipped() - .cornerRadius(12) VStack(alignment: .leading, spacing: 8) { - Text(I18n.Common.latestContent) - .appFont(size: 12, weight: .medium) - .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(I18n.Common.openScheduled) - .appFont(size: 12, weight: .medium) - .foregroundColor(Color(hex: "3bb9f1")) - .padding(2.6) - .background(Color(hex: "003851")) - .cornerRadius(2.6) - } + Text(creatorProfile.creator.nickname) + .appFont(size: 32, weight: .bold) + .foregroundColor(.white) - Text(item.themeStr) - .appFont(size: 12, weight: .medium) - .foregroundColor(Color(hex: "3bac6a")) - .padding(2.6) - .background(Color(hex: "28312b")) - .cornerRadius(2.6) - - Text(item.duration!) - .appFont(size: 12, weight: .medium) - .foregroundColor(Color(hex: "777777")) - .padding(2.6) - .background(Color(hex: "222222")) - .cornerRadius(2.6) - - if item.isPointAvailable { - Text(I18n.Common.points) - .appFont(size: 12, weight: .medium) - .foregroundColor(.white) - .padding(2.6) - .background(Color(hex: "7849bc")) - .cornerRadius(2.6) + if creatorProfile.creator.creatorId != UserDefaults.int(forKey: .userId) { + let asset = FollowButtonImageAsset( + type: creatorProfile.creator.isFollow + ? (creatorProfile.creator.isNotify ? .following : .followingNoAlarm) + : .follow + ) + asset.imageView(defaultSize: CGSize(width: 83.3, height: 26.7)) + .onTapGesture { + if creatorProfile.creator.isFollow { + isShowFollowNotifyDialog = true + } else { + viewModel.creatorFollow() + } + } } } - Text(item.title) - .appFont(size: 18, weight: .medium) - .foregroundColor(Color.white) - .multilineTextAlignment(.leading) - .lineLimit(2) - .truncationMode(.tail) - - HStack(spacing: 14) { - HStack(spacing: 6) { - Image("ic_heart_777") - .resizable() - .frame(width: 18, height: 18) - - Text("\(item.likeCount)") - .appFont(size: 18, weight: .medium) - .foregroundColor(Color(hex: "777777")) - } - - HStack(spacing: 6) { - Image("ic_message_square_777") - .resizable() - .frame(width: 18, height: 18) - - Text("\(item.commentCount)") - .appFont(size: 18, weight: .medium) - .foregroundColor(Color(hex: "777777")) + if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) { + Text(I18n.MemberChannel.followersList) + .appFont(size: 16, weight: .bold) + .foregroundColor(Color.black) + .padding(.vertical, 8) + .frame(maxWidth: .infinity) + .background(Color.white) + .cornerRadius(999) + .onTapGesture { + AppState.shared.setAppStep(step: .followerList(userId: creatorProfile.creator.creatorId)) + } + } else { + VStack(alignment: .leading, spacing: 9.3) { + Text(I18n.MemberChannel.followerCount(creatorProfile.creator.notificationRecipientCount.comma())) + .appFont(size: 16, weight: .medium) + .foregroundColor(Color.white) } } } - - Spacer() + .padding(24) } - .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(I18n.MemberChannel.liveHeader) - .appFont(size: 26, weight: .bold) - .foregroundColor(Color.white) + + 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(I18n.Common.latestContent) + .appFont(size: 12, weight: .medium) + .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(I18n.Common.openScheduled) + .appFont(size: 12, weight: .medium) + .foregroundColor(Color(hex: "3bb9f1")) + .padding(2.6) + .background(Color(hex: "003851")) + .cornerRadius(2.6) + } + + Text(item.themeStr) + .appFont(size: 12, weight: .medium) + .foregroundColor(Color(hex: "3bac6a")) + .padding(2.6) + .background(Color(hex: "28312b")) + .cornerRadius(2.6) + + Text(item.duration!) + .appFont(size: 12, weight: .medium) + .foregroundColor(Color(hex: "777777")) + .padding(2.6) + .background(Color(hex: "222222")) + .cornerRadius(2.6) + + if item.isPointAvailable { + Text(I18n.Common.points) + .appFont(size: 12, weight: .medium) + .foregroundColor(.white) + .padding(2.6) + .background(Color(hex: "7849bc")) + .cornerRadius(2.6) + } + } + + Text(item.title) + .appFont(size: 18, weight: .medium) + .foregroundColor(Color.white) + .multilineTextAlignment(.leading) + .lineLimit(2) + .truncationMode(.tail) + + HStack(spacing: 14) { + HStack(spacing: 6) { + Image("ic_heart_777") + .resizable() + .frame(width: 18, height: 18) + + Text("\(item.likeCount)") + .appFont(size: 18, weight: .medium) + .foregroundColor(Color(hex: "777777")) + } + + HStack(spacing: 6) { + Image("ic_message_square_777") + .resizable() + .frame(width: 18, height: 18) + + Text("\(item.commentCount)") + .appFont(size: 18, weight: .medium) + .foregroundColor(Color(hex: "777777")) + } + } + } Spacer() } - - if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) { - HStack(spacing: 8) { - Text(I18n.MemberChannel.rouletteSettings) - .appFont(size: 16, weight: .bold) - .foregroundColor(Color.grayee) - .padding(.vertical, 12) - .frame(maxWidth: .infinity) - .background(Color.button) - .cornerRadius(12) - .onTapGesture { isShowRouletteSettings = true } - - Text(I18n.MemberChannel.menuSettings) - .appFont(size: 16, weight: .bold) - .foregroundColor(Color.grayee) - .padding(.vertical, 12) - .frame(maxWidth: .infinity) - .background(Color.button) - .cornerRadius(12) - .onTapGesture { isShowMenuSettings = true } - } + .frame(maxWidth: .infinity) + .frame(alignment: .leading) + .padding(.horizontal, 24) + .onTapGesture { + AppState.shared + .setAppStep(step: .contentDetail(contentId: item.contentId)) } - - if creatorProfile.liveRoomList.count > 0 { - UserProfileLiveView( - userId: userId, - liveRoomList: creatorProfile.liveRoomList, - onClickParticipant: { liveRoom in - if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) { - viewModel.errorMessage = I18n.MemberChannel.liveOnNow - viewModel.isShowPopup = true - } else { - AppState.shared.isShowPlayer = false - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - viewModel.enterLiveRoom(roomId: liveRoom.roomId) + } + + if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) || creatorProfile.liveRoomList.count > 0 { + VStack(alignment: .leading, spacing: 14) { + HStack(spacing: 0) { + Text(I18n.MemberChannel.liveHeader) + .appFont(size: 26, weight: .bold) + .foregroundColor(Color.white) + + Spacer() + } + + if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) { + HStack(spacing: 8) { + Text(I18n.MemberChannel.rouletteSettings) + .appFont(size: 16, weight: .bold) + .foregroundColor(Color.grayee) + .padding(.vertical, 12) + .frame(maxWidth: .infinity) + .background(Color.button) + .cornerRadius(12) + .onTapGesture { isShowRouletteSettings = true } + + Text(I18n.MemberChannel.menuSettings) + .appFont(size: 16, weight: .bold) + .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 = I18n.MemberChannel.liveOnNow + 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 = I18n.MemberChannel.cannotReserveOwnLive + viewModel.isShowPopup = true + } else { + viewModel.reservationLiveRoom(roomId: liveRoom.roomId) } } - }, - onClickReservation: { liveRoom in - if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) { - viewModel.errorMessage = I18n.MemberChannel.cannotReserveOwnLive - 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, - totalContentCount: creatorProfile.totalContentCount, - ownedContentCount: creatorProfile.ownedContentCount - ) - .padding(.horizontal, 24) - } - - if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) || viewModel.communityPostList.count > 0 { - VStack(alignment: .leading, spacing: 14) { - HStack(spacing: 0) { - Text(I18n.MemberChannel.communityHeader) - .appFont(size: 26, weight: .bold) - .foregroundColor(Color.white) - .padding(.horizontal, 24) - - Spacer() - } - if viewModel.communityPostList.count > 0 { - ScrollView(.horizontal, showsIndicators: false) { - LazyHStack(spacing: 14) { - if UserDefaults.int(forKey: .userId) == creatorProfile.creator.creatorId { - CreatorCommunityWriteItemView() - .onTapGesture { - AppState.shared.setAppStep( - step: .creatorCommunityWrite( - onSuccess: creatorCommunityWriteSuccess - ) - ) - } - } - - ForEach(0.. 0 { + UserProfileContentView( + userId: userId, + items: creatorProfile.contentList, + totalContentCount: creatorProfile.totalContentCount, + ownedContentCount: creatorProfile.ownedContentCount + ) + .padding(.horizontal, 24) + } + + if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) || viewModel.communityPostList.count > 0 { + VStack(alignment: .leading, spacing: 14) { + HStack(spacing: 0) { + Text(I18n.MemberChannel.communityHeader) + .appFont(size: 26, weight: .bold) + .foregroundColor(Color.white) + .padding(.horizontal, 24) + + Spacer() + } + if viewModel.communityPostList.count > 0 { + ScrollView(.horizontal, showsIndicators: false) { + LazyHStack(spacing: 14) { + 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 + }, + fanTalkInputId: fanTalkInputId, + isLoading: $viewModel.isLoading ) } - - if creatorProfile.creator.creatorId == UserDefaults.int(forKey: .userId) || creatorProfile.userDonationRanking.count > 0 { - UserProfileDonationView(userId: userId, donationRankingResponse: creatorProfile.userDonationRanking) + } + .onChange(of: keyboardHandler.keyboardHeight) { height in + if height > 0 { + withAnimation(.easeOut(duration: 0.25)) { + scrollProxy.scrollTo(fanTalkInputId, anchor: .bottom) + } } - - 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 - ) } } } @@ -556,7 +569,7 @@ struct UserProfileView: View { Spacer() } } - .ignoresSafeArea() + .ignoresSafeArea(.container, edges: .all) .sheet( isPresented: $viewModel.isShowShareView, onDismiss: { viewModel.shareMessage = "" },