feat(chat): 크리에이터 DM 시작을 추가한다
This commit is contained in:
@@ -138,6 +138,19 @@ class AppState: ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func replaceCurrentAppStep(step: AppStep) {
|
||||
DispatchQueue.main.async {
|
||||
if let route = self.navigationPath.popLast() {
|
||||
self.routeStepMap.removeValue(forKey: route)
|
||||
}
|
||||
|
||||
let route = AppRoute()
|
||||
self.routeStepMap[route] = step
|
||||
self.navigationPath.append(route)
|
||||
self.appStep = step
|
||||
}
|
||||
}
|
||||
|
||||
func back() {
|
||||
DispatchQueue.main.async {
|
||||
@@ -244,6 +257,10 @@ class AppState: ObservableObject {
|
||||
pendingMainChatFilter = filter
|
||||
}
|
||||
|
||||
func requestMainChatRefresh(filter: MainChatFilter) {
|
||||
pendingMainChatFilter = filter
|
||||
}
|
||||
|
||||
func consumePendingMainChatFilter() -> MainChatFilter? {
|
||||
let filter = pendingMainChatFilter
|
||||
pendingMainChatFilter = nil
|
||||
|
||||
@@ -185,6 +185,8 @@ enum AppStep {
|
||||
|
||||
case userCreatorChatCreator(creatorId: Int)
|
||||
|
||||
case userCreatorChatRecipientSearch
|
||||
|
||||
case newCharacterAll
|
||||
|
||||
case originalWorkDetail(originalId: Int)
|
||||
|
||||
@@ -354,6 +354,9 @@ struct AppStepLayerView: View {
|
||||
case .userCreatorChatCreator(let creatorId):
|
||||
UserCreatorChatRoomView(creatorId: creatorId)
|
||||
|
||||
case .userCreatorChatRecipientSearch:
|
||||
UserCreatorChatRecipientSearchView()
|
||||
|
||||
case .newCharacterAll:
|
||||
NewCharacterListView()
|
||||
|
||||
|
||||
@@ -33,6 +33,48 @@ enum I18n {
|
||||
}
|
||||
}
|
||||
|
||||
enum UserCreatorChatRecipientSearch {
|
||||
static var searchPlaceholder: String {
|
||||
pick(ko: "팬 이름을 입력하세요", en: "Enter a fan name", ja: "ファンの名前を入力してください")
|
||||
}
|
||||
|
||||
static var sectionTitle: String {
|
||||
pick(ko: "팔로워", en: "Followers", ja: "フォロワー")
|
||||
}
|
||||
|
||||
static var searchResultLabel: String {
|
||||
pick(ko: "검색결과", en: "Search results", ja: "検索結果")
|
||||
}
|
||||
|
||||
static var emptyMessage: String {
|
||||
pick(
|
||||
ko: "사용자가 없어요.\n다른 이름으로 다시 검색해 주세요.",
|
||||
en: "No users found.\nTry searching with another name.",
|
||||
ja: "ユーザーがいません。\n別の名前で検索してください。"
|
||||
)
|
||||
}
|
||||
|
||||
static var dialogTitle: String {
|
||||
pick(ko: "메시지 보내기", en: "Send message", ja: "メッセージを送る")
|
||||
}
|
||||
|
||||
static func dialogMessage(_ nickname: String) -> String {
|
||||
pick(
|
||||
ko: "\(nickname)에게 메시지를 보낼까요?",
|
||||
en: "Send a message to \(nickname)?",
|
||||
ja: "\(nickname)にメッセージを送りますか?"
|
||||
)
|
||||
}
|
||||
|
||||
static var sendButton: String {
|
||||
pick(ko: "보내기", en: "Send", ja: "送信")
|
||||
}
|
||||
|
||||
static var openButtonAccessibilityLabel: String {
|
||||
pick(ko: "메시지 보낼 사용자 선택", en: "Select user to message", ja: "メッセージを送るユーザーを選択")
|
||||
}
|
||||
}
|
||||
|
||||
// 채팅방(캐릭터 톡) 관련 문자열
|
||||
enum ChatRoom {
|
||||
// 잠금된 메시지 다이얼로그
|
||||
|
||||
@@ -124,7 +124,7 @@ struct MainChatView: View {
|
||||
|
||||
private func applyPendingFilterIfNeeded() -> Bool {
|
||||
guard let filter = appState.consumePendingMainChatFilter() else { return false }
|
||||
viewModel.applyFilter(filter)
|
||||
viewModel.fetchFirstPage(filter: filter)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,24 @@
|
||||
import Foundation
|
||||
|
||||
struct UserCreatorCreateRoomRequest: Encodable {
|
||||
let creatorId: Int
|
||||
let recipientId: Int?
|
||||
let creatorId: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case recipientId
|
||||
case creatorId
|
||||
}
|
||||
|
||||
init(recipientId: Int? = nil, creatorId: Int? = nil) {
|
||||
self.recipientId = recipientId
|
||||
self.creatorId = creatorId
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encodeIfPresent(recipientId, forKey: .recipientId)
|
||||
try container.encodeIfPresent(creatorId, forKey: .creatorId)
|
||||
}
|
||||
}
|
||||
|
||||
struct UserCreatorCreateRoomResponse: Decodable {
|
||||
|
||||
+4
@@ -10,6 +10,10 @@ final class UserCreatorChatRepository {
|
||||
return api.requestPublisher(.createRoom(request: UserCreatorCreateRoomRequest(creatorId: creatorId)))
|
||||
}
|
||||
|
||||
func createRoom(recipientId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.createRoom(request: UserCreatorCreateRoomRequest(recipientId: recipientId)))
|
||||
}
|
||||
|
||||
func openRoom(roomId: Int, limit: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.openRoom(roomId: roomId, limit: limit))
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
struct UserCreatorChatRoomView: View {
|
||||
@StateObject private var viewModel = UserCreatorChatRoomViewModel()
|
||||
@@ -200,3 +201,388 @@ struct UserCreatorChatRoomView_Previews: PreviewProvider {
|
||||
UserCreatorChatRoomView(roomId: 1)
|
||||
}
|
||||
}
|
||||
|
||||
struct UserCreatorChatRecipientItem: Identifiable, Equatable {
|
||||
let id: Int
|
||||
let nickname: String
|
||||
let profileImageUrl: String
|
||||
|
||||
init(follower item: GetFollowerListResponseItem) {
|
||||
self.id = item.userId
|
||||
self.nickname = item.nickname
|
||||
self.profileImageUrl = item.profileImage
|
||||
}
|
||||
|
||||
init(search item: GetRoomDetailUser) {
|
||||
self.id = item.id
|
||||
self.nickname = item.nickname
|
||||
self.profileImageUrl = item.profileImageUrl
|
||||
}
|
||||
}
|
||||
|
||||
final class UserCreatorChatRecipientSearchViewModel: ObservableObject {
|
||||
@Published var isLoading = false
|
||||
@Published var errorMessage = ""
|
||||
@Published var isShowPopup = false
|
||||
@Published var searchText = ""
|
||||
@Published private(set) var items = [UserCreatorChatRecipientItem]()
|
||||
@Published private(set) var isSearchMode = false
|
||||
@Published private(set) var isCreatingRoom = false
|
||||
|
||||
private let followRepository = ExplorerRepository()
|
||||
private let userRepository = UserRepository()
|
||||
private let chatRepository = UserCreatorChatRepository()
|
||||
private var subscription = Set<AnyCancellable>()
|
||||
private var searchSubscription: AnyCancellable?
|
||||
private var followingItems = [UserCreatorChatRecipientItem]()
|
||||
private var page = 1
|
||||
private var isLast = false
|
||||
private let pageSize = 10
|
||||
private var isFetchingFollowing = false
|
||||
private var isSearching = false
|
||||
private var didCompleteSearch = false
|
||||
private var activeSearchQuery: String?
|
||||
|
||||
var shouldShowEmptySearchResult: Bool {
|
||||
isSearchMode && didCompleteSearch && !isSearching && items.isEmpty
|
||||
}
|
||||
|
||||
init() {
|
||||
$searchText
|
||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
||||
.removeDuplicates()
|
||||
.debounce(for: .milliseconds(500), scheduler: RunLoop.main)
|
||||
.sink { [weak self] query in
|
||||
self?.handleSearchQuery(query)
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
func fetchFirstPageIfNeeded() {
|
||||
guard followingItems.isEmpty else { return }
|
||||
fetchFollowing()
|
||||
}
|
||||
|
||||
func fetchNextPageIfNeeded(currentItem item: UserCreatorChatRecipientItem) {
|
||||
guard !isSearchMode, item.id == followingItems.last?.id else { return }
|
||||
fetchFollowing()
|
||||
}
|
||||
|
||||
func createRoom(recipientId: Int) {
|
||||
guard recipientId > 0, !isCreatingRoom else { return }
|
||||
isCreatingRoom = true
|
||||
updateLoading()
|
||||
|
||||
chatRepository.createRoom(recipientId: recipientId)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
guard let self else { return }
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
self.applyCreateRoomFailure(message: nil)
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self else { return }
|
||||
do {
|
||||
let decoded = try JSONDecoder().decode(ApiResponse<UserCreatorCreateRoomResponse>.self, from: response.data)
|
||||
if let data = decoded.data, decoded.success, data.roomId > 0 {
|
||||
self.isCreatingRoom = false
|
||||
self.updateLoading()
|
||||
AppState.shared.requestMainChatRefresh(filter: .dm)
|
||||
AppState.shared.replaceCurrentAppStep(step: .userCreatorChatRoom(roomId: data.roomId))
|
||||
} else {
|
||||
self.applyCreateRoomFailure(message: decoded.message)
|
||||
}
|
||||
} catch {
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
self.applyCreateRoomFailure(message: nil)
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
private func handleSearchQuery(_ query: String) {
|
||||
guard query.count >= 2 else {
|
||||
searchSubscription?.cancel()
|
||||
activeSearchQuery = nil
|
||||
isSearching = false
|
||||
didCompleteSearch = false
|
||||
isSearchMode = false
|
||||
items = followingItems
|
||||
updateLoading()
|
||||
return
|
||||
}
|
||||
|
||||
search(query: query)
|
||||
}
|
||||
|
||||
private func fetchFollowing() {
|
||||
guard !isLast, !isFetchingFollowing else { return }
|
||||
isFetchingFollowing = true
|
||||
updateLoading()
|
||||
|
||||
followRepository.getFollowerList(userId: UserDefaults.int(forKey: .userId), page: page, size: pageSize)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
guard let self else { return }
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
self.isFetchingFollowing = false
|
||||
self.applyFailure(message: nil)
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self else { return }
|
||||
self.isFetchingFollowing = false
|
||||
self.updateLoading()
|
||||
do {
|
||||
let decoded = try JSONDecoder().decode(ApiResponse<GetFollowerListResponse>.self, from: response.data)
|
||||
if let data = decoded.data, decoded.success {
|
||||
let mappedItems = data.items.map(UserCreatorChatRecipientItem.init(follower:))
|
||||
if self.page == 1 {
|
||||
self.followingItems.removeAll()
|
||||
}
|
||||
self.followingItems.append(contentsOf: mappedItems)
|
||||
self.items = self.isSearchMode ? self.items : self.followingItems
|
||||
if data.items.isEmpty {
|
||||
self.isLast = true
|
||||
} else {
|
||||
self.page += 1
|
||||
}
|
||||
} else {
|
||||
self.applyFailure(message: decoded.message)
|
||||
}
|
||||
} catch {
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
self.applyFailure(message: nil)
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
private func search(query: String) {
|
||||
isSearchMode = true
|
||||
isSearching = true
|
||||
didCompleteSearch = false
|
||||
items.removeAll()
|
||||
updateLoading()
|
||||
searchSubscription?.cancel()
|
||||
activeSearchQuery = query
|
||||
|
||||
searchSubscription = userRepository.searchUser(nickname: query)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] result in
|
||||
guard let self else { return }
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
guard self.activeSearchQuery == query else { return }
|
||||
self.isSearching = false
|
||||
self.applyFailure(message: nil)
|
||||
}
|
||||
} receiveValue: { [weak self] response in
|
||||
guard let self else { return }
|
||||
guard self.activeSearchQuery == query else { return }
|
||||
self.isSearching = false
|
||||
self.updateLoading()
|
||||
do {
|
||||
let decoded = try JSONDecoder().decode(ApiResponse<[GetRoomDetailUser]>.self, from: response.data)
|
||||
if let data = decoded.data, decoded.success {
|
||||
self.items = data.map(UserCreatorChatRecipientItem.init(search:))
|
||||
self.didCompleteSearch = true
|
||||
} else {
|
||||
self.applyFailure(message: decoded.message)
|
||||
}
|
||||
} catch {
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
self.applyFailure(message: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func applyFailure(message: String?) {
|
||||
updateLoading()
|
||||
errorMessage = message ?? I18n.Common.commonError
|
||||
isShowPopup = true
|
||||
}
|
||||
|
||||
private func applyCreateRoomFailure(message: String?) {
|
||||
isCreatingRoom = false
|
||||
applyFailure(message: message)
|
||||
}
|
||||
|
||||
private func updateLoading() {
|
||||
isLoading = isFetchingFollowing || isSearching || isCreatingRoom
|
||||
}
|
||||
}
|
||||
|
||||
struct UserCreatorChatRecipientSearchView: View {
|
||||
@StateObject private var viewModel = UserCreatorChatRecipientSearchViewModel()
|
||||
@State private var selectedRecipient: UserCreatorChatRecipientItem?
|
||||
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
ZStack {
|
||||
VStack(spacing: 0) {
|
||||
searchBar
|
||||
.padding(.top, SodaSpacing.s8)
|
||||
|
||||
listContent
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.contentShape(Rectangle())
|
||||
.simultaneousGesture(TapGesture().onEnded { hideKeyboard() })
|
||||
}
|
||||
.background(Color.black.ignoresSafeArea())
|
||||
.onAppear { viewModel.fetchFirstPageIfNeeded() }
|
||||
.sodaToast(
|
||||
isPresented: $viewModel.isShowPopup,
|
||||
message: viewModel.errorMessage,
|
||||
autohideIn: 2
|
||||
)
|
||||
|
||||
if let selectedRecipient {
|
||||
confirmationDialog(for: selectedRecipient)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var searchBar: some View {
|
||||
HStack(spacing: SodaSpacing.s8) {
|
||||
Image("ic_back")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.onTapGesture { AppState.shared.back() }
|
||||
|
||||
HStack(spacing: SodaSpacing.s8) {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.foregroundColor(Color.gray500)
|
||||
|
||||
TextField(I18n.UserCreatorChatRecipientSearch.searchPlaceholder, text: $viewModel.searchText)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.accentColor(Color.soda400)
|
||||
.textInputAutocapitalization(.never)
|
||||
.disableAutocorrection(true)
|
||||
}
|
||||
.padding(.horizontal, SodaSpacing.s12)
|
||||
.frame(height: 42)
|
||||
.background(Color.gray900)
|
||||
.cornerRadius(21)
|
||||
}
|
||||
.padding(.horizontal, SodaSpacing.s14)
|
||||
.frame(width: screenSize().width, height: 54)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var listContent: some View {
|
||||
if viewModel.shouldShowEmptySearchResult {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
sectionHeader
|
||||
emptySearchContent
|
||||
}
|
||||
.padding(.top, SodaSpacing.s8)
|
||||
} else {
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
LazyVStack(alignment: .leading, spacing: 0) {
|
||||
sectionHeader
|
||||
|
||||
ForEach(viewModel.items) { item in
|
||||
UserCreatorChatRecipientRow(item: item) {
|
||||
hideKeyboard()
|
||||
selectedRecipient = item
|
||||
}
|
||||
.onAppear {
|
||||
viewModel.fetchNextPageIfNeeded(currentItem: item)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, SodaSpacing.s8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var sectionHeader: some View {
|
||||
if viewModel.isSearchMode {
|
||||
HStack(spacing: SodaSpacing.s4) {
|
||||
Text(I18n.UserCreatorChatRecipientSearch.searchResultLabel)
|
||||
.foregroundColor(.white)
|
||||
Text("\(viewModel.items.count)")
|
||||
.foregroundColor(Color.gray500)
|
||||
}
|
||||
.appFont(size: 14, weight: .bold)
|
||||
.padding(.horizontal, SodaSpacing.s14)
|
||||
.frame(maxWidth: .infinity, minHeight: 40, alignment: .leading)
|
||||
} else {
|
||||
Text(I18n.UserCreatorChatRecipientSearch.sectionTitle)
|
||||
.appFont(size: 14, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, SodaSpacing.s14)
|
||||
.frame(height: 40, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
private var emptySearchContent: some View {
|
||||
Text(I18n.UserCreatorChatRecipientSearch.emptyMessage)
|
||||
.appFont(size: 16, weight: .medium)
|
||||
.foregroundColor(Color.gray500)
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
|
||||
private func confirmationDialog(for item: UserCreatorChatRecipientItem) -> some View {
|
||||
SodaV2ActionModal(
|
||||
title: I18n.UserCreatorChatRecipientSearch.dialogTitle,
|
||||
message: I18n.UserCreatorChatRecipientSearch.dialogMessage(item.nickname),
|
||||
button1: SodaV2ActionModalButton(
|
||||
label: I18n.UserCreatorChatRecipientSearch.sendButton,
|
||||
action: {
|
||||
selectedRecipient = nil
|
||||
viewModel.createRoom(recipientId: item.id)
|
||||
}
|
||||
),
|
||||
button2: SodaV2ActionModalButton(
|
||||
label: I18n.Common.cancel,
|
||||
action: { selectedRecipient = nil }
|
||||
),
|
||||
onDimmedTap: { selectedRecipient = nil }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private struct UserCreatorChatRecipientRow: View {
|
||||
let item: UserCreatorChatRecipientItem
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: SodaSpacing.s12) {
|
||||
DownsampledKFImage(
|
||||
url: URL(string: item.profileImageUrl),
|
||||
size: CGSize(width: 42, height: 42)
|
||||
)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(item.nickname)
|
||||
.appFont(size: 14, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.padding(.horizontal, SodaSpacing.s14)
|
||||
.frame(height: 66)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,26 @@ struct MainView: View {
|
||||
)
|
||||
}
|
||||
|
||||
if shouldShowChatRecipientSearchButton {
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
CreatorChannelFloatingIconButton(
|
||||
imageName: "ic_plus_no_bg",
|
||||
backgroundColor: Color.soda400,
|
||||
accessibilityLabel: I18n.UserCreatorChatRecipientSearch.openButtonAccessibilityLabel,
|
||||
action: showUserCreatorChatRecipientSearch
|
||||
)
|
||||
.padding(.trailing, SodaSpacing.s14)
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea(.container, edges: .bottom)
|
||||
.padding(.bottom, chatRecipientSearchButtonBottomPadding)
|
||||
}
|
||||
|
||||
if isShowPlayer {
|
||||
ContentPlayerView(isShowing: $isShowPlayer, playlist: [])
|
||||
}
|
||||
@@ -161,6 +181,10 @@ struct MainView: View {
|
||||
UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue && viewModel.currentTab == .home
|
||||
}
|
||||
|
||||
private var shouldShowChatRecipientSearchButton: Bool {
|
||||
UserDefaults.string(forKey: .role) == MemberRole.CREATOR.rawValue && viewModel.currentTab == .chat
|
||||
}
|
||||
|
||||
private var creatorActionMenuBottomPadding: CGFloat {
|
||||
var bottomPadding = mainTabBarHeight + SodaSpacing.s14
|
||||
if contentPlayerPlayManager.isShowingMiniPlayer {
|
||||
@@ -172,6 +196,10 @@ struct MainView: View {
|
||||
return bottomPadding
|
||||
}
|
||||
|
||||
private var chatRecipientSearchButtonBottomPadding: CGFloat {
|
||||
creatorActionMenuBottomPadding
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var contentView: some View {
|
||||
switch viewModel.currentTab {
|
||||
@@ -686,6 +714,10 @@ struct MainView: View {
|
||||
appState.setAppStep(step: .createLive(timeSettingMode: .NOW, onSuccess: handleMainCreateLiveSuccess))
|
||||
}
|
||||
|
||||
private func showUserCreatorChatRecipientSearch() {
|
||||
appState.setAppStep(step: .userCreatorChatRecipientSearch)
|
||||
}
|
||||
|
||||
private func handleMainCreateLiveSuccess(response: CreateLiveRoomResponse) {
|
||||
liveViewModel.getLiveMain()
|
||||
if let _ = response.channelName {
|
||||
|
||||
Reference in New Issue
Block a user