라이브 방 추가

This commit is contained in:
Yu Sung
2023-08-15 01:22:15 +09:00
parent 634f50d4f2
commit 0f8b740469
92 changed files with 5213 additions and 20 deletions

View File

@@ -0,0 +1,72 @@
//
// LiveRoomDialogView.swift
// SodaLive
//
// Created by klaus on 2023/08/14.
//
import SwiftUI
struct LiveRoomDialogView: View {
let content: String
let cancelTitle: String?
let cancelAction: (() -> Void)?
let confirmTitle: String?
let confirmAction: (() -> Void)?
var body: some View {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .center, spacing: 11.7) {
Image("ic_request_speak")
.resizable()
.frame(width: 36, height: 36)
Text(content)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee"))
}
.padding(.horizontal, 26.7)
HStack(spacing: 10) {
Spacer()
if let cancelTitle = cancelTitle, let cancelAction = cancelAction {
Text(cancelTitle)
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(.white)
.padding(.horizontal, 8)
.padding(.vertical, 8.3)
.background(Color.white.opacity(0.2))
.cornerRadius(13.3)
.overlay(
RoundedRectangle(cornerRadius: 13.3)
.strokeBorder()
.foregroundColor(.white)
)
.onTapGesture { cancelAction() }
}
if let confirmTitle = confirmTitle, let confirmAction = confirmAction {
Text(confirmTitle)
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: "9970ff"))
.padding(.horizontal, 8)
.padding(.vertical, 8.3)
.background(Color.white)
.cornerRadius(13.3)
.onTapGesture { confirmAction() }
}
}
.padding(.horizontal, 26.7)
.padding(.top, confirmTitle != nil || cancelTitle != nil ? 10 : 0)
}
.padding(.vertical, 20)
.background(Color(hex: "9970ff"))
.cornerRadius(16.7)
.padding(.horizontal, 26.7)
.frame(width: screenSize().width)
}
}

View File

@@ -0,0 +1,118 @@
//
// LiveRoomDonationMessageDialog.swift
// SodaLive
//
// Created by klaus on 2023/08/15.
//
import SwiftUI
struct LiveRoomDonationMessageDialog: View {
@Binding var isShowing: Bool
@StateObject var viewModel = LiveRoomViewModel()
var body: some View {
ZStack {
Color.black
.opacity(0.7)
.ignoresSafeArea()
.onTapGesture {
hideKeyboard()
}
VStack(spacing: 0) {
HStack(spacing: 0) {
Text("후원메시지")
.font(.custom(Font.bold.rawValue, size: 14.7))
.foregroundColor(Color(hex: "eeeeee"))
Text("(\(viewModel.donationMessageCount))")
.font(.custom(Font.medium.rawValue, size: 14.7))
.foregroundColor(Color(hex: "eeeeee"))
Spacer()
Text("닫기")
.font(.custom(Font.light.rawValue, size: 14.7))
.foregroundColor(Color(hex: "eeeeee"))
.onTapGesture { isShowing = false }
}
ScrollView {
if viewModel.donationMessageList.count > 0 {
LazyVStack(spacing: 10.7) {
ForEach(0..<viewModel.donationMessageList.count, id: \.self) { index in
let donationMessage = viewModel.donationMessageList[index]
HStack(alignment: .top, spacing: 0) {
VStack(alignment: .leading, spacing: 8) {
Text("\(donationMessage.nickname)님이")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(.white)
Text("\(donationMessage.canMessage)")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(.white)
Text("'\(donationMessage.donationMessage)'")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(.white)
}
Spacer()
Image("ic_close_white")
.resizable()
.frame(width: 13.3, height: 13.3)
.onTapGesture {
viewModel.deleteDonationMessage(uuid: donationMessage.uuid)
}
}
.padding(13.3)
.background(Color(hex: "333333"))
.cornerRadius(5.3)
.onTapGesture {
UIPasteboard.general.string = donationMessage.donationMessage
self.viewModel.errorMessage = "후원 메시지가 복사되었습니다."
self.viewModel.isShowPopup = true
}
}
}
.padding(.top, 18.7)
} else {
Text("후원메시지가 없습니다.")
.font(.custom(Font.medium.rawValue, size: 14.7))
.foregroundColor(Color(hex: "eeeeee"))
.padding(.top, 30)
}
}
}
.padding(20)
.background(Color(hex: "222222"))
.cornerRadius(8)
if viewModel.isLoading {
LoadingView()
}
}
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) {
HStack {
Spacer()
Text(viewModel.errorMessage)
.padding(.vertical, 13.3)
.frame(width: screenSize().width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12))
.background(Color(hex: "9970ff"))
.foregroundColor(Color.white)
.multilineTextAlignment(.leading)
.cornerRadius(20)
.padding(.bottom, 66.7)
Spacer()
}
}
.onAppear {
viewModel.getDonationMessageList()
}
}
}

View File

@@ -0,0 +1,95 @@
//
// LiveRoomDonationRankingDialog.swift
// SodaLive
//
// Created by klaus on 2023/08/15.
//
import SwiftUI
struct LiveRoomDonationRankingDialog: View {
@Binding var isShowing: Bool
@StateObject var viewModel = LiveRoomViewModel()
let columns = [GridItem(.flexible())]
var body: some View {
ZStack {
VStack(spacing: 0) {
HStack(spacing: 0) {
Text("현재 라이브 후원랭킹")
.font(.custom(Font.bold.rawValue, size: 14.7))
.foregroundColor(Color(hex: "eeeeee"))
Spacer()
Image("ic_close_white")
.onTapGesture { isShowing = false }
}
if let donationStatus = viewModel.donationStatus {
LiveRoomDonationRankingTotalCanView(totalCan: donationStatus.totalCan)
.padding(.top, 25)
HStack(spacing: 0) {
Text("전체")
.font(.custom(Font.medium.rawValue, size: 14.7))
.foregroundColor(Color(hex: "eeeeee"))
Text("\(donationStatus.totalCount)")
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "9970ff"))
.padding(.leading, 6.7)
Text("")
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "777777"))
Spacer()
}
.padding(.top, 13.3)
ScrollView(showsIndicators: false) {
LazyVGrid(columns: columns, spacing: 0) {
ForEach(0..<donationStatus.donationList.count, id: \.self) { index in
let item = donationStatus.donationList[index]
LiveRoomDonationRankingItemView(
index: index,
item: item,
itemCount: donationStatus.donationList.count
)
}
}
}
.padding(.top, 8)
}
}
.padding(20)
.background(Color(hex: "222222"))
.cornerRadius(8)
if viewModel.isLoading {
LoadingView()
}
}
.popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .bottom, autohideIn: 2) {
HStack {
Spacer()
Text(viewModel.errorMessage)
.padding(.vertical, 13.3)
.frame(width: screenSize().width - 66.7, alignment: .center)
.font(.custom(Font.medium.rawValue, size: 12))
.background(Color(hex: "9970ff"))
.foregroundColor(Color.white)
.multilineTextAlignment(.leading)
.cornerRadius(20)
.padding(.bottom, 66.7)
Spacer()
}
}
.onAppear {
viewModel.getDonationStatus()
}
}
}

View File

@@ -0,0 +1,114 @@
//
// LiveRoomDonationRankingItemView.swift
// SodaLive
//
// Created by klaus on 2023/08/15.
//
import SwiftUI
import Kingfisher
struct LiveRoomDonationRankingItemView: View {
let index: Int
let item: GetLiveRoomDonationItem
let itemCount: Int
let rankingCrawns = ["ic_crown_1", "ic_crown_2", "ic_crown_3"]
let rankingColors = [
[Color(hex: "ffdc00"), Color(hex: "ffb600")],
[Color(hex: "ffffff"), Color(hex: "9f9f9f")],
[Color(hex: "e6a77a"), Color(hex: "c67e4a")],
[Color(hex: "ffffff").opacity(0), Color(hex: "ffffff").opacity(0)]
]
var body: some View {
HStack(spacing: 0) {
ZStack {
KFImage(URL(string: item.profileImage))
.cancelOnDisappear(true)
.downsampling(size: CGSize(width: 60, height: 60))
.resizable()
.scaledToFill()
.frame(width: 60, height: 60, alignment: .top)
.clipShape(Circle())
.overlay(
Circle()
.stroke(
AngularGradient(colors: rankingColors[index < 4 ? index : 3], center: .center),
lineWidth: 3
)
)
if index < 3 {
VStack(alignment: .trailing, spacing: 0) {
Spacer()
Image(rankingCrawns[index])
.resizable()
.frame(width: 25, height: 25)
}
.frame(width: 63, height: 63, alignment: .trailing)
}
}
.frame(width: 63, height: 63)
Text("\(index + 1)")
.font(.custom(Font.bold.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee"))
.padding(.leading, 20)
.padding(.trailing, 13.3)
let nickname = item.nickname.count > 10 ? "\(String(item.nickname.prefix(10)))..." : item.nickname
Text(nickname)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee"))
Spacer()
if item.can > 0 {
Text("\(item.can) 코인")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee"))
}
}
.padding(.horizontal, isTop3Index(index: index) ? 20 : 0)
.padding(.top, getTopPadding(index: index))
.padding(.bottom, getBottomPadding(index: index))
.background(Color(hex: "2b2635").opacity(isTop3Index(index: index) ? 1 : 0))
.cornerRadius(4.7, corners: cornerRadiusConers(index: index))
.padding(.horizontal, isTop3Index(index: index) ? 0 : 6.7)
}
private func isTop3Index(index: Int) -> Bool {
return index == 0 || index == 1 || index == 2
}
private func getTopPadding(index: Int) -> CGFloat {
if index == 0 || index == 3 {
return 20
} else {
return 6.7
}
}
private func getBottomPadding(index: Int) -> CGFloat {
if (index == 0 && itemCount == 1) || (index == 1 && itemCount == 2) || index == 2 {
return 20
} else {
return 6.7
}
}
private func cornerRadiusConers(index: Int) -> UIRectCorner {
if (index == 0 && itemCount == 1) {
return [.topLeft, .topRight, .bottomLeft, .bottomRight]
} else if index == 0 {
return [.topLeft, .topRight]
} else if (index == 1 && itemCount == 2) || index == 2 {
return [.bottomLeft, .bottomRight]
} else {
return []
}
}
}

View File

@@ -0,0 +1,36 @@
//
// LiveRoomDonationRankingTotalCanView.swift
// SodaLive
//
// Created by klaus on 2023/08/15.
//
import SwiftUI
struct LiveRoomDonationRankingTotalCanView: View {
let totalCan: Int
var body: some View {
HStack(alignment: .center, spacing: 0) {
Text("합계")
.font(.custom(Font.bold.rawValue, size: 13.3))
.foregroundColor(Color(hex: "d2d2d2"))
Spacer()
Text("\(totalCan)")
.font(.custom(Font.medium.rawValue, size: 16))
.foregroundColor(Color(hex: "9970ff"))
Text("")
.font(.custom(Font.medium.rawValue, size: 10.7))
.foregroundColor(Color(hex: "bbbbbb"))
.padding(.leading, 4)
}
.padding(.horizontal, 18.7)
.padding(.vertical, 10.7)
.background(Color(hex: "2b2635"))
.cornerRadius(8)
}
}

View File

@@ -0,0 +1,238 @@
//
// LiveRoomInfoEditDialog.swift
// SodaLive
//
// Created by klaus on 2023/08/15.
//
import SwiftUI
import Kingfisher
struct LiveRoomInfoEditDialog: View {
@Binding var isShowing: Bool
@Binding var isShowPhotoPicker: Bool
@State private var title = ""
@State private var notice = ""
let placeholder = "라이브 공지를 입력하세요"
let viewModel: LiveRoomViewModel
let isLoading: Bool
let coverImageUrl: String?
let coverImage: UIImage?
var confirmAction: (String, String) -> Void
init(
isShowing: Binding<Bool>,
isShowPhotoPicker: Binding<Bool>,
viewModel: LiveRoomViewModel,
isLoading: Bool,
currentTitle: String,
currentNotice: String,
coverImageUrl: String,
coverImage: UIImage?,
confirmAction: @escaping (String, String) -> Void
) {
self._isShowing = isShowing
self._isShowPhotoPicker = isShowPhotoPicker
self.viewModel = viewModel
self.isLoading = isLoading
self.title = currentTitle
self.notice = currentNotice
self.coverImageUrl = coverImageUrl
self.coverImage = coverImage
self.confirmAction = confirmAction
UITextView.appearance().backgroundColor = .clear
}
var body: some View {
ZStack {
GeometryReader { proxy in
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 0) {
Text("라이브 수정")
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color(hex: "eeeeee"))
Spacer()
Image("ic_close_white")
.onTapGesture {
isShowing = false
}
}
HStack {
Spacer()
ZStack {
if let coverImage = coverImage {
Image(uiImage: coverImage)
.resizable()
.scaledToFill()
.frame(width: 80, height: 116.8, alignment: .top)
.clipped()
.cornerRadius(10)
} else if let coverImageUrl = coverImageUrl {
KFImage(URL(string: coverImageUrl))
.resizable()
.scaledToFill()
.frame(width: 80, height: 116.8, alignment: .top)
.clipped()
.cornerRadius(10)
} else {
Image("ic_logo_220")
.resizable()
.scaledToFit()
.frame(width: 80, height: 116.8)
.background(Color(hex: "3e3358"))
.cornerRadius(10)
}
Image("ic_camera")
.padding(10)
.background(Color(hex: "9970ff"))
.cornerRadius(30)
.offset(x: 40, y: 40)
}
.frame(alignment: .bottomTrailing)
.padding(.top, 13.3)
.onTapGesture {
isShowPhotoPicker = true
}
Spacer()
}
TitleInputView()
.padding(.top, 40)
ContentInputView()
.padding(.top, 33.3)
HStack(spacing: 13.3) {
Text("취소")
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color(hex: "9970ff"))
.padding(.vertical, 16)
.frame(width: (screenSize().width - 40) / 2)
.background(Color(hex: "9970ff").opacity(0.2))
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "9970ff"))
)
.onTapGesture {
isShowing = false
}
Text("수정하기")
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(.white)
.padding(.vertical, 16)
.frame(width: (screenSize().width - 40) / 2)
.background(Color(hex: "9970ff"))
.cornerRadius(10)
.onTapGesture {
confirmAction(
title,
notice.trimmingCharacters(in: .whitespacesAndNewlines) != placeholder ? notice : ""
)
isShowing = false
}
}
.padding(.top, 45)
Spacer()
}
.padding(13.3)
.frame(width: proxy.size.width, height: proxy.size.height)
.onTapGesture { hideKeyboard() }
}
.background(Color(hex: "222222").edgesIgnoringSafeArea(.all))
}
if viewModel.isShowPopup {
LiveRoomDialogView(
content: viewModel.errorMessage,
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
}
}
}
}
if isLoading {
LoadingView()
}
}
}
@ViewBuilder
func TitleInputView() -> some View {
VStack(alignment: .leading, spacing: 0) {
Text("제목")
.font(.custom(Font.bold.rawValue, size: 16.7))
.foregroundColor(Color(hex: "eeeeee"))
TextField("라이브 제목을 입력하세요", text: $title)
.autocapitalization(.none)
.disableAutocorrection(true)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "eeeeee"))
.accentColor(Color(hex: "9970ff"))
.keyboardType(.default)
.padding(.top, 12)
.padding(.horizontal, 6.7)
Rectangle()
.frame(height: 1)
.foregroundColor(Color(hex: "909090").opacity(0.7))
.padding(.top, 8.3)
}
}
@ViewBuilder
func ContentInputView() -> some View {
VStack(spacing: 13.3) {
HStack(spacing: 0) {
Text("공지")
.font(.custom(Font.bold.rawValue, size: 16.7))
.foregroundColor(Color(hex: "eeeeee"))
Spacer()
Text("\(notice.count)")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "ff5c49")) +
Text(" / 1000자")
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "777777"))
}
TextViewWrapper(
text: $notice,
placeholder: placeholder,
textColorHex: "eeeeee",
backgroundColorHex: "303030"
)
.frame(width: screenSize().width - 26.7, height: 133.3)
.cornerRadius(6.7)
.padding(.top, 13.3)
}
}
}

View File

@@ -0,0 +1,86 @@
//
// LiveRoomProfileDialog.swift
// SodaLive
//
// Created by klaus on 2023/08/15.
//
import SwiftUI
import Kingfisher
struct LiveRoomProfileDialog: View {
@Binding var isShowing: Bool
let profileInfo: LiveRoomMember
let creatorId: Int
let isSpeaker: Bool
let memberId = UserDefaults.int(forKey: .userId)
var onClickInviteSpeaker: ((Int) -> Void)? = nil
var onClickChangeListener: ((Int) -> Void)? = nil
var body: some View {
ZStack {
Color.black.opacity(0.7).ignoresSafeArea()
.onTapGesture {
isShowing = false
}
HStack(spacing: 13.3) {
KFImage(URL(string: profileInfo.profileImage))
.resizable()
.frame(width: 80, height: 116.7, alignment: .top)
.clipped()
.cornerRadius(13.3)
VStack(alignment: .leading, spacing: 0) {
Text(profileInfo.nickname)
.font(.custom(Font.bold.rawValue, size: 20))
.foregroundColor(Color(hex: "eeeeee"))
.padding(.top, 6.7)
Spacer()
if isSpeaker {
if profileInfo.role == .LISTENER, let onClickInviteSpeaker = onClickInviteSpeaker {
Text("스피커로 초대")
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: "9970ff"))
.padding(.horizontal, 15.4)
.padding(.vertical, 8.3)
.background(Color.white)
.cornerRadius(13.3)
.onTapGesture {
onClickInviteSpeaker(profileInfo.id)
isShowing = false
}
}
if (memberId == creatorId || memberId == profileInfo.id) && profileInfo.id != creatorId && profileInfo.role == .SPEAKER,
let onClickChangeListener = onClickChangeListener {
Text("리스너로 변경")
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: "9970ff"))
.padding(.horizontal, 15.4)
.padding(.vertical, 8.3)
.background(Color.white)
.cornerRadius(13.3)
.onTapGesture {
onClickChangeListener(profileInfo.id)
isShowing = false
}
}
}
}
.frame(height: 116.7)
Spacer()
}
.padding(20)
.frame(width: screenSize().width - 53.4)
.background(Color(hex: "9970ff"))
.cornerRadius(16.7)
}
}
}

View File

@@ -0,0 +1,189 @@
//
// LiveRoomProfileItemTitleView.swift
// SodaLive
//
// Created by klaus on 2023/08/14.
//
import SwiftUI
import Kingfisher
struct LiveRoomProfileItemTitleView: View {
let title: String
let count: Int?
let totalCount: Int?
var body: some View {
HStack(spacing: 0) {
Text(title)
.font(.custom(Font.bold.rawValue, size: 13))
.foregroundColor(Color(hex: "eeeeee"))
if let count = count {
Text("\(count)")
.font(.custom(Font.medium.rawValue, size: 13))
.foregroundColor(Color(hex: "9970ff"))
.padding(.leading, 6.7)
}
if let totalCount = totalCount {
Text("/\(totalCount > 9 ? 9 : totalCount - 1)")
.font(.custom(Font.medium.rawValue, size: 13))
.foregroundColor(Color(hex: "bbbbbb"))
}
Spacer()
}
}
}
struct LiveRoomProfileItemMasterView: View {
let id: Int
let nickname: String
let profileUrl: String
let onClickProfile: (Int) -> Void
var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack(spacing: 0) {
KFImage(URL(string: profileUrl))
.resizable()
.frame(width: 60, height: 60)
.clipShape(Circle())
.onTapGesture { onClickProfile(id) }
Image("ic_crown")
.padding(.leading, 16.7)
Text(nickname)
.font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "eeeeee"))
.padding(.leading, 4)
}
.padding(.horizontal, 16.7)
Rectangle()
.frame(height: 1)
.foregroundColor(Color(hex: "909090").opacity(0.3))
}
}
}
struct LiveRoomProfileItemUserView: View {
let isStaff: Bool
let userId: Int
let nickname: String
let profileUrl: String
let role: LiveRoomMemberRole
let onClickChangeListener: (Int) -> Void
let onClickInviteSpeaker: (Int) -> Void
let onClickKickOut: (Int) -> Void
let onClickProfile: (Int) -> Void
var body: some View {
ZStack {
VStack(spacing: 10) {
HStack(spacing: 0) {
KFImage(URL(string: profileUrl))
.resizable()
.frame(width: 46.7, height: 46.7)
.clipShape(Circle())
.onTapGesture { onClickProfile(userId) }
if role == .MANAGER {
Image("ic_badge_manager")
.padding(.leading, 16.7)
Text(nickname)
.font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "eeeeee"))
.lineLimit(2)
.multilineTextAlignment(.leading)
.padding(.leading, 4)
.padding(.trailing, 10)
} else {
Text(nickname)
.font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "eeeeee"))
.lineLimit(2)
.multilineTextAlignment(.leading)
.padding(.horizontal, 10)
}
Spacer()
if role == .LISTENER && isStaff {
Text("스피커로 초대")
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: "ffffff"))
.padding(.horizontal, 5.5)
.padding(.vertical, 12)
.background(Color(hex: "9970ff").opacity(0.3))
.cornerRadius(6.7)
.overlay(
RoundedRectangle(cornerRadius: 6.7)
.stroke(Color(hex: "9970ff"), lineWidth: 1)
)
.onTapGesture {
onClickInviteSpeaker(userId)
}
}
if role == .SPEAKER && (userId == UserDefaults.int(forKey: .userId) || isStaff) {
Text("리스너로 변경")
.font(.custom(Font.medium.rawValue, size: 10))
.foregroundColor(Color(hex: "ffffff"))
.padding(.horizontal, 5.5)
.padding(.vertical, 12)
.background(Color(hex: "9970ff"))
.cornerRadius(6.7)
.onTapGesture {
onClickChangeListener(userId)
}
}
if role != .MANAGER && isStaff {
Image("ic_kick_out")
.padding(.leading, 10)
.onTapGesture {
onClickKickOut(userId)
}
}
}
Rectangle()
.frame(height: 1)
.foregroundColor(Color(hex: "909090").opacity(0.3))
}
.padding(.horizontal, 16.7)
}
}
}
struct LiveRoomProfileRequestSpeakerView: View {
let onClickRequestSpeaker: () -> Void
var body: some View {
HStack(spacing: 6.7) {
Spacer()
Image("ic_request_speak")
Text("스피커 요청하기")
.font(.custom(Font.bold.rawValue, size: 13.3))
.foregroundColor(.white)
Spacer()
}
.padding(.vertical, 8)
.overlay(
RoundedRectangle(cornerRadius: 5.3)
.stroke(Color(hex: "909090"), lineWidth: 1)
)
.onTapGesture {
onClickRequestSpeaker()
}
.padding(.horizontal, 16.7)
}
}

View File

@@ -0,0 +1,249 @@
//
// LiveRoomProfilesDialogView.swift
// SodaLive
//
// Created by klaus on 2023/08/14.
//
import SwiftUI
import Kingfisher
struct LiveRoomProfilesDialogView: View {
@Binding var isShowing: Bool
let viewModel: LiveRoomViewModel
let roomInfo: GetRoomInfoResponse
var profiles: [AnyView] = []
let accountId = UserDefaults.int(forKey: .userId)
init(
isShowing: Binding<Bool>,
viewModel: LiveRoomViewModel,
roomInfo: GetRoomInfoResponse,
isShowRequestSpeaker: Bool,
onClickRequestSpeaker: @escaping () -> Void,
registerNotification: @escaping () -> Void,
unRegisterNotification: @escaping () -> Void,
onClickProfile: @escaping (Int) -> Void
) {
self._isShowing = isShowing
self.viewModel = viewModel
self.roomInfo = roomInfo
self.profiles.append(
AnyView(
LiveRoomProfileItemTitleView(
title: "스탭",
count: roomInfo.managerList.count,
totalCount: nil
)
.padding(.vertical, 14)
)
)
let isStaff = viewModel.isEqualToStaffId(creatorId: UserDefaults.int(forKey: .userId)) ||
roomInfo.creatorId == UserDefaults.int(forKey: .userId)
for manager in roomInfo.managerList {
self.profiles.append(
AnyView(
LiveRoomProfileItemUserView(
isStaff: isStaff ,
userId: manager.id,
nickname: manager.nickname,
profileUrl: manager.profileImage,
role: manager.role,
onClickChangeListener: { _ in },
onClickInviteSpeaker: { _ in },
onClickKickOut: { _ in },
onClickProfile: onClickProfile
)
)
)
}
self.profiles.append(
AnyView(
LiveRoomProfileItemTitleView(
title: "스피커",
count: roomInfo.speakerList.count - 1,
totalCount: roomInfo.totalAvailableParticipantsCount
)
.padding(.vertical, 14)
)
)
for speaker in roomInfo.speakerList {
if speaker.id == roomInfo.creatorId {
self.profiles.insert(
AnyView(
LiveRoomProfileItemMasterView(
id: speaker.id,
nickname: speaker.nickname,
profileUrl: speaker.profileImage,
onClickProfile: onClickProfile
)
),
at: 0
)
} else {
self.profiles.append(
AnyView(
LiveRoomProfileItemUserView(
isStaff: isStaff,
userId: speaker.id,
nickname: speaker.nickname,
profileUrl: speaker.profileImage,
role: speaker.role,
onClickChangeListener: {
if $0 == UserDefaults.int(forKey: .userId) {
viewModel.setListener()
return
}
viewModel.changeListener(peerId: $0)
},
onClickInviteSpeaker: { _ in },
onClickKickOut: {
viewModel.kickOutId = $0
viewModel.isShowKickOutPopup = true
},
onClickProfile: onClickProfile
)
)
)
}
}
if isShowRequestSpeaker {
self.profiles.append(
AnyView(
LiveRoomProfileRequestSpeakerView {
onClickRequestSpeaker()
}
)
)
}
self.profiles.append(
AnyView(
LiveRoomProfileItemTitleView(
title: "리스너",
count: nil,
totalCount: nil
)
.padding(.top, 20)
.padding(.bottom, 14)
)
)
for listener in roomInfo.listenerList {
self.profiles.append(
AnyView(
LiveRoomProfileItemUserView(
isStaff: isStaff,
userId: listener.id,
nickname: listener.nickname,
profileUrl: listener.profileImage,
role: listener.role,
onClickChangeListener: { _ in },
onClickInviteSpeaker: {
if viewModel.liveRoomInfo!.speakerList.count <= 9 {
viewModel.inviteSpeaker(peerId: $0)
viewModel.popupContent = "스피커 요청을 보냈습니다.\n잠시만 기다려 주세요."
viewModel.isShowPopup = true
} else {
viewModel.errorMessage = "스피커 정원을 초과했습니다."
viewModel.isShowErrorPopup = true
}
},
onClickKickOut: {
viewModel.kickOutId = $0
viewModel.isShowKickOutPopup = true
},
onClickProfile: onClickProfile
)
)
)
}
}
var body: some View {
ZStack {
VStack(spacing: 16.7) {
HStack(spacing: 0) {
Text("참여자")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "eeeeee"))
Text("\(roomInfo.participantsCount)")
.font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "9970ff"))
.padding(.leading, 6.7)
Text("/\(roomInfo.totalAvailableParticipantsCount)")
.font(.custom(Font.medium.rawValue, size: 14))
.foregroundColor(Color(hex: "bbbbbb"))
Spacer()
Image("ic_close_white")
.resizable()
.frame(width: 20, height: 20)
.onTapGesture { isShowing = false }
}
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 20) {
ForEach(0..<profiles.count, id: \.self) { index in
profiles[index]
}
}
}
}
.padding(.vertical, 26.7)
.padding(.horizontal, 13.3)
.background(Color(hex: "222222").edgesIgnoringSafeArea(.all))
.cornerRadius(16.7)
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.isShowKickOutPopup {
SodaDialog(
title: "내보내기",
desc: viewModel.kickOutDesc,
confirmButtonTitle: "내보내기",
confirmButtonAction: {
viewModel.kickOut()
},
cancelButtonTitle: "취소",
cancelButtonAction: {
viewModel.isShowKickOutPopup = false
viewModel.kickOutDesc = ""
viewModel.kickOutId = 0
}
)
}
}
}
}

View File

@@ -0,0 +1,259 @@
//
// LiveRoomUserProfileDialogView.swift
// SodaLive
//
// Created by klaus on 2023/08/15.
//
import SwiftUI
import Kingfisher
struct LiveRoomUserProfileDialogView: View {
@Binding var isShowing: Bool
@State private var introduceLineLimit: Int? = 2
let viewModel: LiveRoomViewModel
let userProfile: GetLiveRoomUserProfileResponse
let onClickSetManager: (Int) -> Void
let onClickReleaseManager: (Int) -> Void
let onClickFollow: (Int) -> Void
let onClickUnFollow: (Int) -> Void
let onClickInviteSpeaker: (Int) -> Void
let onClickChangeListener: (Int) -> Void
let onClickMenu: (Int, String, Bool) -> Void
var body: some View {
ZStack {
VStack(spacing: 0) {
HStack(spacing: 0) {
Text("프로필")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "eeeeee"))
Spacer()
Image("ic_close_white")
.onTapGesture {
isShowing = false
}
}
.padding(.top, 13.3)
ScrollView(.vertical, showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) {
HStack(spacing: 8) {
Text(userProfile.nickname)
.font(.custom(Font.bold.rawValue, size: 18.3))
.foregroundColor(Color(hex: "eeeeee"))
Text(userProfile.gender)
.font(.custom(Font.medium.rawValue, size: 11.3))
.foregroundColor(Color(hex: "ffffff"))
.padding(.horizontal, 5.3)
.padding(.vertical, 3)
.background(Color(hex: "555555"))
.cornerRadius(23.3)
Spacer()
Image("ic_seemore_vertical")
.onTapGesture {
onClickMenu(
userProfile.userId,
userProfile.nickname,
userProfile.isBlock
)
}
}
.padding(.top, 21.3)
HStack(spacing: 8) {
if let isFollwing = userProfile.isFollowing {
if isFollwing {
HStack(spacing: 4) {
Image("ic_alarm_selected")
.resizable()
.frame(width: 18.7, height: 18.7)
Text("팔로잉")
.font(.custom(Font.bold.rawValue, size: 12))
.foregroundColor(Color(hex: "ffffff"))
}
.padding(.vertical, 7.3)
.frame(maxWidth: .infinity)
.background(Color(hex: "3e1b93"))
.cornerRadius(16.7)
.overlay(
RoundedRectangle(cornerRadius: 16.7)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "9970ff"))
)
.onTapGesture { onClickUnFollow(userProfile.userId) }
} else {
HStack(spacing: 4) {
Image("ic_alarm")
.resizable()
.frame(width: 18.7, height: 18.7)
Text("팔로우")
.font(.custom(Font.bold.rawValue, size: 12))
.foregroundColor(Color(hex: "ffffff"))
}
.padding(.vertical, 7.3)
.frame(maxWidth: .infinity)
.cornerRadius(16.7)
.overlay(
RoundedRectangle(cornerRadius: 16.7)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "9970ff"))
)
.onTapGesture { onClickFollow(userProfile.userId) }
}
}
HStack(spacing: 4) {
Image("ic_message_send")
.resizable()
.frame(width: 18.7, height: 18.7)
Text("메시지 보내기")
.font(.custom(Font.bold.rawValue, size: 12))
.foregroundColor(Color(hex: "ffffff"))
}
.padding(.vertical, 7.3)
.frame(maxWidth: .infinity)
.background(Color(hex: "9970ff"))
.cornerRadius(16.7)
.onTapGesture {
AppState.shared.setAppStep(step: .writeTextMessage(
userId: userProfile.userId,
nickname: userProfile.nickname))
}
}
.fixedSize(horizontal: false, vertical: true)
.padding(.top, 21.3)
KFImage(URL(string: userProfile.profileUrl))
.resizable()
.aspectRatio(CGSize(width: 1, height: 1), contentMode: .fill)
.cornerRadius(8)
.padding(.top, 21.3)
HStack(spacing: 8) {
if let isSpeaker = userProfile.isSpeaker {
Text(isSpeaker ? "리스너 변경" : "스피커 초대")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "9970ff"))
.frame(maxWidth: .infinity)
.padding(.vertical, 13)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "9970ff"))
)
.onTapGesture {
if isSpeaker {
onClickChangeListener(userProfile.userId)
} else {
onClickInviteSpeaker(userProfile.userId)
}
isShowing = false
}
}
if let isManager = userProfile.isManager {
Text(isManager ? "스탭 해제" : "스탭 지정")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "9970ff"))
.frame(maxWidth: .infinity)
.padding(.vertical, 13)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "9970ff"))
)
.onTapGesture {
if isManager {
onClickReleaseManager(userProfile.userId)
} else {
onClickSetManager(userProfile.userId)
}
isShowing = false
}
}
if (userProfile.isSpeaker != nil && !viewModel.isEqualToStaffId(creatorId: userProfile.userId)) ||
(userProfile.isSpeaker != nil && userProfile.isManager != nil) {
Text("내보내기")
.font(.custom(Font.bold.rawValue, size: 15))
.foregroundColor(Color(hex: "9970ff"))
.frame(maxWidth: .infinity)
.padding(.vertical, 13)
.cornerRadius(8)
.overlay(
RoundedRectangle(cornerRadius: 8)
.strokeBorder(lineWidth: 1)
.foregroundColor(Color(hex: "9970ff"))
)
.onTapGesture {
viewModel.kickOutId = userProfile.userId
viewModel.isShowKickOutPopup = true
}
}
}
.fixedSize(horizontal: false, vertical: true)
.padding(.top, 21.3)
Text(userProfile.tags)
.font(.custom(Font.medium.rawValue, size: 13.3))
.foregroundColor(Color(hex: "9970ff"))
.lineSpacing(3)
.padding(.top, 21.3)
Text(userProfile.introduce)
.font(.custom(Font.medium.rawValue, size: 12))
.foregroundColor(Color(hex: "909090"))
.lineLimit(introduceLineLimit)
.lineSpacing(3)
.fixedSize(horizontal: false, vertical: true)
.padding(.vertical, 20)
.onTapGesture {
if let _ = introduceLineLimit {
self.introduceLineLimit = nil
} else {
self.introduceLineLimit = 2
}
}
}
}
}
.padding(.horizontal, 13.3)
.background(Color(hex: "222222").edgesIgnoringSafeArea(.all))
.cornerRadius(8)
if viewModel.isShowKickOutPopup {
SodaDialog(
title: "내보내기",
desc: viewModel.kickOutDesc,
confirmButtonTitle: "내보내기",
confirmButtonAction: {
viewModel.kickOut()
isShowing = false
},
cancelButtonTitle: "취소",
cancelButtonAction: {
viewModel.isShowKickOutPopup = false
viewModel.kickOutDesc = ""
viewModel.kickOutId = 0
}
)
}
}
}
}