라이브 UI 변경
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// LiveRoomNewChatView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/18.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LiveRoomNewChatView: View {
|
||||
|
||||
let scrollToBottom: () -> Void
|
||||
|
||||
var body: 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.grayee)
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 13.3)
|
||||
.background(Color.gray55.opacity(0.8))
|
||||
.cornerRadius(16.7)
|
||||
.padding(.bottom, 13.3)
|
||||
.onTapGesture { scrollToBottom() }
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomNewChatView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomNewChatView {}
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
//
|
||||
// LiveRoomOverlayStrokeImageButton.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/17.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LiveRoomOverlayStrokeImageButton: View {
|
||||
|
||||
let imageName: String
|
||||
let strokeColor: Color
|
||||
let strokeWidth: CGFloat
|
||||
let strokeCornerRadius: CGFloat
|
||||
|
||||
let onClick: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Image(imageName)
|
||||
.padding(4)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: strokeCornerRadius)
|
||||
.stroke(
|
||||
strokeColor,
|
||||
lineWidth: strokeWidth
|
||||
)
|
||||
)
|
||||
.onTapGesture { onClick() }
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomOverlayStrokeImageButton_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomOverlayStrokeImageButton(
|
||||
imageName: "ic_edit",
|
||||
strokeColor: Color.graybb,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3,
|
||||
onClick: {}
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// LiveRoomOverlayStrokeTextButton.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/17.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LiveRoomOverlayStrokeTextButton: View {
|
||||
|
||||
let text: String
|
||||
let textColor: Color
|
||||
let strokeColor: Color
|
||||
let strokeWidth: CGFloat
|
||||
let strokeCornerRadius: CGFloat
|
||||
|
||||
let onClick: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(Color.red)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 6)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: strokeCornerRadius)
|
||||
.stroke(strokeColor, lineWidth: strokeWidth)
|
||||
)
|
||||
.onTapGesture { onClick() }
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomOverlayStrokeTextButton_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomOverlayStrokeTextButton(
|
||||
text: "라이브 종료",
|
||||
textColor: Color.mainRed,
|
||||
strokeColor: Color.mainRed,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3,
|
||||
onClick: {}
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
//
|
||||
// LiveRoomOverlayStrokeTextToggleButton.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/17.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LiveRoomOverlayStrokeTextToggleButton: View {
|
||||
|
||||
let isOn: Bool
|
||||
|
||||
let onText: String
|
||||
let onTextColor: Color
|
||||
let onStrokeColor: Color
|
||||
|
||||
let offText: String?
|
||||
let offTextColor: Color
|
||||
let offStrokeColor: Color
|
||||
|
||||
let strokeWidth: CGFloat
|
||||
let strokeCornerRadius: CGFloat
|
||||
|
||||
let onClick: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Text(isOn ? onText : offText != nil && offText?.count ?? 0 > 0 ? offText! : onText)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(isOn ? onTextColor : offTextColor)
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.vertical, 6)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: strokeCornerRadius)
|
||||
.stroke(
|
||||
isOn ? onStrokeColor : offStrokeColor,
|
||||
lineWidth: strokeWidth
|
||||
)
|
||||
)
|
||||
.onTapGesture { onClick() }
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomOverlayStrokeTextToggleButton_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomOverlayStrokeTextToggleButton(
|
||||
isOn: true,
|
||||
onText: "배경 ON",
|
||||
onTextColor: Color.button,
|
||||
onStrokeColor: Color.button,
|
||||
offText: "배경 OFF",
|
||||
offTextColor: Color.grayee,
|
||||
offStrokeColor: Color.graybb,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3,
|
||||
onClick: {}
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// LiveRoomRightBottomButton.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/17.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LiveRoomRightBottomButton: View {
|
||||
|
||||
let imageName: String
|
||||
let onClick: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Image(imageName)
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.padding(10)
|
||||
.background(Color.gray52.opacity(0.6))
|
||||
.cornerRadius(10)
|
||||
.onTapGesture { onClick() }
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomRightBottomButton_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomRightBottomButton(
|
||||
imageName: "ic_donation",
|
||||
onClick: {}
|
||||
)
|
||||
}
|
||||
}
|
31
SodaLive/Sources/Live/Room/V2/Component/Text/TextView.swift
Normal file
31
SodaLive/Sources/Live/Room/V2/Component/Text/TextView.swift
Normal file
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// TextView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/18.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
struct DetectableTextView: UIViewRepresentable {
|
||||
var text: String
|
||||
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
let textView = UITextView()
|
||||
textView.isEditable = false // Make it readonly
|
||||
textView.backgroundColor = .clear
|
||||
textView.isScrollEnabled = true
|
||||
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
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
//
|
||||
// LiveRoomChatView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/17.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LiveRoomChatView: View {
|
||||
|
||||
let messages: [LiveRoomChat]
|
||||
let getUserProfile: (Int) -> Void
|
||||
|
||||
var body: some View {
|
||||
LazyVStack(alignment: .leading, spacing: 18) {
|
||||
ForEach(0..<messages.count, id: \.self) { index in
|
||||
switch (messages[index].type) {
|
||||
case LiveRoomChatType.ROULETTE_DONATION:
|
||||
let chatMessage = messages[index] as! LiveRoomRouletteDonationChat
|
||||
LiveRoomRouletteDonationChatItemView(chatMessage: chatMessage)
|
||||
|
||||
case LiveRoomChatType.DONATION:
|
||||
let chatMessage = messages[index] as! LiveRoomDonationChat
|
||||
LiveRoomDonationChatItemView(chatMessage: chatMessage)
|
||||
|
||||
case LiveRoomChatType.JOIN:
|
||||
let chatMessage = messages[index] as! LiveRoomJoinChat
|
||||
LiveRoomJoinChatItemView(chatMessage: chatMessage)
|
||||
|
||||
default:
|
||||
let chatMessage = messages[index] as! LiveRoomNormalChat
|
||||
LiveRoomChatItemView(
|
||||
chatMessage: chatMessage,
|
||||
onClickProfile: {
|
||||
if chatMessage.userId != UserDefaults.int(forKey: .userId) {
|
||||
getUserProfile(chatMessage.userId)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomChatView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomChatView(
|
||||
messages: [
|
||||
LiveRoomRouletteDonationChat(
|
||||
profileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||
nickname: "jkljkljkl",
|
||||
rouletteResult: "sdfjkldfsjkl",
|
||||
type: .ROULETTE_DONATION
|
||||
),
|
||||
LiveRoomRouletteDonationChat(
|
||||
profileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||
nickname: "jkljkljkl",
|
||||
rouletteResult: "sdfjkldfsjkl",
|
||||
type: .ROULETTE_DONATION
|
||||
)
|
||||
],
|
||||
getUserProfile: { _ in }
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// LiveRoomInfoCreatorView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/17.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct LiveRoomInfoCreatorView: View {
|
||||
|
||||
let roomTitle: String
|
||||
let creatorNickname: String
|
||||
let creatorProfileUrl: String
|
||||
|
||||
let isMute: Bool
|
||||
let isAdult: Bool
|
||||
let isFollowing: Bool
|
||||
let isActiveSpeaker: Bool
|
||||
let isShowFollowingButton: Bool
|
||||
|
||||
let onClickFollow: () -> Void
|
||||
let onClickProfile: () -> Void
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 5.3) {
|
||||
ZStack(alignment: .center) {
|
||||
KFImage(URL(string: creatorProfileUrl))
|
||||
.resizable()
|
||||
.frame(width: 33.3, height: 33.3)
|
||||
.clipShape(Circle())
|
||||
.overlay(
|
||||
Circle()
|
||||
.stroke(
|
||||
Color.button,
|
||||
lineWidth: isActiveSpeaker ? 3 : 0
|
||||
)
|
||||
)
|
||||
.onTapGesture { onClickProfile() }
|
||||
|
||||
if isMute {
|
||||
Image("ic_mute")
|
||||
.resizable()
|
||||
.frame(width: 33.3, height: 33.3)
|
||||
}
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 2.7) {
|
||||
HStack(spacing: 2.7) {
|
||||
if isAdult {
|
||||
Text("19")
|
||||
.font(.custom(Font.bold.rawValue, size: 8))
|
||||
.foregroundColor(.white)
|
||||
.padding(.vertical, 2.8)
|
||||
.padding(.horizontal, 2)
|
||||
.background(Circle().foregroundColor(Color.mainRed2))
|
||||
}
|
||||
|
||||
Text(roomTitle)
|
||||
.font(.custom(Font.bold.rawValue, size: 12))
|
||||
.foregroundColor(.grayee)
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
Text(creatorNickname)
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(.gray77)
|
||||
}
|
||||
|
||||
if isShowFollowingButton {
|
||||
Image(isFollowing ? "btn_select_checked" : "btn_plus_round")
|
||||
.resizable()
|
||||
.frame(width: 20, height: 20)
|
||||
.onTapGesture { onClickFollow() }
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
.padding(.horizontal, 5.3)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 5.3)
|
||||
.stroke(Color.graybb, lineWidth: 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomInfoCreatorView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomInfoCreatorView(
|
||||
roomTitle: "오늘 라이브방송은 OOO 입니다.",
|
||||
creatorNickname: "도화",
|
||||
creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||
isMute: false,
|
||||
isAdult: false,
|
||||
isFollowing: true,
|
||||
isActiveSpeaker: true,
|
||||
isShowFollowingButton: true,
|
||||
onClickFollow: {},
|
||||
onClickProfile: {}
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,190 @@
|
||||
//
|
||||
// LiveRoomInfoGuestView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/17.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LiveRoomInfoGuestView: View {
|
||||
|
||||
let title: String
|
||||
let totalDonationCan: Int
|
||||
|
||||
let isOnBg: Bool
|
||||
let isOnNotice: Bool
|
||||
|
||||
let creatorId: Int
|
||||
let creatorNickname: String
|
||||
let creatorProfileUrl: String
|
||||
let speakerList: [LiveRoomMember]
|
||||
let muteSpeakerList: [UInt]
|
||||
let activeSpeakerList: [UInt]
|
||||
|
||||
let isFollowing: Bool
|
||||
let isAdult: Bool
|
||||
|
||||
let onClickQuit: () -> Void
|
||||
let onClickToggleBg: () -> Void
|
||||
let onClickShare: () -> Void
|
||||
let onClickFollow: (Bool) -> Void
|
||||
let onClickProfile: (Int) -> Void
|
||||
let onClickNotice: () -> Void
|
||||
let onClickTotalDonation: () -> Void
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
VStack(spacing: 13.3) {
|
||||
HStack(spacing: 5.3) {
|
||||
LiveRoomOverlayStrokeTextButton(
|
||||
text: "나가기",
|
||||
textColor: Color.red,
|
||||
strokeColor: Color.red,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3
|
||||
) { onClickQuit() }
|
||||
|
||||
Spacer()
|
||||
|
||||
LiveRoomOverlayStrokeTextToggleButton(
|
||||
isOn: isOnBg,
|
||||
onText: "배경 ON",
|
||||
onTextColor: Color.button,
|
||||
onStrokeColor: Color.button,
|
||||
offText: "배경 OFF",
|
||||
offTextColor: Color.graybb,
|
||||
offStrokeColor: Color.graybb,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3
|
||||
) { onClickToggleBg() }
|
||||
|
||||
LiveRoomOverlayStrokeImageButton(
|
||||
imageName: "ic_share",
|
||||
strokeColor: Color.graybb,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3
|
||||
) { onClickShare() }
|
||||
}
|
||||
|
||||
HStack(spacing: 8) {
|
||||
LiveRoomInfoCreatorView(
|
||||
roomTitle: title,
|
||||
creatorNickname: creatorNickname,
|
||||
creatorProfileUrl: creatorProfileUrl,
|
||||
isMute: muteSpeakerList.contains(UInt(creatorId)),
|
||||
isAdult: isAdult,
|
||||
isFollowing: isFollowing,
|
||||
isActiveSpeaker: activeSpeakerList.contains(UInt(creatorId)),
|
||||
isShowFollowingButton: true,
|
||||
onClickFollow: { onClickFollow(isFollowing) },
|
||||
onClickProfile: { onClickProfile(creatorId) }
|
||||
)
|
||||
.frame(width: 180, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
ForEach(0..<speakerList.count, id: \.self) { index in
|
||||
let speaker = speakerList[index]
|
||||
|
||||
if speaker.id != UInt(creatorId) {
|
||||
LiveRoomInfoSpeakerView(
|
||||
nickname: speaker.nickname,
|
||||
profileUrl: speaker.profileImage,
|
||||
isMute: muteSpeakerList.contains(UInt(speaker.id)),
|
||||
isActiveSpeaker: activeSpeakerList.contains(UInt(speaker.id)),
|
||||
onClickProfile: { onClickProfile(speaker.id) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HStack(spacing: 5.3) {
|
||||
LiveRoomOverlayStrokeTextToggleButton(
|
||||
isOn: isOnNotice,
|
||||
onText: "공지",
|
||||
onTextColor: .button,
|
||||
onStrokeColor: .button,
|
||||
offText: nil,
|
||||
offTextColor: .graybb,
|
||||
offStrokeColor: .graybb,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3,
|
||||
onClick: { onClickNotice() }
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 2.7) {
|
||||
Image("ic_can")
|
||||
.resizable()
|
||||
.frame(width: 12, height: 12)
|
||||
|
||||
Text("\(totalDonationCan)")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(.graybb)
|
||||
}
|
||||
.padding(.horizontal, 11)
|
||||
.padding(.vertical, 5.3)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 5.3)
|
||||
.stroke(Color.graybb, lineWidth: 1)
|
||||
)
|
||||
.onTapGesture { onClickTotalDonation() }
|
||||
}
|
||||
}
|
||||
|
||||
if muteSpeakerList.contains(UInt(creatorId)) {
|
||||
Image("img_noti_mute")
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 13.3)
|
||||
.padding(.top, 16)
|
||||
.background(Color.gray22)
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomInfoGuestView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomInfoGuestView(
|
||||
title: "오늘의 라이브방송은 OOO입니다.",
|
||||
totalDonationCan: 123456,
|
||||
isOnBg: true,
|
||||
isOnNotice: false,
|
||||
creatorId: 1,
|
||||
creatorNickname: "도화",
|
||||
creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||
speakerList: [
|
||||
LiveRoomMember(
|
||||
id: 1,
|
||||
nickname: "도화",
|
||||
profileImage: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||
role: .SPEAKER
|
||||
),
|
||||
LiveRoomMember(
|
||||
id: 2,
|
||||
nickname: "청령",
|
||||
profileImage: "https://cf.sodalive.net/profile/13/13-profile-fabb75e0-2870-4d99-900e-1d9aa63e605b-685-1704859996417",
|
||||
role: .SPEAKER
|
||||
),
|
||||
LiveRoomMember(
|
||||
id: 3,
|
||||
nickname: "LUNA",
|
||||
profileImage: "https://cf.sodalive.net/profile/4679/4679-profile-41e83399-234e-4541-8591-f961a025cfaa-5819-1699536915310",
|
||||
role: .SPEAKER
|
||||
),
|
||||
],
|
||||
muteSpeakerList: [],
|
||||
activeSpeakerList: [],
|
||||
isFollowing: false,
|
||||
isAdult: false,
|
||||
onClickQuit: {},
|
||||
onClickToggleBg: {},
|
||||
onClickShare: {},
|
||||
onClickFollow: { _ in },
|
||||
onClickProfile: { _ in },
|
||||
onClickNotice: {},
|
||||
onClickTotalDonation: {}
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,217 @@
|
||||
//
|
||||
// LiveRoomInfoHostView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/17.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct LiveRoomInfoHostView: View {
|
||||
|
||||
let title: String
|
||||
let totalDonationCan: Int
|
||||
let participantsCount: Int
|
||||
|
||||
let isOnBg: Bool
|
||||
let isOnNotice: Bool
|
||||
|
||||
let creatorId: Int
|
||||
let creatorNickname: String
|
||||
let creatorProfileUrl: String
|
||||
let speakerList: [LiveRoomMember]
|
||||
let muteSpeakerList: [UInt]
|
||||
let activeSpeakerList: [UInt]
|
||||
|
||||
let isAdult: Bool
|
||||
|
||||
let onClickQuit: () -> Void
|
||||
let onClickToggleBg: () -> Void
|
||||
let onClickShare: () -> Void
|
||||
let onClickEdit: () -> Void
|
||||
let onClickProfile: (Int) -> Void
|
||||
let onClickNotice: () -> Void
|
||||
let onClickTotalDonation: () -> Void
|
||||
let onClickParticipants: () -> Void
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
VStack(alignment: .leading, spacing: 13.3) {
|
||||
HStack(spacing: 5.3) {
|
||||
LiveRoomOverlayStrokeTextButton(
|
||||
text: "라이브 종료",
|
||||
textColor: Color.red,
|
||||
strokeColor: Color.red,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3
|
||||
) { onClickQuit() }
|
||||
|
||||
Spacer()
|
||||
|
||||
LiveRoomOverlayStrokeTextToggleButton(
|
||||
isOn: isOnBg,
|
||||
onText: "배경 ON",
|
||||
onTextColor: Color.button,
|
||||
onStrokeColor: Color.button,
|
||||
offText: "배경 OFF",
|
||||
offTextColor: Color.graybb,
|
||||
offStrokeColor: Color.graybb,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3
|
||||
) { onClickToggleBg() }
|
||||
|
||||
LiveRoomOverlayStrokeImageButton(
|
||||
imageName: "ic_share",
|
||||
strokeColor: Color.graybb,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3
|
||||
) { onClickShare() }
|
||||
|
||||
LiveRoomOverlayStrokeImageButton(
|
||||
imageName: "ic_edit",
|
||||
strokeColor: Color.graybb,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3
|
||||
) { onClickEdit() }
|
||||
}
|
||||
|
||||
HStack(spacing: 8) {
|
||||
LiveRoomInfoCreatorView(
|
||||
roomTitle: title,
|
||||
creatorNickname: creatorNickname,
|
||||
creatorProfileUrl: creatorProfileUrl,
|
||||
isMute: muteSpeakerList.contains(UInt(creatorId)),
|
||||
isAdult: isAdult,
|
||||
isFollowing: false,
|
||||
isActiveSpeaker: activeSpeakerList.contains(UInt(creatorId)),
|
||||
isShowFollowingButton: false,
|
||||
onClickFollow: {},
|
||||
onClickProfile: {}
|
||||
)
|
||||
.frame(width: 180, alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
ForEach(0..<speakerList.count, id: \.self) { index in
|
||||
let speaker = speakerList[index]
|
||||
|
||||
if speaker.id != UInt(creatorId) {
|
||||
LiveRoomInfoSpeakerView(
|
||||
nickname: speaker.nickname,
|
||||
profileUrl: speaker.profileImage,
|
||||
isMute: muteSpeakerList.contains(UInt(speaker.id)),
|
||||
isActiveSpeaker: activeSpeakerList.contains(UInt(speaker.id)),
|
||||
onClickProfile: { onClickProfile(speaker.id) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HStack(spacing: 5.3) {
|
||||
LiveRoomOverlayStrokeTextToggleButton(
|
||||
isOn: isOnNotice,
|
||||
onText: "공지",
|
||||
onTextColor: .button,
|
||||
onStrokeColor: .button,
|
||||
offText: nil,
|
||||
offTextColor: .graybb,
|
||||
offStrokeColor: .graybb,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3,
|
||||
onClick: { onClickNotice() }
|
||||
)
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 2.7) {
|
||||
Image("ic_can")
|
||||
.resizable()
|
||||
.frame(width: 12, height: 12)
|
||||
|
||||
Text("\(totalDonationCan)")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(.graybb)
|
||||
}
|
||||
.padding(.horizontal, 11)
|
||||
.padding(.vertical, 5.3)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 5.3)
|
||||
.stroke(Color.graybb, lineWidth: 1)
|
||||
)
|
||||
.onTapGesture { onClickTotalDonation() }
|
||||
|
||||
HStack(spacing: 6.7) {
|
||||
Text("참여자")
|
||||
.font(.custom(Font.medium.rawValue, size: 12))
|
||||
.foregroundColor(.graybb)
|
||||
|
||||
Text("\(participantsCount)")
|
||||
.font(.custom(Font.bold.rawValue, size: 12))
|
||||
.foregroundColor(.graybb)
|
||||
}
|
||||
.padding(.horizontal, 11)
|
||||
.padding(.vertical, 5.3)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 5.3)
|
||||
.stroke(Color.graybb, lineWidth: 1)
|
||||
)
|
||||
.onTapGesture { onClickParticipants() }
|
||||
}
|
||||
}
|
||||
|
||||
if muteSpeakerList.contains(UInt(creatorId)) {
|
||||
Image("img_noti_mute")
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 13.3)
|
||||
.padding(.top, 16)
|
||||
.background(Color.gray22)
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomInfoHostView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomInfoHostView(
|
||||
title: "오늘의 라이브방송은 OOO입니다.",
|
||||
totalDonationCan: 123456,
|
||||
participantsCount: 18,
|
||||
isOnBg: true,
|
||||
isOnNotice: true,
|
||||
creatorId: 1,
|
||||
creatorNickname: "도화",
|
||||
creatorProfileUrl: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||
speakerList: [
|
||||
LiveRoomMember(
|
||||
id: 1,
|
||||
nickname: "도화",
|
||||
profileImage: "https://cf.sodalive.net/profile/26/26-profile-ddf78b4d-0300-4c50-9c84-5d8a95fd5fe2-4892-1705256364320",
|
||||
role: .SPEAKER
|
||||
),
|
||||
LiveRoomMember(
|
||||
id: 2,
|
||||
nickname: "청령",
|
||||
profileImage: "https://cf.sodalive.net/profile/13/13-profile-fabb75e0-2870-4d99-900e-1d9aa63e605b-685-1704859996417",
|
||||
role: .SPEAKER
|
||||
),
|
||||
LiveRoomMember(
|
||||
id: 3,
|
||||
nickname: "LUNA",
|
||||
profileImage: "https://cf.sodalive.net/profile/4679/4679-profile-41e83399-234e-4541-8591-f961a025cfaa-5819-1699536915310",
|
||||
role: .SPEAKER
|
||||
),
|
||||
],
|
||||
muteSpeakerList: [],
|
||||
activeSpeakerList: [],
|
||||
isAdult: false,
|
||||
onClickQuit: {},
|
||||
onClickToggleBg: {},
|
||||
onClickShare: {},
|
||||
onClickEdit: {},
|
||||
onClickProfile: { _ in },
|
||||
onClickNotice: {},
|
||||
onClickTotalDonation: {},
|
||||
onClickParticipants: {}
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// LiveRoomInfoSpeakerView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/17.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct LiveRoomInfoSpeakerView: View {
|
||||
|
||||
let nickname: String
|
||||
let profileUrl: String
|
||||
|
||||
let isMute: Bool
|
||||
let isActiveSpeaker: Bool
|
||||
|
||||
let onClickProfile: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 2.7) {
|
||||
ZStack(alignment: .center) {
|
||||
KFImage(URL(string: profileUrl))
|
||||
.resizable()
|
||||
.frame(width: 26.7, height: 26.7)
|
||||
.clipShape(Circle())
|
||||
.overlay(
|
||||
Circle()
|
||||
.stroke(
|
||||
Color.button,
|
||||
lineWidth: isActiveSpeaker ? 3 : 0
|
||||
)
|
||||
)
|
||||
|
||||
if isMute {
|
||||
Image("ic_mute")
|
||||
.resizable()
|
||||
.frame(width: 30, height: 30)
|
||||
}
|
||||
}
|
||||
|
||||
Text(nickname)
|
||||
.font(.custom(Font.medium.rawValue, fixedSize: 10.7))
|
||||
.foregroundColor(.gray77)
|
||||
}
|
||||
.frame(width: 30, height: 30)
|
||||
.onTapGesture { onClickProfile() }
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomInfoSpeakerView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomInfoSpeakerView(
|
||||
nickname: "청령",
|
||||
profileUrl: "https://cf.sodalive.net/profile/13/13-profile-fabb75e0-2870-4d99-900e-1d9aa63e605b-685-1704859996417",
|
||||
isMute: false,
|
||||
isActiveSpeaker: true,
|
||||
onClickProfile: {}
|
||||
)
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
//
|
||||
// LiveRoomInputChatView.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/17.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct LiveRoomInputChatView: View {
|
||||
|
||||
@State private var chatMessage = ""
|
||||
|
||||
|
||||
let sendMessage: (String) -> Bool
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 6.7) {
|
||||
TextField("채팅을 입력하세요", text: $chatMessage)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.font(.custom(Font.medium.rawValue, size: 13.3))
|
||||
.foregroundColor(.graybb)
|
||||
.accentColor(.button)
|
||||
.keyboardType(.default)
|
||||
.padding(.horizontal, 13.3)
|
||||
.padding(.vertical, 18.3)
|
||||
.background(Color.gray22)
|
||||
.cornerRadius(5.3)
|
||||
.frame(maxWidth: .infinity)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 5.3)
|
||||
.strokeBorder(lineWidth: 1)
|
||||
.foregroundColor(.gray77)
|
||||
)
|
||||
|
||||
Image("btn_message_send")
|
||||
.resizable()
|
||||
.frame(width: 35, height: 35)
|
||||
.padding(6.7)
|
||||
.onTapGesture {
|
||||
if sendMessage(chatMessage) {
|
||||
chatMessage = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(13.3)
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomInputChatView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomInputChatView(sendMessage: { _ in return true })
|
||||
}
|
||||
}
|
668
SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift
Normal file
668
SodaLive/Sources/Live/Room/V2/LiveRoomViewV2.swift
Normal file
@@ -0,0 +1,668 @@
|
||||
//
|
||||
// LiveRoomViewV2.swift
|
||||
// SodaLive
|
||||
//
|
||||
// Created by klaus on 2024/01/17.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Kingfisher
|
||||
|
||||
struct LiveRoomViewV2: View {
|
||||
|
||||
@StateObject var keyboardHandler = KeyboardHandler()
|
||||
@StateObject var viewModel = LiveRoomViewModel()
|
||||
|
||||
@State private var textHeight: CGFloat = .zero
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.black.edgesIgnoringSafeArea(.all)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
if let liveRoomInfo = viewModel.liveRoomInfo {
|
||||
if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) {
|
||||
LiveRoomInfoHostView(
|
||||
title: liveRoomInfo.title,
|
||||
totalDonationCan: viewModel.totalDonationCan,
|
||||
participantsCount: liveRoomInfo.participantsCount,
|
||||
isOnBg: viewModel.isBgOn,
|
||||
isOnNotice: viewModel.isShowNotice,
|
||||
creatorId: liveRoomInfo.creatorId,
|
||||
creatorNickname: liveRoomInfo.creatorNickname,
|
||||
creatorProfileUrl: liveRoomInfo.creatorProfileUrl,
|
||||
speakerList: liveRoomInfo.speakerList,
|
||||
muteSpeakerList: viewModel.muteSpeakers,
|
||||
activeSpeakerList: viewModel.activeSpeakers,
|
||||
isAdult: liveRoomInfo.isAdult,
|
||||
onClickQuit: {
|
||||
viewModel.isShowLiveEndPopup = true
|
||||
},
|
||||
onClickToggleBg: {
|
||||
viewModel.isBgOn.toggle()
|
||||
},
|
||||
onClickShare: {
|
||||
viewModel.shareRoom()
|
||||
},
|
||||
onClickEdit: {
|
||||
viewModel.isShowEditRoomInfoDialog = true
|
||||
},
|
||||
onClickProfile: {
|
||||
if $0 != UserDefaults.int(forKey: .userId) {
|
||||
viewModel.getUserProfile(userId: $0)
|
||||
}
|
||||
},
|
||||
onClickNotice: {
|
||||
viewModel.isShowNotice.toggle()
|
||||
},
|
||||
onClickTotalDonation: {
|
||||
viewModel.isShowDonationRankingPopup = true
|
||||
},
|
||||
onClickParticipants: {
|
||||
viewModel.isShowProfileList = true
|
||||
}
|
||||
)
|
||||
} else {
|
||||
LiveRoomInfoGuestView(
|
||||
title: liveRoomInfo.title,
|
||||
totalDonationCan: viewModel.totalDonationCan,
|
||||
isOnBg: viewModel.isBgOn,
|
||||
isOnNotice: viewModel.isShowNotice,
|
||||
creatorId: liveRoomInfo.creatorId,
|
||||
creatorNickname: liveRoomInfo.creatorNickname,
|
||||
creatorProfileUrl: liveRoomInfo.creatorProfileUrl,
|
||||
speakerList: liveRoomInfo.speakerList,
|
||||
muteSpeakerList: viewModel.muteSpeakers,
|
||||
activeSpeakerList: viewModel.activeSpeakers,
|
||||
isFollowing: liveRoomInfo.isFollowing,
|
||||
isAdult: liveRoomInfo.isAdult,
|
||||
onClickQuit: {
|
||||
viewModel.isShowQuitPopup = true
|
||||
},
|
||||
onClickToggleBg: {
|
||||
viewModel.isBgOn.toggle()
|
||||
},
|
||||
onClickShare: {
|
||||
viewModel.shareRoom()
|
||||
},
|
||||
onClickFollow: {
|
||||
if $0 {
|
||||
viewModel.creatorUnFollow()
|
||||
} else {
|
||||
viewModel.creatorFollow()
|
||||
}
|
||||
},
|
||||
onClickProfile: {
|
||||
if $0 != UserDefaults.int(forKey: .userId) {
|
||||
viewModel.getUserProfile(userId: $0)
|
||||
}
|
||||
},
|
||||
onClickNotice: {
|
||||
viewModel.isShowNotice.toggle()
|
||||
},
|
||||
onClickTotalDonation: {
|
||||
viewModel.isShowDonationRankingPopup = true
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
ZStack(alignment: .topLeading) {
|
||||
Rectangle()
|
||||
.foregroundColor(.gray22)
|
||||
.frame(height: 16)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
ScrollViewReader { proxy in
|
||||
ZStack(alignment: .bottom) {
|
||||
ZStack {
|
||||
if viewModel.isBgOn {
|
||||
KFImage(URL(string: liveRoomInfo.coverImageUrl))
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
Rectangle()
|
||||
.foregroundColor(.black.opacity(0.25))
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
scrollObservableView
|
||||
|
||||
LiveRoomChatView(messages: viewModel.messages) {
|
||||
if $0 != UserDefaults.int(forKey: .userId) {
|
||||
viewModel.getUserProfile(userId: $0)
|
||||
}
|
||||
}
|
||||
.frame(width: screenSize().width)
|
||||
.rotationEffect(Angle(degrees: 180))
|
||||
.valueChanged(value: viewModel.messageChangeFlag) { _ in
|
||||
if viewModel.offset - viewModel.originOffset > (56.7 * 2) {
|
||||
viewModel.isShowingNewChat = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.rotationEffect(Angle(degrees: 180))
|
||||
.onTapGesture { hideKeyboard() }
|
||||
.onPreferenceChange(ScrollOffsetKey.self) {
|
||||
viewModel.setOffset($0)
|
||||
}
|
||||
.padding(.bottom, 70)
|
||||
}
|
||||
.padding(.top, 16)
|
||||
|
||||
VStack(alignment: .trailing, spacing: 0) {
|
||||
Spacer()
|
||||
|
||||
VStack(spacing: 13.3) {
|
||||
if liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId) {
|
||||
Image("ic_roulette_settings")
|
||||
.resizable()
|
||||
.frame(width: 26.7, height: 26.7)
|
||||
.padding(11)
|
||||
.background(Color(hex: "525252").opacity(0.6))
|
||||
.cornerRadius(10)
|
||||
.onTapGesture {
|
||||
viewModel.isShowRouletteSettings = true
|
||||
}
|
||||
} else if liveRoomInfo.creatorId != UserDefaults.int(forKey: .userId) && viewModel.isActiveRoulette {
|
||||
Image("ic_roulette")
|
||||
.resizable()
|
||||
.frame(width: 26.7, height: 26.7)
|
||||
.padding(11)
|
||||
.background(Color(hex: "525252").opacity(0.6))
|
||||
.cornerRadius(10)
|
||||
.onTapGesture {
|
||||
viewModel.showRoulette()
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
.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)
|
||||
.onTapGesture {
|
||||
viewModel.toggleSpeakerMute()
|
||||
}
|
||||
|
||||
if 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, 13.3)
|
||||
|
||||
LiveRoomInputChatView {
|
||||
viewModel.sendMessage(chatMessage: $0) {
|
||||
viewModel.isShowingNewChat = false
|
||||
proxy.scrollTo(viewModel.messages.count - 1, anchor: .center)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
|
||||
if viewModel.isShowingNewChat {
|
||||
LiveRoomNewChatView{
|
||||
viewModel.isShowingNewChat = false
|
||||
proxy.scrollTo(viewModel.messages.count - 1, anchor: .center)
|
||||
}.padding(.bottom, 70)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.isShowNotice {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Image("ic_notice_triangle")
|
||||
.padding(.leading, 13.3)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("[방송공지]")
|
||||
.font(.custom(Font.bold.rawValue, size: 11.3))
|
||||
.foregroundColor(.white)
|
||||
|
||||
DetectableTextView(text: liveRoomInfo.notice)
|
||||
.frame(
|
||||
width: 280,
|
||||
height: textHeight > 450 ? 450 : textHeight
|
||||
)
|
||||
.onAppear {
|
||||
self.textHeight = self.estimatedHeight(
|
||||
for: liveRoomInfo.notice,
|
||||
width: 280
|
||||
)
|
||||
}
|
||||
.onChange(of: liveRoomInfo.notice) { newText in
|
||||
self.textHeight = self.estimatedHeight(
|
||||
for: newText,
|
||||
width: 280
|
||||
)
|
||||
}
|
||||
}
|
||||
.padding(8)
|
||||
.background(Color.gray33)
|
||||
.padding(.horizontal, 13.3)
|
||||
.padding(.bottom, 120)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.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.button)
|
||||
.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.button)
|
||||
.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.isShowRouletteSettings {
|
||||
RouletteSettingsView(isShowing: $viewModel.isShowRouletteSettings) { isActiveRoulette in
|
||||
self.viewModel.setActiveRoulette(isActiveRoulette: isActiveRoulette)
|
||||
}
|
||||
}
|
||||
|
||||
if let preview = viewModel.roulettePreview, viewModel.isShowRoulettePreview {
|
||||
RoulettePreviewDialog(
|
||||
isShowing: $viewModel.isShowRoulettePreview,
|
||||
title: nil,
|
||||
onClickSpin: { viewModel.spinRoulette() },
|
||||
preview: preview
|
||||
)
|
||||
}
|
||||
|
||||
if viewModel.isShowRoulette {
|
||||
RouletteViewDialog(isShowing: $viewModel.isShowRoulette, options: viewModel.rouletteItems, selectedOption: viewModel.rouletteSelectedItem) {
|
||||
viewModel.sendRouletteDonation()
|
||||
}
|
||||
}
|
||||
|
||||
if viewModel.isLoading && viewModel.liveRoomInfo == nil {
|
||||
LoadingView()
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea(.keyboard)
|
||||
.edgesIgnoringSafeArea(keyboardHandler.keyboardHeight > 0 ? .bottom : .init())
|
||||
.sheet(
|
||||
isPresented: $viewModel.isShowShareView,
|
||||
onDismiss: { viewModel.shareMessage = "" },
|
||||
content: {
|
||||
ActivityViewController(activityItems: [viewModel.shareMessage])
|
||||
}
|
||||
)
|
||||
.sheet(isPresented: $viewModel.isShowPhotoPicker) {
|
||||
ImagePicker(
|
||||
isShowing: $viewModel.isShowPhotoPicker,
|
||||
selectedImage: $viewModel.coverImage,
|
||||
sourceType: .photoLibrary
|
||||
)
|
||||
}
|
||||
.sheet(isPresented: $viewModel.isShowEditRoomInfoDialog) {
|
||||
if let liveRoomInfo = viewModel.liveRoomInfo {
|
||||
LiveRoomInfoEditDialog(
|
||||
isShowing: $viewModel.isShowEditRoomInfoDialog,
|
||||
isShowPhotoPicker: $viewModel.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)
|
||||
}
|
||||
}
|
||||
|
||||
private func estimatedHeight(for text: String, width: CGFloat) -> CGFloat {
|
||||
let textView = UITextView(frame: CGRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude))
|
||||
textView.font = UIFont.systemFont(ofSize: 11.3)
|
||||
textView.text = text
|
||||
return textView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude)).height
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomViewV2_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomViewV2()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user