feat(live-room): 채팅창 얼리기 기능을 추가한다
채팅 입력 제어와 룸 상태 동기화를 통합해 지연 입장자도 동일 상태를 적용한다.
This commit is contained in:
@@ -30,6 +30,7 @@ enum LiveApi {
|
||||
case setListener(request: SetManagerOrSpeakerOrAudienceRequest)
|
||||
case setSpeaker(request: SetManagerOrSpeakerOrAudienceRequest)
|
||||
case setManager(request: SetManagerOrSpeakerOrAudienceRequest)
|
||||
case setChatFreeze(request: SetChatFreezeRequest)
|
||||
case kickOut(request: LiveRoomKickOutRequest)
|
||||
case donationStatus(roomId: Int)
|
||||
case donationTotal(roomId: Int)
|
||||
@@ -112,6 +113,9 @@ extension LiveApi: TargetType {
|
||||
|
||||
case .setManager:
|
||||
return "/live/room/info/set/manager"
|
||||
|
||||
case .setChatFreeze:
|
||||
return "/live/room/info/set/chat-freeze"
|
||||
|
||||
case .kickOut:
|
||||
return "/live/room/kick-out"
|
||||
@@ -156,7 +160,7 @@ extension LiveApi: TargetType {
|
||||
case .makeReservation, .enterRoom, .createRoom, .quitRoom, .donation, .refundDonation, .kickOut, .likeHeart:
|
||||
return .post
|
||||
|
||||
case .setListener, .setSpeaker, .setManager, .cancelReservation, .startLive, .cancelRoom, .editLiveRoomInfo:
|
||||
case .setListener, .setSpeaker, .setManager, .setChatFreeze, .cancelReservation, .startLive, .cancelRoom, .editLiveRoomInfo:
|
||||
return .put
|
||||
|
||||
case .deleteDonationMessage:
|
||||
@@ -237,6 +241,9 @@ extension LiveApi: TargetType {
|
||||
|
||||
case .setListener(let request), .setSpeaker(let request), .setManager(let request):
|
||||
return .requestJSONEncodable(request)
|
||||
|
||||
case .setChatFreeze(let request):
|
||||
return .requestJSONEncodable(request)
|
||||
|
||||
case .kickOut(let request):
|
||||
return .requestJSONEncodable(request)
|
||||
|
||||
@@ -92,6 +92,10 @@ final class LiveRepository {
|
||||
func setManager(roomId: Int, userId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.setManager(request: SetManagerOrSpeakerOrAudienceRequest(roomId: roomId, memberId: userId)))
|
||||
}
|
||||
|
||||
func setChatFreeze(roomId: Int, isChatFrozen: Bool) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.setChatFreeze(request: SetChatFreezeRequest(roomId: roomId, isChatFrozen: isChatFrozen)))
|
||||
}
|
||||
|
||||
func kickOut(roomId: Int, userId: Int) -> AnyPublisher<Response, MoyaError> {
|
||||
return api.requestPublisher(.kickOut(request: LiveRoomKickOutRequest(roomId: roomId, userId: userId)))
|
||||
|
||||
@@ -46,6 +46,12 @@ struct LiveRoomRouletteDonationChat: LiveRoomChat {
|
||||
|
||||
struct LiveRoomJoinChat: LiveRoomChat {
|
||||
let nickname: String
|
||||
let statusMessage: String?
|
||||
|
||||
var type: LiveRoomChatType = .JOIN
|
||||
|
||||
init(nickname: String, statusMessage: String? = nil) {
|
||||
self.nickname = nickname
|
||||
self.statusMessage = statusMessage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import Foundation
|
||||
|
||||
struct LiveRoomChatRawMessage: Codable {
|
||||
enum LiveRoomChatRawMessageType: String, Codable {
|
||||
case DONATION, SECRET_DONATION, EDIT_ROOM_INFO, SET_MANAGER, TOGGLE_ROULETTE, ROULETTE_DONATION
|
||||
case DONATION, SECRET_DONATION, EDIT_ROOM_INFO, SET_MANAGER, TOGGLE_ROULETTE, TOGGLE_CHAT_FREEZE, ROULETTE_DONATION
|
||||
case HEART_DONATION, BIG_HEART_DONATION
|
||||
}
|
||||
|
||||
@@ -20,4 +20,5 @@ struct LiveRoomChatRawMessage: Codable {
|
||||
var signatureImageUrl: String? = nil
|
||||
let donationMessage: String?
|
||||
var isActiveRoulette: Bool? = nil
|
||||
var isChatFrozen: Bool? = nil
|
||||
}
|
||||
|
||||
@@ -12,18 +12,27 @@ struct LiveRoomJoinChatItemView: View {
|
||||
let chatMessage: LiveRoomJoinChat
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
Text("'")
|
||||
.appFont(size: 12)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text(chatMessage.nickname)
|
||||
.appFont(size: 12, weight: .bold)
|
||||
.foregroundColor(Color.mainYellow)
|
||||
|
||||
Text("'님이 입장하셨습니다.")
|
||||
.appFont(size: 12)
|
||||
.foregroundColor(Color.grayee)
|
||||
Group {
|
||||
if let statusMessage = chatMessage.statusMessage,
|
||||
!statusMessage.isEmpty {
|
||||
Text(statusMessage)
|
||||
.appFont(size: 12)
|
||||
.foregroundColor(Color.grayee)
|
||||
} else {
|
||||
HStack(spacing: 0) {
|
||||
Text("'")
|
||||
.appFont(size: 12)
|
||||
.foregroundColor(Color.grayee)
|
||||
|
||||
Text(chatMessage.nickname)
|
||||
.appFont(size: 12, weight: .bold)
|
||||
.foregroundColor(Color.mainYellow)
|
||||
|
||||
Text("'님이 입장하셨습니다.")
|
||||
.appFont(size: 12)
|
||||
.foregroundColor(Color.grayee)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 6.7)
|
||||
.frame(width: screenSize().width - 86)
|
||||
|
||||
@@ -28,6 +28,7 @@ struct GetRoomInfoResponse: Decodable {
|
||||
let menuPan: String
|
||||
let creatorLanguageCode: String?
|
||||
let isActiveRoulette: Bool
|
||||
let isChatFrozen: Bool?
|
||||
let isPrivateRoom: Bool
|
||||
let password: String?
|
||||
}
|
||||
|
||||
@@ -185,6 +185,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
@Published var remainingNoChattingTime = 0
|
||||
|
||||
@Published var isActiveRoulette = false
|
||||
@Published var isChatFrozen = false
|
||||
|
||||
@Published var isShowRouletteSettings = false
|
||||
|
||||
@@ -281,6 +282,7 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
var bigHeartParticleTimer: DispatchSourceTimer?
|
||||
|
||||
var isAvailableLikeHeart = false
|
||||
private var isSettingChatFreeze = false
|
||||
|
||||
private var blockedMemberIdList = Set<Int>()
|
||||
|
||||
@@ -308,6 +310,14 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
v2vAgentId != nil
|
||||
}
|
||||
|
||||
var isChatFrozenForCurrentUser: Bool {
|
||||
guard let liveRoomInfo = liveRoomInfo else {
|
||||
return false
|
||||
}
|
||||
|
||||
return isChatFrozen && liveRoomInfo.creatorId != UserDefaults.int(forKey: .userId)
|
||||
}
|
||||
|
||||
func stopV2VTranslationIfJoined(clearCaptionText: Bool = true) {
|
||||
guard isV2VJoined else { return }
|
||||
stopV2VTranslation(clearCaptionText: clearCaptionText)
|
||||
@@ -591,6 +601,9 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
let decoded = try jsonDecoder.decode(ApiResponse<GetRoomInfoResponse>.self, from: responseData)
|
||||
|
||||
if let data = decoded.data, decoded.success {
|
||||
let previousIsChatFrozen = self.isChatFrozen
|
||||
let syncedIsChatFrozen = data.isChatFrozen ?? false
|
||||
|
||||
self.liveRoomInfo = data
|
||||
self.updateV2VAvailability(roomInfo: data)
|
||||
|
||||
@@ -599,6 +612,12 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
}
|
||||
|
||||
self.isActiveRoulette = data.isActiveRoulette
|
||||
self.isChatFrozen = syncedIsChatFrozen
|
||||
|
||||
if syncedIsChatFrozen && !previousIsChatFrozen {
|
||||
self.appendChatFreezeStatusMessage(isChatFrozen: true)
|
||||
}
|
||||
|
||||
self.isLoading = true
|
||||
|
||||
let rtcState = self.agora.getRtcConnectionState()
|
||||
@@ -670,7 +689,10 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
|
||||
func sendMessage(chatMessage: String, onSuccess: @escaping () -> Void) {
|
||||
DispatchQueue.main.async {[unowned self] in
|
||||
if isNoChatting {
|
||||
if isChatFrozenForCurrentUser {
|
||||
self.popupContent = I18n.LiveRoom.chatFreezeBlockedMessage
|
||||
self.isShowPopup = true
|
||||
} else if isNoChatting {
|
||||
self.popupContent = "\(remainingNoChattingTime)초 동안 채팅하실 수 없습니다"
|
||||
self.isShowPopup = true
|
||||
} else if chatMessage.count > 0 {
|
||||
@@ -1883,6 +1905,74 @@ final class LiveRoomViewModel: NSObject, ObservableObject {
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
func setChatFreeze(isChatFrozen: Bool) {
|
||||
guard let liveRoomInfo = liveRoomInfo,
|
||||
liveRoomInfo.creatorId == UserDefaults.int(forKey: .userId),
|
||||
!isSettingChatFreeze else {
|
||||
return
|
||||
}
|
||||
|
||||
isSettingChatFreeze = true
|
||||
|
||||
repository.setChatFreeze(roomId: liveRoomInfo.roomId, isChatFrozen: isChatFrozen)
|
||||
.sink { [unowned self] result in
|
||||
switch result {
|
||||
case .finished:
|
||||
DEBUG_LOG("finish")
|
||||
|
||||
case .failure(let error):
|
||||
ERROR_LOG(error.localizedDescription)
|
||||
self.isSettingChatFreeze = false
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowErrorPopup = true
|
||||
}
|
||||
} receiveValue: { [unowned self] response in
|
||||
self.isSettingChatFreeze = false
|
||||
let responseData = response.data
|
||||
|
||||
do {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
let decoded = try jsonDecoder.decode(ApiResponseWithoutData.self, from: responseData)
|
||||
|
||||
if decoded.success {
|
||||
self.isChatFrozen = isChatFrozen
|
||||
self.appendChatFreezeStatusMessage(isChatFrozen: isChatFrozen)
|
||||
self.invalidateChat()
|
||||
|
||||
self.agora.sendRawMessageToGroup(
|
||||
rawMessage: LiveRoomChatRawMessage(
|
||||
type: .TOGGLE_CHAT_FREEZE,
|
||||
message: "",
|
||||
can: 0,
|
||||
donationMessage: "",
|
||||
isActiveRoulette: nil,
|
||||
isChatFrozen: isChatFrozen
|
||||
)
|
||||
)
|
||||
} else {
|
||||
if let message = decoded.message {
|
||||
self.errorMessage = message
|
||||
} else {
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
}
|
||||
|
||||
self.isShowErrorPopup = true
|
||||
}
|
||||
} catch {
|
||||
self.errorMessage = I18n.Common.commonError
|
||||
self.isShowErrorPopup = true
|
||||
}
|
||||
}
|
||||
.store(in: &subscription)
|
||||
}
|
||||
|
||||
private func appendChatFreezeStatusMessage(isChatFrozen: Bool) {
|
||||
let statusMessage = isChatFrozen
|
||||
? I18n.LiveRoom.chatFreezeOnStatusMessage
|
||||
: I18n.LiveRoom.chatFreezeOffStatusMessage
|
||||
messages.append(LiveRoomJoinChat(nickname: "", statusMessage: statusMessage))
|
||||
}
|
||||
|
||||
func showRoulette() {
|
||||
if let liveRoomInfo = liveRoomInfo, !isLoading {
|
||||
@@ -2858,6 +2948,16 @@ extension LiveRoomViewModel: AgoraRtmClientDelegate {
|
||||
self.totalDonationCan += decoded.can
|
||||
} else if decoded.type == .TOGGLE_ROULETTE && decoded.isActiveRoulette != nil {
|
||||
self.isActiveRoulette = decoded.isActiveRoulette!
|
||||
} else if decoded.type == .TOGGLE_CHAT_FREEZE && decoded.isChatFrozen != nil {
|
||||
if Int(publisher) == self.liveRoomInfo?.creatorId {
|
||||
self.isChatFrozen = decoded.isChatFrozen!
|
||||
|
||||
if Int(publisher) != UserDefaults.int(forKey: .userId) {
|
||||
self.appendChatFreezeStatusMessage(isChatFrozen: self.isChatFrozen)
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG("Ignore TOGGLE_CHAT_FREEZE from non-creator publisher=\(publisher)")
|
||||
}
|
||||
} else if decoded.type == .EDIT_ROOM_INFO || decoded.type == .SET_MANAGER {
|
||||
self.getRoomInfo()
|
||||
} else if decoded.type == .HEART_DONATION {
|
||||
|
||||
@@ -11,3 +11,8 @@ struct SetManagerOrSpeakerOrAudienceRequest: Encodable {
|
||||
let roomId: Int
|
||||
let memberId: Int
|
||||
}
|
||||
|
||||
struct SetChatFreezeRequest: Encodable {
|
||||
let roomId: Int
|
||||
let isChatFrozen: Bool
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ struct LiveRoomInfoHostView: View {
|
||||
let isOnNotice: Bool
|
||||
let isOnMenuPan: Bool
|
||||
let isOnSignature: Bool
|
||||
let isOnChatFreeze: Bool
|
||||
let isShowMenuPanButton: Bool
|
||||
|
||||
let creatorId: Int
|
||||
@@ -40,6 +41,7 @@ struct LiveRoomInfoHostView: View {
|
||||
let onClickTotalHeart: () -> Void
|
||||
let onClickTotalDonation: () -> Void
|
||||
let onClickParticipants: () -> Void
|
||||
let onClickToggleChatFreeze: () -> Void
|
||||
let onClickToggleSignature: () -> Void
|
||||
|
||||
var body: some View {
|
||||
@@ -55,6 +57,18 @@ struct LiveRoomInfoHostView: View {
|
||||
) { onClickQuit() }
|
||||
|
||||
Spacer()
|
||||
|
||||
LiveRoomOverlayStrokeTextToggleButton(
|
||||
isOn: isOnChatFreeze,
|
||||
onText: I18n.LiveRoom.chatFreezeOn,
|
||||
onTextColor: Color.button,
|
||||
onStrokeColor: Color.button,
|
||||
offText: I18n.LiveRoom.chatFreezeOff,
|
||||
offTextColor: Color.graybb,
|
||||
offStrokeColor: Color.graybb,
|
||||
strokeWidth: 1,
|
||||
strokeCornerRadius: 5.3
|
||||
) { onClickToggleChatFreeze() }
|
||||
|
||||
LiveRoomOverlayStrokeTextToggleButton(
|
||||
isOn: isOnSignature,
|
||||
@@ -240,6 +254,7 @@ struct LiveRoomInfoHostView_Previews: PreviewProvider {
|
||||
isOnNotice: true,
|
||||
isOnMenuPan: false,
|
||||
isOnSignature: false,
|
||||
isOnChatFreeze: false,
|
||||
isShowMenuPanButton: false,
|
||||
creatorId: 1,
|
||||
creatorNickname: "도화",
|
||||
@@ -271,6 +286,7 @@ struct LiveRoomInfoHostView_Previews: PreviewProvider {
|
||||
onClickTotalHeart: {},
|
||||
onClickTotalDonation: {},
|
||||
onClickParticipants: {},
|
||||
onClickToggleChatFreeze: {},
|
||||
onClickToggleSignature: {}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,16 +11,27 @@ struct LiveRoomInputChatView: View {
|
||||
|
||||
@State private var chatMessage = ""
|
||||
|
||||
|
||||
let isInputDisabled: Bool
|
||||
let sendMessage: (String) -> Bool
|
||||
let onDisabledInputTap: () -> Void
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 6.7) {
|
||||
ChatTextFieldView(text: $chatMessage, placeholder: "채팅을 입력하세요") {
|
||||
ChatTextFieldView(text: $chatMessage, placeholder: "채팅을 입력하세요", isEnabled: !isInputDisabled) {
|
||||
if sendMessage(chatMessage) {
|
||||
chatMessage = ""
|
||||
}
|
||||
}
|
||||
.allowsHitTesting(!isInputDisabled)
|
||||
.overlay {
|
||||
if isInputDisabled {
|
||||
Color.clear
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
onDisabledInputTap()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 18.3)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.frame(maxWidth: .infinity)
|
||||
@@ -28,7 +39,13 @@ struct LiveRoomInputChatView: View {
|
||||
Image("btn_message_send")
|
||||
.resizable()
|
||||
.frame(width: 35, height: 35)
|
||||
.opacity(isInputDisabled ? 0.5 : 1)
|
||||
.onTapGesture {
|
||||
if isInputDisabled {
|
||||
onDisabledInputTap()
|
||||
return
|
||||
}
|
||||
|
||||
if sendMessage(chatMessage) {
|
||||
chatMessage = ""
|
||||
}
|
||||
@@ -43,12 +60,18 @@ struct LiveRoomInputChatView: View {
|
||||
.strokeBorder(lineWidth: 1)
|
||||
.foregroundColor(.gray77)
|
||||
)
|
||||
.onChange(of: isInputDisabled) { isDisabled in
|
||||
if isDisabled {
|
||||
hideKeyboard()
|
||||
chatMessage = ""
|
||||
}
|
||||
}
|
||||
.padding(13.3)
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveRoomInputChatView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LiveRoomInputChatView(sendMessage: { _ in return true })
|
||||
LiveRoomInputChatView(isInputDisabled: false, sendMessage: { _ in return true }, onDisabledInputTap: {})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,14 @@ struct LiveRoomViewV2: View {
|
||||
@State private var isShowFollowNotifyDialog: Bool = false
|
||||
@State private var guestFollowButtonTypeOverride: FollowButtonImageType? = nil
|
||||
let heartWaveTimer = Timer.publish(every: 1/60, on: .main, in: .common).autoconnect()
|
||||
|
||||
private var appliedKeyboardHeight: CGFloat {
|
||||
guard !viewModel.isChatFrozenForCurrentUser else {
|
||||
return 0
|
||||
}
|
||||
|
||||
return max(keyboardHandler.keyboardHeight, 0)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@@ -48,6 +56,7 @@ struct LiveRoomViewV2: View {
|
||||
isOnNotice: viewModel.isShowNotice,
|
||||
isOnMenuPan: viewModel.isShowMenuPan,
|
||||
isOnSignature: viewModel.isSignatureOn,
|
||||
isOnChatFreeze: viewModel.isChatFrozen,
|
||||
isShowMenuPanButton: !liveRoomInfo.menuPan.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty,
|
||||
creatorId: liveRoomInfo.creatorId,
|
||||
creatorNickname: liveRoomInfo.creatorNickname,
|
||||
@@ -88,6 +97,9 @@ struct LiveRoomViewV2: View {
|
||||
onClickParticipants: {
|
||||
viewModel.isShowProfileList = true
|
||||
},
|
||||
onClickToggleChatFreeze: {
|
||||
viewModel.setChatFreeze(isChatFrozen: !viewModel.isChatFrozen)
|
||||
},
|
||||
onClickToggleSignature: {
|
||||
viewModel.isSignatureOn.toggle()
|
||||
}
|
||||
@@ -343,14 +355,21 @@ struct LiveRoomViewV2: View {
|
||||
.padding(.horizontal, 13.3)
|
||||
}
|
||||
|
||||
LiveRoomInputChatView {
|
||||
viewModel.sendMessage(chatMessage: $0) {
|
||||
viewModel.isShowingNewChat = false
|
||||
proxy.scrollTo(viewModel.messages.count - 1, anchor: .center)
|
||||
LiveRoomInputChatView(
|
||||
isInputDisabled: viewModel.isChatFrozenForCurrentUser,
|
||||
sendMessage: {
|
||||
viewModel.sendMessage(chatMessage: $0) {
|
||||
viewModel.isShowingNewChat = false
|
||||
proxy.scrollTo(viewModel.messages.count - 1, anchor: .center)
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
onDisabledInputTap: {
|
||||
viewModel.errorMessage = I18n.LiveRoom.chatFreezeBlockedMessage
|
||||
viewModel.isShowErrorPopup = true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
)
|
||||
.padding(.top, isV2VCaptionVisible ? -13.3 : 0)
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
@@ -468,7 +487,7 @@ struct LiveRoomViewV2: View {
|
||||
}
|
||||
.sodaToast(isPresented: $viewModel.isShowErrorPopup, message: viewModel.errorMessage, autohideIn: 1.3)
|
||||
.cornerRadius(16.7, corners: [.topLeft, .topRight])
|
||||
.offset(y: -(keyboardHandler.keyboardHeight > 0 ? keyboardHandler.keyboardHeight : 0))
|
||||
.offset(y: -appliedKeyboardHeight)
|
||||
.onAppear {
|
||||
UIApplication.shared.isIdleTimerDisabled = true
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
@@ -810,8 +829,8 @@ struct LiveRoomViewV2: View {
|
||||
.drawingGroup(opaque: false, colorMode: .linear)
|
||||
}
|
||||
// 키보드가 올라오면 중앙 하트를 위로 올려 가리지 않도록 이동
|
||||
.offset(y: keyboardHandler.keyboardHeight > 0 ? -(keyboardHandler.keyboardHeight / 2 + 60) : 0)
|
||||
.animation(.spring(response: 0.3, dampingFraction: 0.85), value: keyboardHandler.keyboardHeight)
|
||||
.offset(y: appliedKeyboardHeight > 0 ? -(appliedKeyboardHeight / 2 + 60) : 0)
|
||||
.animation(.spring(response: 0.3, dampingFraction: 0.85), value: appliedKeyboardHeight)
|
||||
}
|
||||
.onReceive(heartWaveTimer) { _ in
|
||||
guard isLongPressingHeart else { return }
|
||||
@@ -836,8 +855,13 @@ struct LiveRoomViewV2: View {
|
||||
.onReceive(NotificationCenter.default.publisher(for: .requestLiveRoomQuitForExternalNavigation)) { _ in
|
||||
viewModel.quitRoom()
|
||||
}
|
||||
.onChange(of: viewModel.isChatFrozenForCurrentUser) { isFrozen in
|
||||
if isFrozen {
|
||||
hideKeyboard()
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea(.keyboard)
|
||||
.edgesIgnoringSafeArea(keyboardHandler.keyboardHeight > 0 ? .bottom : .init())
|
||||
.edgesIgnoringSafeArea(appliedKeyboardHeight > 0 ? .bottom : .init())
|
||||
.sheet(
|
||||
isPresented: $viewModel.isShowShareView,
|
||||
onDismiss: { viewModel.shareMessage = "" },
|
||||
|
||||
Reference in New Issue
Block a user