sodalive-ios/SodaLive/Sources/Live/Room/LiveRoomView.swift

983 lines
45 KiB
Swift

//
// LiveRoomView.swift
// SodaLive
//
// Created by klaus on 2023/08/14.
//
import SwiftUI
import Kingfisher
import PopupView
struct LiveRoomView: View {
@State private var isShowingNewChat = false
@State private var isShowPhotoPicker = false
@State private var noticeViewHeight: CGFloat = UIFont.systemFontSize
let columns = [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
]
let chatColumns = [GridItem(.flexible())]
@StateObject var keyboardHandler = KeyboardHandler()
@StateObject var viewModel = LiveRoomViewModel()
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
VStack(spacing: 0) {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 6.7) {
Text(
UserDefaults.int(forKey: .userId) == viewModel.liveRoomInfo?.creatorId ?
"라이브 종료":
"나가기"
)
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: "ff5c49"))
.padding(.horizontal, 14.3)
.padding(.vertical, 8.3)
.overlay(
RoundedRectangle(cornerRadius: 13.3)
.stroke(Color(hex: "ff5c49"), lineWidth: 1)
)
.onTapGesture {
viewModel.isExpandNotice = false
if let liveRoomInfo = viewModel.liveRoomInfo, liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) {
viewModel.isShowLiveEndPopup = true
} else {
viewModel.isShowQuitPopup = true
}
}
Spacer()
Text(viewModel.isBgOn ? "배경 ON" : "배경 OFF")
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: viewModel.isBgOn ? "9970ff" : "eeeeee"))
.padding(.horizontal, 14.3)
.padding(.vertical, 8.3)
.overlay(
RoundedRectangle(cornerRadius: 13.3)
.stroke(Color(hex: viewModel.isBgOn ? "9970ff" : "bbbbbb"), lineWidth: 1)
)
.onTapGesture {
viewModel.isBgOn.toggle()
}
HStack(spacing: 4.7) {
Image("ic_share")
.resizable()
.frame(width: 16, height: 16)
}
.padding(.horizontal, 14.3)
.padding(.vertical, 6)
.overlay(
RoundedRectangle(cornerRadius: 13.3)
.stroke(Color(hex: "bbbbbb"), lineWidth: 1)
)
.onTapGesture {
viewModel.shareRoom()
}
if let liveRoomInfo = viewModel.liveRoomInfo,
liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) {
HStack(spacing: 4.7) {
Image("ic_edit")
.resizable()
.frame(width: 16, height: 16)
}
.padding(.horizontal, 14.3)
.padding(.vertical, 6)
.overlay(
RoundedRectangle(cornerRadius: 13.3)
.stroke(Color(hex: "bbbbbb"), lineWidth: 1)
)
.onTapGesture {
viewModel.isShowEditRoomInfoDialog = true
}
}
}
.padding(.horizontal, 13.3)
if let liveRoomInfo = viewModel.liveRoomInfo {
ZStack {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 5.3) {
if liveRoomInfo.isAdult {
Text("19")
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "e33621"))
.padding(.horizontal, 5.3)
.padding(.vertical, 3.3)
.background(Color(hex: "601d14"))
.cornerRadius(2.6)
}
Text(liveRoomInfo.title)
.font(.custom(Font.bold.rawValue, size: 15.3))
.foregroundColor(Color(hex: "eeeeee"))
.lineLimit(1)
}
.padding(.top, 16.7)
.padding(.horizontal, 13.3)
LiveRoomTopCreatorView(
creatorId: liveRoomInfo.creatorId,
nickname: liveRoomInfo.creatorNickname,
profileImageUrl: liveRoomInfo.creatorProfileUrl,
isFollowing: liveRoomInfo.isFollowing,
onClickProfile: {
if liveRoomInfo.creatorId != UserDefaults.int(forKey: .userId) {
viewModel.getUserProfile(userId: liveRoomInfo.creatorId)
}
},
onClickFollow: {
if $0 {
viewModel.creatorUnFollow()
} else {
viewModel.creatorFollow()
}
}
)
.padding(.top, 16.7)
.padding(.horizontal, 13.3)
Rectangle()
.frame(height: 1)
.foregroundColor(Color(hex: "909090").opacity(0.3))
.padding(.horizontal, 13.3)
.padding(.top, 8)
NotificationView(liveRoomInfo: liveRoomInfo)
}
if viewModel.isMute {
Image("img_noti_mute")
}
}
if viewModel.isShowNotice {
HStack(alignment: .top, spacing: 8) {
Text("[공지]")
.font(.custom(Font.bold.rawValue, size: 11.3))
.foregroundColor(.white)
.onTapGesture {
viewModel.isExpandNotice.toggle()
}
if viewModel.isExpandNotice {
VStack(spacing: 10) {
TextView(text: liveRoomInfo.notice, dynamicHeight: $noticeViewHeight)
.frame(height: viewModel.isExpandNotice ? noticeViewHeight : UIFont.systemFontSize)
Text("닫기")
.font(.custom(Font.light.rawValue, size: 11.3))
.foregroundColor(.white)
.onTapGesture {
viewModel.isExpandNotice = false
}
}
} else {
Text(liveRoomInfo.notice)
.font(.custom(Font.light.rawValue, size: 11.3))
.foregroundColor(.white)
.lineLimit(1)
.onTapGesture {
viewModel.isExpandNotice = true
}
}
}
.padding(.horizontal, 26.7)
.padding(.vertical, 13.3)
.frame(width: screenSize().width, alignment: .leading)
.background(Color(hex: "3d2a6c"))
.padding(.top, 10)
.contentShape(Rectangle())
}
if !viewModel.isSpeakerFold {
HStack(spacing: 0) {
Text("스피커")
.font(.custom(Font.bold.rawValue, size: 12))
.foregroundColor(Color(hex: "eeeeee"))
Spacer()
}
.padding(.top, 20)
.padding(.horizontal, 23.3)
LazyVGrid(columns: columns) {
ForEach(liveRoomInfo.speakerList, id: \.self) { speaker in
VStack(spacing: 6.7) {
ZStack {
KFImage(URL(string: speaker.profileImage))
.resizable()
.scaledToFill()
.frame(width: 46.7, height: 46.7, alignment: .top)
.clipShape(Circle())
.overlay(
Circle()
.stroke(
Color(hex: "9970ff"),
lineWidth: viewModel.activeSpeakers.contains(UInt(speaker.id)) ? 3 : 0
)
)
if viewModel.muteSpeakers.contains(UInt(speaker.id)) {
Image("ic_mute")
.resizable()
.frame(width: 46.7, height: 46.7)
}
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 0) {
Spacer()
if liveRoomInfo.creatorId == speaker.id {
Image("ic_crown")
.resizable()
.frame(width: 16.7, height: 16.7)
}
}
Spacer()
}
}
.frame(width: 46.7, height: 46.7)
Text(speaker.nickname)
.font(.custom(Font.light.rawValue, size: 12))
.foregroundColor(Color(hex: "bbbbbb"))
.lineLimit(1)
}
.onTapGesture {
viewModel.selectedProfile = speaker
viewModel.isShowProfilePopup = true
}
}
}
.padding(.top, 16.7)
.padding(.horizontal, 23.3)
}
}
}
.padding(.vertical, 16.7)
.frame(width: screenSize().width)
.background(Color(hex: "222222"))
.cornerRadius(16.7, corners: [.topLeft, .topRight])
ZStack(alignment: .top) {
ScrollViewReader { proxy in
ZStack(alignment: .bottomTrailing) {
if let liveRoomInfo = viewModel.liveRoomInfo, viewModel.isBgOn {
GeometryReader { proxy in
KFImage(URL(string: liveRoomInfo.coverImageUrl))
.resizable()
.scaledToFill()
.frame(width: proxy.size.width, height: proxy.size.height, alignment: .center)
.clipped()
Color.black.opacity(0.4)
}
}
VStack(alignment: .leading, spacing: 0) {
ScrollView(.vertical, showsIndicators: false) {
scrollObservableView
ChatView()
.frame(width: screenSize().width)
}
.rotationEffect(Angle(degrees: 180))
.onTapGesture { hideKeyboard() }
.onPreferenceChange(ScrollOffsetKey.self) {
viewModel.setOffset($0)
}
InputChatView {
isShowingNewChat = false
proxy.scrollTo(viewModel.messages.count - 1, anchor: .center)
}.padding(.bottom, 10)
}
VStack(spacing: 13.3) {
if viewModel.role == .SPEAKER {
Image(viewModel.isMute ? "ic_mic_off" : "ic_mic_on")
.resizable()
.frame(width: 26.7, height: 26.7)
.padding(11)
.background(Color(hex: "525252").opacity(0.6))
.cornerRadius(10)
.padding(.bottom, 13.3)
.onTapGesture {
viewModel.toggleMute()
}
}
Image(viewModel.isSpeakerMute ? "ic_speaker_off" : "ic_speaker_on")
.resizable()
.frame(width: 26.7, height: 26.7)
.padding(11)
.background(Color(hex: "525252").opacity(0.6))
.cornerRadius(10)
.padding(.bottom, 13.3)
.onTapGesture {
viewModel.toggleSpeakerMute()
}
if let liveRoomInfo = viewModel.liveRoomInfo, liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) && UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue {
Image("ic_donation_message_list")
.resizable()
.frame(width: 26.7, height: 26.7)
.padding(11)
.background(Color(hex: "525252").opacity(0.6))
.cornerRadius(10)
.onTapGesture {
viewModel.isShowDonationMessagePopup = true
}
} else {
Image("ic_donation")
.resizable()
.frame(width: 26.7, height: 26.7)
.padding(11)
.background(Color(hex: "525252").opacity(0.6))
.cornerRadius(10)
.onTapGesture {
viewModel.isShowDonationPopup = true
}
}
}
.padding(.trailing, 16.7)
.padding(.bottom, 85)
if isShowingNewChat {
NewChatView{
isShowingNewChat = false
proxy.scrollTo(viewModel.messages.count - 1, anchor: .center)
}.padding(.bottom, 70)
}
}
.frame(width: screenSize().width)
.animation(nil)
}
HStack(spacing: 0) {
Spacer()
HStack(spacing: 6.7) {
Image(viewModel.isSpeakerFold ? "ic_live_detail_bottom" : "ic_live_detail_top")
.resizable()
.frame(width: 20, height: 20)
Text(viewModel.isSpeakerFold ? "펼치기" : "접기")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "bbbbbb"))
}
.padding(.vertical, 6.7)
.padding(.horizontal, 13.3)
.background(Color(hex: "222222"))
.cornerRadius(10, corners: [.bottomLeft, .bottomRight])
.onTapGesture {
viewModel.isSpeakerFold.toggle()
}
}
.background(
LinearGradient(
gradient: Gradient(colors: [Color(hex: "222222").opacity(0.95), Color.black.opacity(0.005)]),
startPoint: .top,
endPoint: .bottom
).ignoresSafeArea()
)
}
}
.popup(isPresented: $viewModel.isShowErrorPopup, type: .toast, position: .top, autohideIn: 1.3) {
GeometryReader { geo in
HStack {
Spacer()
Text(viewModel.errorMessage)
.padding(.vertical, 13.3)
.frame(width: geo.size.width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12))
.background(Color(hex: "9970ff"))
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.cornerRadius(20)
.padding(.top, 66.7)
Spacer()
}
.onDisappear {
viewModel.quitRoom()
}
}
}
.cornerRadius(16.7, corners: [.topLeft, .topRight])
.offset(y: -(keyboardHandler.keyboardHeight > 0 ? keyboardHandler.keyboardHeight : 0))
.onAppear {
UIApplication.shared.isIdleTimerDisabled = true
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
viewModel.getMemberCan()
viewModel.initAgoraEngine()
viewModel.getRoomInfo()
NotificationCenter.default.addObserver(
forName: UIApplication.willTerminateNotification,
object: nil,
queue: .main) { _ in
viewModel.quitRoom()
sleep(3)
}
}
.onDisappear {
UIApplication.shared.isIdleTimerDisabled = false
NotificationCenter.default.removeObserver(self)
}
ZStack {
if viewModel.isShowProfilePopup, let liveRoomInfo = viewModel.liveRoomInfo, let selectedProfile = viewModel.selectedProfile {
LiveRoomProfileDialog(
isShowing: $viewModel.isShowProfilePopup,
profileInfo: selectedProfile,
creatorId: liveRoomInfo.creatorId,
isSpeaker: viewModel.role == .SPEAKER,
onClickInviteSpeaker: { inviteSpeaker(peerId: $0) },
onClickChangeListener: {
if $0 == UserDefaults.int(forKey: .userId) {
viewModel.setListener()
return
}
viewModel.changeListener(peerId: $0)
}
)
}
if viewModel.isShowDonationPopup {
LiveRoomDonationDialogView(isShowing: $viewModel.isShowDonationPopup, isAudioContentDonation: false) { can, message in
viewModel.donation(can: can, message: message)
}
}
if viewModel.isShowQuitPopup {
SodaDialog(
title: "라이브 나가기",
desc: "라이브에서 나가시겠습니까?",
confirmButtonTitle: "",
confirmButtonAction: {
viewModel.isShowQuitPopup = false
viewModel.quitRoom()
},
cancelButtonTitle: "아니오",
cancelButtonAction: {
viewModel.isShowQuitPopup = false
}
)
}
if viewModel.isShowLiveEndPopup {
SodaDialog(
title: "라이브 종료",
desc: "라이브를 종료하시겠습니까?\n" +
"라이브를 종료하면 대화내용은\n" +
"저장되지 않고 사라집니다.\n" +
"참여자들 또한 라이브가 종료되어\n" +
"강제퇴장 됩니다.",
confirmButtonTitle: "",
confirmButtonAction: {
viewModel.isShowLiveEndPopup = false
viewModel.quitRoom()
},
cancelButtonTitle: "아니오",
cancelButtonAction: {
viewModel.isShowLiveEndPopup = false
}
)
}
}
ZStack {
if viewModel.isShowProfileList, let liveRoomInfo = viewModel.liveRoomInfo {
LiveRoomProfilesDialogView(
isShowing: $viewModel.isShowProfileList,
viewModel: viewModel,
roomInfo: liveRoomInfo,
registerNotification: { viewModel.creatorFollow() },
unRegisterNotification: { viewModel.creatorUnFollow() },
onClickProfile: {
if $0 != UserDefaults.int(forKey: .userId) {
viewModel.getUserProfile(userId: $0)
}
},
onClickNoChatting: { userId, nickname, profileUrl in
viewModel.noChattingUserId = userId
viewModel.noChattingUserNickname = nickname
viewModel.noChattingUserProfileUrl = profileUrl
viewModel.isShowNoChattingConfirm = true
}
)
}
if viewModel.isShowUserProfilePopup, let userProfile = viewModel.userProfile {
Color.black.opacity(0.7)
.edgesIgnoringSafeArea(.all)
LiveRoomUserProfileDialogView(
isShowing: $viewModel.isShowUserProfilePopup,
viewModel: viewModel,
userProfile: userProfile,
onClickSetManager: {
viewModel.setManagerMessageToPeer(userId: $0)
viewModel.setManager(userId: $0)
},
onClickReleaseManager: { viewModel.changeListener(peerId: $0, isFromManager: true) },
onClickFollow: { viewModel.creatorFollow(creatorId: $0, isGetUserProfile: true) },
onClickUnFollow: { viewModel.creatorUnFollow(creatorId: $0, isGetUserProfile: true) },
onClickInviteSpeaker: { inviteSpeaker(peerId: $0) },
onClickChangeListener: {
viewModel.changeListener(peerId: $0)
},
onClickMenu: { userId, userNickname, isBlocked in
viewModel.reportUserId = userId
viewModel.reportUserNickname = userNickname
viewModel.reportUserIsBlocked = isBlocked
viewModel.isShowReportMenu = true
},
onClickNoChatting: { userId, nickname, profileUrl in
viewModel.noChattingUserId = userId
viewModel.noChattingUserNickname = nickname
viewModel.noChattingUserProfileUrl = profileUrl
viewModel.isShowNoChattingConfirm = true
}
)
.padding(20)
.popup(isPresented: $viewModel.isShowReportPopup, type: .toast, position: .top, autohideIn: 1.3) {
GeometryReader { geo in
HStack {
Spacer()
Text(viewModel.reportMessage)
.padding(.vertical, 13.3)
.frame(width: geo.size.width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12))
.background(Color(hex: "9970ff"))
.foregroundColor(Color.white)
.multilineTextAlignment(.center)
.cornerRadius(20)
.padding(.top, 66.7)
Spacer()
}
}
}
}
if viewModel.isShowReportMenu {
VStack(spacing: 0) {
ProfileReportMenuView(
isShowing: $viewModel.isShowReportMenu,
isBlockedUser: viewModel.reportUserIsBlocked,
userBlockAction: { viewModel.isShowUesrBlockConfirm = true },
userUnBlockAction: { viewModel.userUnBlock() },
userReportAction: { viewModel.isShowUesrReportView = true },
profileReportAction: { viewModel.isShowProfileReportConfirm = true }
)
Rectangle()
.foregroundColor(Color(hex: "222222"))
.frame(width: screenSize().width, height: 15.3)
}
.ignoresSafeArea()
}
if viewModel.isShowUesrBlockConfirm {
UserBlockConfirmDialogView(
isShowing: $viewModel.isShowUesrBlockConfirm,
nickname: viewModel.reportUserNickname,
confirmAction: {
viewModel.userBlock { userId in
viewModel.kickOutId = userId
viewModel.kickOut()
}
}
)
}
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.isShowNoChattingConfirm && viewModel.noChattingUserId > 0 {
LiveRoomNoChattingDialogView(
nickname: viewModel.noChattingUserNickname,
profileUrl: viewModel.noChattingUserProfileUrl,
confirmAction: {
viewModel.isShowNoChattingConfirm = false
viewModel.setNoChatting()
},
cancelAction: {
viewModel.noChattingUserId = 0
viewModel.noChattingUserNickname = ""
viewModel.noChattingUserProfileUrl = ""
viewModel.isShowNoChattingConfirm = false
}
)
}
if viewModel.isShowPopup {
LiveRoomDialogView(
content: viewModel.popupContent,
cancelTitle: viewModel.popupCancelTitle,
cancelAction: viewModel.popupCancelAction,
confirmTitle: viewModel.popupConfirmTitle,
confirmAction: viewModel.popupConfirmAction
).onAppear {
if viewModel.popupConfirmTitle == nil && viewModel.popupConfirmAction == nil {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
viewModel.isShowPopup = false
viewModel.popupCancelTitle = nil
viewModel.popupCancelAction = nil
viewModel.popupConfirmTitle = nil
viewModel.popupConfirmAction = nil
}
}
}
}
}
if viewModel.isLoading && viewModel.liveRoomInfo == nil {
LoadingView()
}
}
.ignoresSafeArea(.keyboard)
.edgesIgnoringSafeArea(keyboardHandler.keyboardHeight > 0 ? .bottom : .init())
.transaction { transaction in
transaction.animation = nil
}
.sheet(
isPresented: $viewModel.isShowShareView,
onDismiss: { viewModel.shareMessage = "" },
content: {
ActivityViewController(activityItems: [viewModel.shareMessage])
}
)
.sheet(isPresented: $isShowPhotoPicker) {
ImagePicker(
isShowing: $isShowPhotoPicker,
selectedImage: $viewModel.coverImage,
sourceType: .photoLibrary
)
}
.sheet(isPresented: $viewModel.isShowEditRoomInfoDialog) {
if let liveRoomInfo = viewModel.liveRoomInfo {
LiveRoomInfoEditDialog(
isShowing: $viewModel.isShowEditRoomInfoDialog,
isShowPhotoPicker: $isShowPhotoPicker,
viewModel: viewModel,
isLoading: viewModel.isLoading,
currentTitle: liveRoomInfo.title,
currentNotice: liveRoomInfo.notice,
coverImageUrl: liveRoomInfo.coverImageUrl,
coverImage: viewModel.coverImage
) { newTitle, newNotice in
self.viewModel.editLiveRoomInfo(
title: newTitle,
notice: newNotice
)
}
} else {
EmptyView()
.onAppear {
viewModel.isShowEditRoomInfoDialog = false
}
}
}
.sheet(isPresented: $viewModel.isShowDonationRankingPopup) {
LiveRoomDonationRankingDialog(isShowing: $viewModel.isShowDonationRankingPopup)
}
.sheet(isPresented: $viewModel.isShowDonationMessagePopup) {
LiveRoomDonationMessageDialog(isShowing: $viewModel.isShowDonationMessagePopup)
}
}
func makeAttributedString(_ text: String) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: text)
let urlRegex = try! NSRegularExpression(pattern: "\\b(https?://\\S+\\b|www\\.\\S+\\b)")
let matches = urlRegex.matches(in: text, options: [], range: NSRange(text.startIndex..., in: text))
for match in matches {
let url = (text as NSString).substring(with: match.range)
if let detectedURL = URL(string: url) {
attributedString.addAttribute(.link, value: detectedURL, range: match.range)
}
}
return attributedString
}
private func inviteSpeaker(peerId: Int) {
if viewModel.liveRoomInfo!.speakerList.count <= 4 {
viewModel.inviteSpeaker(peerId: peerId)
self.viewModel.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요."
self.viewModel.isShowPopup = true
} else {
viewModel.popupContent = "스피커 정원을 초과했습니다."
viewModel.isShowPopup = true
}
}
private var scrollObservableView: some View {
GeometryReader { proxy in
let offsetY = proxy.frame(in: .global).origin.y
Color.clear
.preference(
key: ScrollOffsetKey.self,
value: offsetY
)
.onAppear {
viewModel.setOriginOffset(offsetY)
}
}
.frame(height: 0)
}
struct ScrollOffsetKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
@ViewBuilder
func NotificationView(liveRoomInfo: GetRoomInfoResponse) -> some View {
HStack(spacing: 8) {
Image(
viewModel.isShowNotice ?
"ic_notice_selected" :
"ic_notice_normal"
)
.contentShape(Rectangle())
.onTapGesture {
viewModel.isShowNotice.toggle()
}
.padding(.trailing, 10)
Spacer()
HStack(spacing: 4.7) {
Image("ic_can")
.resizable()
.frame(width: 16, height: 16)
Text("\(viewModel.totalDonationCan)")
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "bbbbbb"))
}
.padding(.horizontal, 11.5)
.padding(.vertical, 5.3)
.overlay(
RoundedRectangle(cornerRadius: 12.8)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "eeeeee"))
)
.contentShape(Rectangle())
.onTapGesture {
viewModel.isShowDonationRankingPopup = true
}
if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) {
HStack(spacing: 0) {
Text("참여자")
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "bbbbbb"))
Text("\(liveRoomInfo.participantsCount)")
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "9970ff"))
.padding(.leading, 6.7)
}
.padding(.horizontal, 11.5)
.padding(.vertical, 7.3)
.overlay(
RoundedRectangle(cornerRadius: 12.8)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "eeeeee"))
)
.contentShape(Rectangle())
.onTapGesture {
viewModel.isShowProfileList = true
}
}
}
.padding(.top, 13.3)
.padding(.horizontal, 13.3)
}
@ViewBuilder
func NewChatView(scrollToBottom: @escaping () -> Void) -> some View {
HStack(spacing: 0) {
Spacer()
HStack(spacing: 6.7) {
Image("ic_bottom_white")
Text("새로운 채팅")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee"))
}
.padding(.vertical, 8)
.padding(.horizontal, 13.3)
.background(Color(hex: "555555").opacity(0.8))
.cornerRadius(16.7)
.padding(.bottom, 13.3)
.onTapGesture { scrollToBottom() }
Spacer()
}
}
@ViewBuilder
func InputChatView(scrollToBottom: @escaping () -> Void) -> some View {
HStack(spacing: 0) {
TextField("채팅을 입력하세요", text: $viewModel.chatMessage)
.autocapitalization(.none)
.disableAutocorrection(true)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee"))
.accentColor(Color(hex: "3bb9f1"))
.keyboardType(.default)
.padding(.horizontal, 13.3)
.onTapGesture {
if viewModel.isNoChatting {
viewModel.popupContent = "\(viewModel.remainingNoChattingTime)초 동안 채팅하실 수 없습니다"
viewModel.isShowPopup = true
}
}
Spacer()
Image("btn_message_send")
.resizable()
.frame(width: 35, height: 35)
.padding(6.7)
.onTapGesture {
viewModel.sendMessage()
scrollToBottom()
}
}
.background(Color(hex: "232323"))
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "eeeeee"))
)
.padding(13.3)
}
@ViewBuilder
func ChatView() -> some View {
LazyVGrid(columns: chatColumns, alignment: .leading, spacing: 20) {
ForEach(0..<viewModel.messages.count, id: \.self) { index in
switch (viewModel.messages[index].type) {
case LiveRoomChatType.DONATION:
let chatMessage = viewModel.messages[index] as! LiveRoomDonationChat
LiveRoomDonationChatItemView(chatMessage: chatMessage)
case LiveRoomChatType.JOIN:
let chatMessage = viewModel.messages[index] as! LiveRoomJoinChat
LiveRoomJoinChatItemView(chatMessage: chatMessage)
default:
let chatMessage = viewModel.messages[index] as! LiveRoomNormalChat
LiveRoomChatItemView(
chatMessage: chatMessage,
onClickProfile: {
if chatMessage.userId != UserDefaults.int(forKey: .userId) {
viewModel.getUserProfile(userId: chatMessage.userId)
}
}
)
}
}
}
.rotationEffect(Angle(degrees: 180))
.valueChanged(value: viewModel.messageChangeFlag) { _ in
if viewModel.offset - viewModel.originOffset > (56.7 * 2) {
isShowingNewChat = true
}
}
}
}
struct LiveRoomView_Previews: PreviewProvider {
static var previews: some View {
LiveRoomView()
}
}
struct TextView: UIViewRepresentable {
var text: String
@Binding var dynamicHeight: CGFloat
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.text = text
textView.isEditable = false
textView.isScrollEnabled = true
textView.backgroundColor = .clear
textView.dataDetectorTypes = .link
textView.font = UIFont(name: Font.light.rawValue, size: 11.3)
textView.textColor = .white
textView.textContainer.lineFragmentPadding = 0
textView.textContainerInset = .zero
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
DispatchQueue.main.async {
let height = uiView.sizeThatFits(uiView.frame.size).height
self.dynamicHeight = height > 500 ? 500 : height
}
}
func makeCoordinator() -> Coordinator {
Coordinator($dynamicHeight)
}
class Coordinator: NSObject, UITextViewDelegate {
var dynamicHeight: Binding<CGFloat>
init(_ dynamicHeight: Binding<CGFloat>) {
self.dynamicHeight = dynamicHeight
}
func textViewDidChange(_ textView: UITextView) {
DispatchQueue.main.async {
self.dynamicHeight.wrappedValue = textView.sizeThatFits(textView.frame.size).height
}
}
}
}