// // VoiceMessageWriteView.swift // SodaLive // // Created by klaus on 2023/08/10. // import SwiftUI struct VoiceMessageWriteView: View { @StateObject var viewModel = VoiceMessageViewModel() @StateObject var soundManager = SoundManager() @StateObject var appState = AppState.shared var replySenderId: Int? = nil var replySenderNickname: String? = nil let onRefresh: () -> Void @State var isShowSearchUser = false @State var timer = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect() @State var progress: TimeInterval = 0 var body: some View { ZStack { Color.black.opacity(0.7) .ignoresSafeArea() .onTapGesture { hideView() } GeometryReader { proxy in VStack { Spacer() VStack(spacing: 0) { HStack(spacing: 0) { Text("음성메시지") .font(.custom(Font.bold.rawValue, size: 18.3)) .foregroundColor(.white) Spacer() Image("ic_close_white") .resizable() .frame(width: 20, height: 20) .onTapGesture { hideView() } } .padding(.horizontal, 26.7) .padding(.top, 26.7) HStack(spacing: 13.3) { Image("img_thumb_default") .resizable() .frame(width: 46.7, height: 46.7) VStack(alignment: .leading, spacing: 10) { Text("TO.") .font(.custom(Font.medium.rawValue, size: 13.3)) .foregroundColor(Color(hex: "eeeeee")) Text( viewModel.recipientNickname.count > 0 ? viewModel.recipientNickname : "받는 사람" ) .font( .custom( viewModel.recipientNickname.count > 0 ? Font.bold.rawValue : Font.light.rawValue, size: 16.7 ) ) .foregroundColor( Color( hex: viewModel.recipientNickname.count > 0 ? "eeeeee" : "bbbbbb" ) ) } Spacer() if replySenderId == nil && replySenderNickname == nil { Image("btn_plus_round") .resizable() .frame(width: 27, height: 27) } } .padding(13.3) .background(Color(hex: "9970ff").opacity(0.2)) .cornerRadius(6.7) .padding(.horizontal, 13.3) .padding(.top, 26.7) .onTapGesture { if replySenderId == nil && replySenderNickname == nil { isShowSearchUser = true } } Text(secondsToHoursMinutesSeconds(seconds:Int(progress))) .font(.custom(Font.light.rawValue, size: 33.3)) .foregroundColor(.white) .padding(.top, 81) switch viewModel.recordMode { case .RECORD: Image(soundManager.isRecording ? "ic_record_stop" : "ic_record") .resizable() .frame(width: 70, height: 70) .padding(.vertical, 52.3) .onTapGesture { if viewModel.recipientId <= 0 { viewModel.errorMessage = "받는 사람을 선택해 주세요." viewModel.isShowPopup = true } else { progress = 0 if !soundManager.isRecording { soundManager.startRecording() } else { soundManager.stopRecording() viewModel.recordMode = .PLAY } } } case .PLAY: VStack(spacing: 0) { HStack(spacing: 0) { Spacer() Text("삭제") .font(.custom(Font.medium.rawValue, size: 15.3)) .foregroundColor(Color(hex: "bbbbbb").opacity(0)) Spacer() Image( !soundManager.isPlaying ? "ic_record_play" : "ic_record_pause" ) .onTapGesture { progress = 0 if !soundManager.isPlaying { soundManager.playAudio() } else { soundManager.stopAudio() } } Spacer() Text("삭제") .font(.custom(Font.medium.rawValue, size: 15.3)) .foregroundColor(Color(hex: "bbbbbb")) .onTapGesture { soundManager.stopAudio() soundManager.deleteAudioFile() viewModel.recordMode = .RECORD } Spacer() } .padding(.top, 90) HStack(spacing: 13.3) { Text("다시 녹음") .font(.custom(Font.bold.rawValue, size: 18.3)) .foregroundColor(Color(hex: "9970ff")) .frame(width: (proxy.size.width - 40) / 3, height: 50) .background(Color(hex: "9970ff").opacity(0.2)) .cornerRadius(10) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(Color(hex: "9970ff"), lineWidth: 1.3) ) .onTapGesture { soundManager.stopAudio() soundManager.deleteAudioFile() viewModel.recordMode = .RECORD } Text(viewModel.sendText) .font(.custom(Font.bold.rawValue, size: 18.3)) .foregroundColor(.white) .frame(width: (proxy.size.width - 40) * 2 / 3, height: 50) .background(Color(hex: "9970ff")) .cornerRadius(10) .onTapGesture { do { let soundData = try Data(contentsOf: soundManager.getAudioFileURL()) viewModel.write (soundData: soundData) { soundManager.deleteAudioFile() onRefresh() } } catch { viewModel.errorMessage = "음성메시지를 전송하지 못했습니다.\n다시 시도해 주세요.\n계속 같은 문제가 발생할 경우 고객센터로 문의 주시기 바랍니다." viewModel.isShowPopup = true } } } .padding(.top, 26.7) .padding(.bottom, 13.3) .padding(.horizontal, 13.3) } } if proxy.safeAreaInsets.bottom > 0 { Rectangle() .foregroundColor(Color(hex: "222222")) .frame(width: proxy.size.width, height: 15.3) } } .background(Color(hex: "222222")) .cornerRadius(16.7, corners: [.topLeft, .topRight]) } .edgesIgnoringSafeArea(.bottom) } if isShowSearchUser { SelectRecipientView(isShowing: $isShowSearchUser) { viewModel.recipientId = $0.id viewModel.recipientNickname = $0.nickname } } if viewModel.isLoading || soundManager.isLoading { LoadingView() } } .popup(isPresented: $viewModel.isShowPopup, type: .toast, position: .top, autohideIn: 2) { GeometryReader { geo in HStack { Spacer() Text(viewModel.errorMessage) .padding(.vertical, 13.3) .padding(.horizontal, 6.7) .frame(width: geo.size.width - 66.7, alignment: .center) .font(.custom(Font.medium.rawValue, size: 12)) .background(Color(hex: "9970ff")) .foregroundColor(Color.white) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .cornerRadius(20) .padding(.top, 66.7) Spacer() } } } .popup(isPresented: $soundManager.isShowPopup, type: .toast, position: .top, autohideIn: 1) { GeometryReader { geo in HStack { Spacer() Text(soundManager.errorMessage) .padding(.vertical, 13.3) .padding(.horizontal, 6.7) .frame(width: geo.size.width - 66.7, alignment: .center) .font(.custom(Font.medium.rawValue, size: 12)) .background(Color(hex: "9970ff")) .foregroundColor(Color.white) .multilineTextAlignment(.leading) .fixedSize(horizontal: false, vertical: true) .cornerRadius(20) .padding(.top, 66.7) Spacer() } .onDisappear { if soundManager.onClose { hideView() } } } } .onAppear { stopTimer() soundManager.startTimer = startTimer soundManager.stopTimer = stopTimer soundManager.prepareRecording() UITextView.appearance().backgroundColor = .clear if let replySenderId = replySenderId, let replySenderNickname = replySenderNickname { viewModel.recipientId = replySenderId viewModel.recipientNickname = replySenderNickname } } .onReceive(timer) { _ in switch viewModel.recordMode { case .RECORD: progress = soundManager.getRecorderCurrentTime() case .PLAY: progress = soundManager.getPlayerCurrentTime() } } } private func hideView() { if isShowSearchUser { isShowSearchUser = false } soundManager.deleteAudioFile() onRefresh() AppState.shared.back() } private func secondsToHoursMinutesSeconds(seconds: Int) -> String { let hour = String(format: "%02d", seconds / 3600) let minute = String(format: "%02d", (seconds % 3600) / 60) let second = String(format: "%02d", (seconds % 3600) % 60) return "\(hour):\(minute):\(second)" } private func startTimer() { timer = Timer.publish(every: 0.5, on: .main, in: .common).autoconnect() } private func stopTimer() { timer.upstream.connect().cancel() } } struct VoiceMessageWriteView_Previews: PreviewProvider { static var previews: some View { VoiceMessageWriteView(onRefresh: {}) } }