feat(chat): 유저 크리에이터 DM을 추가한다
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
struct UserCreatorChatRoomView: View {
|
||||
@StateObject private var viewModel = UserCreatorChatRoomViewModel()
|
||||
@State private var messageText = ""
|
||||
|
||||
private let roomId: Int?
|
||||
private let creatorId: Int?
|
||||
|
||||
init(roomId: Int) {
|
||||
self.roomId = roomId
|
||||
self.creatorId = nil
|
||||
}
|
||||
|
||||
init(creatorId: Int) {
|
||||
self.roomId = nil
|
||||
self.creatorId = creatorId
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
BaseView(isLoading: $viewModel.isLoading) {
|
||||
VStack(spacing: 0) {
|
||||
headerView
|
||||
|
||||
messageListView
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
|
||||
inputBarView
|
||||
}
|
||||
.background(Color.black.ignoresSafeArea())
|
||||
}
|
||||
.onAppear(perform: enterRoom)
|
||||
.onDisappear { viewModel.leaveAndClose() }
|
||||
.sodaToast(
|
||||
isPresented: $viewModel.isShowPopup,
|
||||
message: viewModel.errorMessage,
|
||||
autohideIn: 2
|
||||
)
|
||||
}
|
||||
|
||||
private var headerView: some View {
|
||||
HStack(spacing: SodaSpacing.s12) {
|
||||
Image("ic_back")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.onTapGesture { AppState.shared.back() }
|
||||
|
||||
DownsampledKFImage(
|
||||
url: URL(string: viewModel.opponentProfileImageUrl ?? ""),
|
||||
size: CGSize(width: 36, height: 36)
|
||||
)
|
||||
.clipShape(Circle())
|
||||
|
||||
Text(viewModel.opponentNickname)
|
||||
.appFont(size: 14, weight: .bold)
|
||||
.foregroundColor(.white)
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.padding(.horizontal, SodaSpacing.s16)
|
||||
.padding(.vertical, SodaSpacing.s8)
|
||||
.frame(width: screenSize().width, height: 60)
|
||||
.background(Color.black)
|
||||
}
|
||||
|
||||
private var messageListView: some View {
|
||||
GeometryReader { geometry in
|
||||
ScrollViewReader { proxy in
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
LazyVStack(spacing: SodaSpacing.s16) {
|
||||
if viewModel.isLoadingNextPage {
|
||||
ProgressView()
|
||||
.progressViewStyle(.circular)
|
||||
.tint(Color.soda400)
|
||||
.padding(.vertical, SodaSpacing.s12)
|
||||
}
|
||||
|
||||
ForEach(textMessages) { message in
|
||||
UserCreatorChatTextMessageItemView(message: message)
|
||||
.id(message.id)
|
||||
.onAppear {
|
||||
if message.id == textMessages.first?.id {
|
||||
viewModel.loadMore()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, SodaSpacing.s24)
|
||||
.padding(.vertical, SodaSpacing.s12)
|
||||
.background(
|
||||
GeometryReader { contentGeometry in
|
||||
Color.clear.preference(
|
||||
key: UserCreatorChatContentHeightPreferenceKey.self,
|
||||
value: contentGeometry.size.height
|
||||
)
|
||||
}
|
||||
)
|
||||
.frame(minHeight: geometry.size.height, alignment: .bottom)
|
||||
}
|
||||
.onPreferenceChange(UserCreatorChatContentHeightPreferenceKey.self) { contentHeight in
|
||||
guard contentHeight <= geometry.size.height else { return }
|
||||
viewModel.loadMore()
|
||||
}
|
||||
.onChange(of: textMessages.last?.id) { _ in
|
||||
scrollToBottom(proxy)
|
||||
}
|
||||
.onChange(of: viewModel.socketState) { state in
|
||||
guard state == .joined else { return }
|
||||
scrollToBottom(proxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var inputBarView: some View {
|
||||
HStack(spacing: SodaSpacing.s8) {
|
||||
ZStack(alignment: .leading) {
|
||||
if messageText.isEmpty {
|
||||
Text(I18n.UserCreatorChat.messagePlaceholder)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(Color.gray500)
|
||||
}
|
||||
|
||||
TextField("", text: $messageText)
|
||||
.appFont(size: 14, weight: .regular)
|
||||
.foregroundColor(.white)
|
||||
.accentColor(Color.soda400)
|
||||
.disabled(isInputEnabled == false)
|
||||
.onSubmit { sendText() }
|
||||
}
|
||||
.padding(.horizontal, SodaSpacing.s16)
|
||||
.padding(.vertical, SodaSpacing.s12)
|
||||
.background(Color.gray900)
|
||||
.cornerRadius(999)
|
||||
|
||||
Button(action: sendText) {
|
||||
Image("ic_message_send")
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
.opacity(isSendEnabled ? 1 : 0.4)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.disabled(isSendEnabled == false)
|
||||
}
|
||||
.padding(.horizontal, SodaSpacing.s12)
|
||||
.padding(.vertical, SodaSpacing.s12)
|
||||
.frame(width: screenSize().width)
|
||||
.background(Color.black)
|
||||
}
|
||||
|
||||
private var textMessages: [UserCreatorChatDisplayMessage] {
|
||||
viewModel.messages.filter { $0.messageType == "TEXT" }
|
||||
}
|
||||
|
||||
private var isInputEnabled: Bool {
|
||||
viewModel.socketState == .joined
|
||||
}
|
||||
|
||||
private var isSendEnabled: Bool {
|
||||
isInputEnabled && messageText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == false
|
||||
}
|
||||
|
||||
private func enterRoom() {
|
||||
if let roomId {
|
||||
viewModel.enter(roomId: roomId)
|
||||
} else if let creatorId {
|
||||
viewModel.enter(creatorId: creatorId)
|
||||
}
|
||||
}
|
||||
|
||||
private func sendText() {
|
||||
let trimmedText = messageText.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard isInputEnabled, trimmedText.isEmpty == false else { return }
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
viewModel.sendText(trimmedText)
|
||||
messageText = ""
|
||||
}
|
||||
|
||||
private func scrollToBottom(_ proxy: ScrollViewProxy) {
|
||||
guard let lastMessage = textMessages.last else { return }
|
||||
withAnimation(.easeOut(duration: 0.3)) {
|
||||
proxy.scrollTo(lastMessage.id, anchor: .bottom)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct UserCreatorChatContentHeightPreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGFloat = 0
|
||||
|
||||
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
|
||||
value = nextValue()
|
||||
}
|
||||
}
|
||||
|
||||
struct UserCreatorChatRoomView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
UserCreatorChatRoomView(roomId: 1)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user