QmeraAudioViewController.swift 67 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382
  1. //
  2. // OutgoingViewController.swift
  3. // Qmera
  4. //
  5. // Created by Yayan Dwi on 07/10/21.
  6. //
  7. import UIKit
  8. import AVFoundation
  9. import nuSDKService
  10. import NotificationBannerSwift
  11. import MediaPlayer
  12. class QmeraAudioViewController: UIViewController {
  13. static private var nMaxSPOn: Float! = 20.0
  14. static private var nMaxSPOff: Float! = 20.0
  15. static private var volumeView: MPVolumeView!
  16. static private var lastVolume: Float! = AVAudioSession.sharedInstance().outputVolume
  17. static private var bSpeakerPhone: Bool! = false
  18. private var tempSpeaker = false
  19. private var timerSpeaker: Timer?
  20. private var canChangeSpeaker = false
  21. static private var isLoop = false
  22. let stackViewToolbar2 = UIStackView()
  23. var onScreenConstraintWB = [NSLayoutConstraint]()
  24. let buttonWB = UIButton()
  25. let buttonChat = UIButton()
  26. var wbVC : WhiteboardViewController?
  27. var wbTimer = Timer()
  28. var wbBlink = false
  29. var wbRoomId = ""
  30. var callFCM = true
  31. var autoAcceptAPN = false
  32. var timeStartCall = ""
  33. var idCall = ""
  34. let buttonSize: CGFloat = 70
  35. lazy var data: String = "" {
  36. didSet {
  37. getUserData { user in
  38. self.user = user
  39. }
  40. }
  41. }
  42. var user: User?
  43. var isAddCall = ""
  44. private var users: [User] = [] {
  45. didSet {
  46. DispatchQueue.main.async {
  47. if oldValue.count > self.users.count { // remove
  48. let remove = oldValue.filter { !self.users.contains($0) }
  49. remove.forEach { user in
  50. if let subviews = self.profiles.subviews as? [ProfileView] {
  51. subviews.forEach { p in
  52. if p.user == user {
  53. self.profiles.removeArrangeSubview(view: p)
  54. }
  55. }
  56. }
  57. }
  58. } else {
  59. if let user = self.users.last {
  60. let profile = ProfileView(image: UIImage(systemName: "person.circle.fill"))
  61. profile.user = user
  62. self.profiles.addArrangedSubview(view: profile)
  63. }
  64. }
  65. self.name.text = self.users.map { $0.fullName }.joined(separator: ", ")
  66. }
  67. }
  68. }
  69. var isOutgoing: Bool = true
  70. var isOnGoing: Bool = false
  71. var ticketId: String = ""
  72. private var timer: Timer?
  73. private var firstCall: Bool = true
  74. // private var isSpeaker: Bool = false
  75. private var isMuted: Bool = false
  76. var listRemoteViewFix: [UIImageView] = [
  77. UIImageView(),
  78. UIImageView(),
  79. UIImageView(),
  80. UIImageView(),
  81. UIImageView()
  82. ]
  83. let zoomView = UIImageView()
  84. let cameraView = UIImageView()
  85. let status: UILabel = {
  86. let label = UILabel()
  87. label.text = "Calling..."
  88. label.font = UIFont.systemFont(ofSize: 14)
  89. label.textColor = .white
  90. label.textAlignment = .center
  91. return label
  92. }()
  93. let profiles: GroupView = {
  94. let groupView = GroupView()
  95. groupView.spacing = 50
  96. groupView.maxUser = 3
  97. return groupView
  98. }()
  99. let name: UILabel = {
  100. let label = UILabel()
  101. label.text = "uwitan"
  102. label.font = UIFont.systemFont(ofSize: 14)
  103. label.textColor = .white
  104. label.textAlignment = .center
  105. return label
  106. }()
  107. let end: UIButton = {
  108. let button = UIButton()
  109. button.setImage(UIImage(systemName: "phone.down"), for: .normal)
  110. button.imageView?.contentMode = .scaleAspectFit
  111. button.imageView?.tintColor = .white
  112. button.setBackgroundColor(.red, for: .normal)
  113. button.setBackgroundColor(.white, for: .highlighted)
  114. button.contentVerticalAlignment = .fill
  115. button.contentHorizontalAlignment = .fill
  116. button.imageEdgeInsets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
  117. return button
  118. }()
  119. let reject: UIButton = {
  120. let button = UIButton()
  121. let image = UIImage(systemName: "xmark")
  122. button.setImage(image, for: .normal)
  123. let selectedImage = image?.withTintColor(.mainColor)
  124. button.setImage(selectedImage, for: .selected)
  125. button.imageView?.contentMode = .scaleAspectFit
  126. button.imageView?.tintColor = .white
  127. button.setBackgroundColor(.red, for: .normal)
  128. button.setBackgroundColor(.white, for: .highlighted)
  129. button.contentVerticalAlignment = .fill
  130. button.contentHorizontalAlignment = .fill
  131. button.imageEdgeInsets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
  132. return button
  133. }()
  134. let accept: UIButton = {
  135. let button = UIButton()
  136. let image = UIImage(systemName: "checkmark")
  137. button.setImage(image, for: .normal)
  138. button.imageView?.contentMode = .scaleAspectFit
  139. button.imageView?.tintColor = .white
  140. button.setBackgroundColor(.greenColor, for: .normal)
  141. button.setBackgroundColor(.white, for: .highlighted)
  142. button.contentVerticalAlignment = .fill
  143. button.contentHorizontalAlignment = .fill
  144. button.imageEdgeInsets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
  145. return button
  146. }()
  147. let invite: UIButton = {
  148. let button = UIButton()
  149. let image = UIImage(systemName: "person.badge.plus")
  150. button.setImage(image, for: .normal)
  151. button.imageView?.contentMode = .scaleAspectFit
  152. button.imageView?.tintColor = .mainColor
  153. button.setBackgroundColor(.white, for: .normal)
  154. button.setBackgroundColor(.mainColor, for: .highlighted)
  155. button.contentVerticalAlignment = .fill
  156. button.contentHorizontalAlignment = .fill
  157. button.imageEdgeInsets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
  158. return button
  159. }()
  160. let speaker: UIButton = {
  161. let button = UIButton()
  162. button.setImage(UIImage(systemName: "speaker.slash")?.withTintColor(.mainColor, renderingMode: .alwaysOriginal), for: .normal)
  163. button.setImage(UIImage(systemName: "speaker.wave.3")?.withTintColor(.white, renderingMode: .alwaysOriginal), for: .selected)
  164. button.imageView?.contentMode = .scaleAspectFit
  165. button.setBackgroundColor(.white, for: .normal)
  166. button.setBackgroundColor(.mainColor, for: .highlighted)
  167. button.setBackgroundColor(.mainColor, for: .selected)
  168. button.contentVerticalAlignment = .fill
  169. button.contentHorizontalAlignment = .fill
  170. button.imageEdgeInsets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
  171. return button
  172. }()
  173. let mic: UIButton = {
  174. let button = UIButton()
  175. button.setImage(UIImage(systemName: "mic")?.withTintColor(.mainColor, renderingMode: .alwaysOriginal), for: .normal)
  176. button.setImage(UIImage(systemName: "mic.slash")?.withTintColor(.white, renderingMode: .alwaysOriginal), for: .selected)
  177. button.imageView?.contentMode = .scaleAspectFit
  178. button.setBackgroundColor(.white, for: .normal)
  179. button.setBackgroundColor(.mainColor, for: .highlighted)
  180. button.setBackgroundColor(.mainColor, for: .selected)
  181. button.contentVerticalAlignment = .fill
  182. button.contentHorizontalAlignment = .fill
  183. button.imageEdgeInsets = UIEdgeInsets(top: 15, left: 15, bottom: 15, right: 15)
  184. return button
  185. }()
  186. let stack: UIStackView = {
  187. let stackView = UIStackView()
  188. stackView.axis = .horizontal
  189. stackView.distribution = .fillEqually
  190. return stackView
  191. }()
  192. let stack2: UIStackView = {
  193. let stackView = UIStackView()
  194. stackView.axis = .horizontal
  195. stackView.distribution = .fillEqually
  196. return stackView
  197. }()
  198. let poweredByView: UIStackView = {
  199. let stackView = UIStackView()
  200. stackView.axis = .horizontal
  201. stackView.spacing = 5
  202. return stackView
  203. }()
  204. let poweredByLabel: UILabel = {
  205. let label = UILabel()
  206. label.text = "Powered by Nexilis".localized()
  207. return label
  208. }()
  209. let qmeraLogo: UIButton = {
  210. let image = UIImage(named: "Q-Button-PNG", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  211. let button = UIButton()
  212. button.setImage(image, for: .normal)
  213. button.imageView?.contentMode = .scaleAspectFit
  214. button.imageEdgeInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
  215. button.contentVerticalAlignment = .fill
  216. button.contentHorizontalAlignment = .fill
  217. // button.frame.size.width = 30
  218. // button.frame.size.height = 30
  219. return button
  220. }()
  221. let nexilisLogo: UIButton = {
  222. let image = UIImage(named: "pb_powered_button", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)
  223. let button = UIButton()
  224. button.setImage(image, for: .normal)
  225. button.imageView?.contentMode = .scaleAspectFit
  226. button.imageEdgeInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
  227. button.contentVerticalAlignment = .fill
  228. button.contentHorizontalAlignment = .fill
  229. // button.frame.size.width = 30
  230. // button.frame.size.height = 30
  231. return button
  232. }()
  233. static func turnSpeakerOn() {
  234. // var bAudioEngineIsAvtive: Bool! = false
  235. // API.turnSpeakerPhone(bSPon: bSpeakerPhone)
  236. // repeat {
  237. // Thread.sleep(forTimeInterval : 0.3)
  238. // bAudioEngineIsAvtive = API.bAudioEngineIsRunning()
  239. // print("Audio Session State: \(bAudioEngineIsAvtive ? "Active" : "Inactive" )")
  240. // if (bAudioEngineIsAvtive) {
  241. // break
  242. // }
  243. // API.restartAudioEngine()
  244. // } while (!bAudioEngineIsAvtive)
  245. // var volume:Float! = 0
  246. do {
  247. let audioSession = AVAudioSession.sharedInstance()
  248. try audioSession.overrideOutputAudioPort(bSpeakerPhone ? .speaker : .none)
  249. } catch {
  250. }
  251. if (bSpeakerPhone) {
  252. DispatchQueue.main.async {
  253. UIDevice.current.isProximityMonitoringEnabled = false
  254. }
  255. // volume = lastVolume * nMaxSPOn
  256. } else {
  257. DispatchQueue.main.async {
  258. UIDevice.current.isProximityMonitoringEnabled = true
  259. }
  260. // volume = lastVolume * nMaxSPOff
  261. }
  262. // API.adjustVolume(fValue: volume)
  263. }
  264. // static func toggleSpeakerPhone() {
  265. // bSpeakerPhone = !bSpeakerPhone
  266. // var volume:Float! = 0
  267. // if (bSpeakerPhone) {
  268. // volume = lastVolume * nMaxSPOn
  269. // } else {
  270. // volume = lastVolume * nMaxSPOff
  271. // }
  272. // API.adjustVolume(fValue: volume)
  273. // }
  274. override func viewWillDisappear(_ animated: Bool) {
  275. NotificationCenter.default.removeObserver(self)
  276. UIDevice.current.isProximityMonitoringEnabled = false
  277. Nexilis.floatingButton.isHidden = false
  278. }
  279. deinit {
  280. UIDevice.current.isProximityMonitoringEnabled = false
  281. Nexilis.floatingButton.isHidden = false
  282. NotificationCenter.default.removeObserver(self)
  283. AVAudioSession.sharedInstance().removeObserver(self, forKeyPath: "outputVolume")
  284. }
  285. private func backToDefaultAudioSession() {
  286. do {
  287. let audioSession = AVAudioSession.sharedInstance()
  288. try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.allowBluetooth, .mixWithOthers])
  289. try audioSession.overrideOutputAudioPort(.speaker)
  290. try audioSession.setPreferredSampleRate(44100)
  291. try audioSession.setActive(true)
  292. } catch {
  293. }
  294. }
  295. override func viewDidAppear(_ animated: Bool) {
  296. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "onShowAC"), object: nil, userInfo: nil)
  297. }
  298. override func viewDidLoad() {
  299. super.viewDidLoad()
  300. QmeraAudioViewController.volumeView = MPVolumeView(frame: .zero)
  301. QmeraAudioViewController.volumeView.isHidden = true
  302. AVAudioSession.sharedInstance().addObserver(self, forKeyPath: "outputVolume", options: NSKeyValueObservingOptions.new, context: nil)
  303. Nexilis.floatingButton.isHidden = true
  304. let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .systemUltraThinMaterialDark))
  305. effectView.frame = view.frame
  306. view.insertSubview(effectView, at: 0)
  307. view.addSubview(status)
  308. view.addSubview(profiles)
  309. view.addSubview(name)
  310. status.anchor(left: view.leftAnchor, bottom: profiles.topAnchor, right: view.rightAnchor, paddingBottom: 30, centerX: view.centerXAnchor)
  311. profiles.anchor(centerX: view.centerXAnchor, centerY: view.centerYAnchor, width: 150, height: 150)
  312. name.anchor(top: profiles.bottomAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 5, paddingLeft: 20, paddingRight: 20, centerX: view.centerXAnchor)
  313. definesPresentationContext = true
  314. if isOutgoing {
  315. outgoingView()
  316. } else if isOnGoing || autoAcceptAPN {
  317. ongoingView()
  318. } else {
  319. incomingView()
  320. }
  321. UIDevice.current.isProximityMonitoringEnabled = true
  322. NotificationCenter.default.addObserver(self, selector: #selector(onStatusCall(_:)), name: NSNotification.Name(rawValue: Nexilis.listenerStatusCall), object: nil)
  323. NotificationCenter.default.addObserver(self, selector: #selector(onReceiveMessage(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerReceiveChat), object: nil)
  324. NotificationCenter.default.addObserver(self, selector: #selector(onCallFCM(notification:)), name: NSNotification.Name(rawValue: Nexilis.callFCM), object: nil)
  325. if let u = self.user {
  326. self.users.append(u)
  327. if isOutgoing && ticketId.isEmpty {
  328. // if onGoingCC.isEmpty {
  329. // Nexilis.shared.callManager.startCall(handle: u.pin)
  330. // } else {
  331. // API.initiateCCall(sParty: u.pin)
  332. // }
  333. if callFCM {
  334. DispatchQueue.global().async {
  335. if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getCalling(fPin: u.pin, type: "1"), timeout: 30 * 1000) {
  336. if response.isOk() {
  337. DispatchQueue.main.async {
  338. self.status.text = "Waiting for answer".localized()
  339. }
  340. } else if response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "01" {
  341. API.initiateCCall(sParty: u.pin)
  342. } else {
  343. DispatchQueue.main.async {
  344. Nexilis.stopRingbacktoneCall()
  345. }
  346. DispatchQueue.main.async {
  347. let longCall = "0"
  348. Nexilis.saveMessageCall(idCall: self.idCall, textMessage: "Outgoing audio call".localized() + " at \(longCall)", fPin: User.getMyPin() ?? "", lPin: !self.data.isEmpty ? self.data : self.user != nil ? self.user!.pin : "", timeCall: self.timeStartCall, attachment_type: MessageScope.CALL)
  349. self.status.text = "Busy..."
  350. self.end.isEnabled = false
  351. if self.isOutgoing {
  352. Nexilis.playBusyCall()
  353. }
  354. DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
  355. Nexilis.stopBusyCall()
  356. self.didEnd(sender: false)
  357. }
  358. }
  359. }
  360. } else {
  361. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  362. imageView.tintColor = .white
  363. let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  364. banner.show()
  365. }
  366. }
  367. } else {
  368. API.initiateCCall(sParty: u.pin)
  369. }
  370. } else if !ticketId.isEmpty {
  371. if isOutgoing {
  372. self.backToDefaultAudioSession()
  373. API.ccs(sTicketID: ticketId, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivRemoteView: listRemoteViewFix, ivLocalView: cameraView, ivRemoteZ: zoomView, bCameraOn: false)
  374. if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getIncomingCallCS(f_pin_opposite: u.pin), timeout: 30 * 1000){
  375. if response.mBodies[CoreMessage_TMessageKey.ERRCOD] != "01" {
  376. self.didEnd(sender: true)
  377. }
  378. }
  379. } else {
  380. self.backToDefaultAudioSession()
  381. API.csa(sTicketID: ticketId, nCamIdx: 1, nResIdx: 2, nVQuality: 4, ivRemoteView: listRemoteViewFix, ivLocalView: cameraView, ivRemoteZ: zoomView, bCameraOn: false)
  382. }
  383. } else if autoAcceptAPN {
  384. DispatchQueue.global().async {
  385. do {
  386. if API.nGetCLXConnState() == 0 {
  387. let id = Utils.getConnectionID()
  388. try API.initConnection(sAPIK: Nexilis.sAPIKey, cbiI: Callback(), sTCPAddr: Nexilis.ADDRESS, nTCPPort: Nexilis.PORT, sUserID: id, sStartWH: "09:00")
  389. }
  390. } catch {
  391. }
  392. self.backToDefaultAudioSession()
  393. while API.nGetCLXConnState() == 0 {
  394. Thread.sleep(forTimeInterval : 0.3)
  395. }
  396. _ = Nexilis.write(message: CoreMessage_TMessageBank.getNotifyCalling(fPin: u.pin, lPin: User.getMyPin()!, type: "1"))
  397. }
  398. }
  399. }
  400. self.timeStartCall = String(Date().currentTimeMillis())
  401. self.idCall = (User.getMyPin() ?? "") + CoreMessage_TMessageUtil.getTID()
  402. }
  403. override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
  404. if (keyPath! == "outputVolume") {
  405. if let newKey = change?[NSKeyValueChangeKey.newKey] as? NSNumber {
  406. QmeraAudioViewController.lastVolume = newKey.floatValue
  407. if (QmeraAudioViewController.bSpeakerPhone) {
  408. let volume = QmeraAudioViewController.lastVolume * QmeraAudioViewController.nMaxSPOn
  409. API.adjustVolume(fValue: volume)
  410. } else {
  411. let volume = QmeraAudioViewController.lastVolume * QmeraAudioViewController.nMaxSPOff
  412. API.adjustVolume(fValue: volume)
  413. }
  414. }
  415. }
  416. }
  417. @objc func onCallFCM(notification: NSNotification) {
  418. DispatchQueue.main.async {
  419. let data:[AnyHashable : Any] = notification.userInfo!
  420. if let l_pin = data["l_pin"] as? String {
  421. if let f_pin = data["f_pin"] as? String {
  422. if f_pin == User.getMyPin()! {
  423. API.initiateCCall(sParty: l_pin)
  424. }
  425. }
  426. } else if data["call_cancel"] != nil {
  427. DispatchQueue.main.async {
  428. Nexilis.stopRingbacktoneCall()
  429. }
  430. DispatchQueue.main.async {
  431. let longCall = "0"
  432. Nexilis.saveMessageCall(idCall: self.idCall, textMessage: "Outgoing audio call".localized() + " at \(longCall)", fPin: User.getMyPin() ?? "", lPin: !self.data.isEmpty ? self.data : self.user != nil ? self.user!.pin : "", timeCall: self.timeStartCall, attachment_type: MessageScope.CALL)
  433. self.status.text = "Busy..."
  434. self.end.isEnabled = false
  435. if self.isOutgoing {
  436. Nexilis.playBusyCall()
  437. }
  438. DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
  439. Nexilis.stopBusyCall()
  440. self.didEnd(sender: false)
  441. }
  442. }
  443. }
  444. }
  445. }
  446. override func viewWillLayoutSubviews() {
  447. super.viewWillLayoutSubviews()
  448. end.circle()
  449. reject.circle()
  450. accept.circle()
  451. invite.circle()
  452. speaker.circle()
  453. mic.circle()
  454. }
  455. private func getUserData(completion: @escaping (User?) -> ()) {
  456. if let user = self.user {
  457. completion(user)
  458. return
  459. }
  460. var user: User?
  461. DispatchQueue.global().async {
  462. Database.shared.database?.inTransaction({ fmdb, rollback in
  463. do {
  464. if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select f_pin, first_name, last_name, image_id from BUDDY where f_pin = '\(self.data)'"), cursor.next() {
  465. user = User(pin: cursor.string(forColumnIndex: 0) ?? "",
  466. firstName: cursor.string(forColumnIndex: 1) ?? "",
  467. lastName: cursor.string(forColumnIndex: 2) ?? "",
  468. thumb: cursor.string(forColumnIndex: 3) ?? "")
  469. cursor.close()
  470. }
  471. } catch {
  472. rollback.pointee = true
  473. print("Access database error: \(error.localizedDescription)")
  474. }
  475. })
  476. }
  477. completion(user)
  478. }
  479. private func resetViewToOutgoing() {
  480. self.timer?.invalidate()
  481. self.timer = nil
  482. self.firstCall = true
  483. status.removeFromSuperview()
  484. profiles.removeFromSuperview()
  485. name.removeFromSuperview()
  486. stack.removeFromSuperview()
  487. stack2.removeFromSuperview()
  488. stackViewToolbar2.removeFromSuperview()
  489. buttonWB.removeFromSuperview()
  490. buttonChat.removeFromSuperview()
  491. poweredByView.removeFromSuperview()
  492. view.addSubview(status)
  493. view.addSubview(profiles)
  494. view.addSubview(name)
  495. status.anchor(left: view.leftAnchor, bottom: profiles.topAnchor, right: view.rightAnchor, paddingBottom: 30, centerX: view.centerXAnchor)
  496. profiles.anchor(centerX: view.centerXAnchor, centerY: view.centerYAnchor, width: 150, height: 150)
  497. name.anchor(top: profiles.bottomAnchor, left: view.leftAnchor, right: view.rightAnchor, paddingTop: 5, paddingLeft: 20, paddingRight: 20, centerX: view.centerXAnchor)
  498. status.text = "Connecting..."
  499. view.addSubview(end)
  500. end.anchor(bottom: view.bottomAnchor, paddingBottom: 60, centerX: view.centerXAnchor, width: buttonSize, height: buttonSize)
  501. end.addTarget(self, action: #selector(didPressEnd(sender:)), for: .touchUpInside)
  502. }
  503. private func outgoingView() {
  504. // Nexilis.setSpeakerphoneOn(isSpeaker)
  505. if ticketId.isEmpty {
  506. backToDefaultAudioSession()
  507. Nexilis.playRingbacktoneCall()
  508. }
  509. status.text = "Connecting..."
  510. view.addSubview(end)
  511. end.anchor(bottom: view.bottomAnchor, paddingBottom: 60, centerX: view.centerXAnchor, width: buttonSize, height: buttonSize)
  512. end.addTarget(self, action: #selector(didPressEnd(sender:)), for: .touchUpInside)
  513. }
  514. private func incomingView() {
  515. if ticketId.isEmpty {
  516. backToDefaultAudioSession()
  517. Nexilis.playRingtoneCall()
  518. }
  519. status.text = "Incoming..."
  520. stack.spacing = buttonSize
  521. view.addSubview(stack)
  522. stack.anchor(bottom: view.bottomAnchor, paddingBottom: 60, centerX: view.centerXAnchor, width: buttonSize * 3, height: buttonSize)
  523. stack.addArrangedSubview(reject)
  524. stack.addArrangedSubview(accept)
  525. reject.addTarget(self, action: #selector(didReject(sender:)), for: .touchUpInside)
  526. accept.addTarget(self, action: #selector(didAccept(sender:)), for: .touchUpInside)
  527. }
  528. private func ongoingView() {
  529. status.text = "Connecting..."
  530. stack.spacing = buttonSize / 2
  531. stack2.spacing = buttonSize / 2
  532. view.addSubview(stack)
  533. view.addSubview(stack2)
  534. stack.anchor(bottom: view.bottomAnchor, paddingBottom: 60, centerX: view.centerXAnchor, width: buttonSize * 4, height: buttonSize)
  535. stack2.anchor(bottom: stack.topAnchor, paddingBottom: 10, centerX: view.centerXAnchor, width: buttonSize, height: buttonSize)
  536. stack.addArrangedSubview(invite)
  537. stack.addArrangedSubview(end)
  538. stack.addArrangedSubview(speaker)
  539. stack2.addArrangedSubview(mic)
  540. invite.addTarget(self, action: #selector(didInvite(sender:)), for: .touchUpInside)
  541. end.addTarget(self, action: #selector(didPressEnd(sender:)), for: .touchUpInside)
  542. speaker.addTarget(self, action: #selector(didSpeaker(sender:)), for: .touchUpInside)
  543. mic.addTarget(self, action: #selector(didMute(sender:)), for: .touchUpInside)
  544. if !ticketId.isEmpty {
  545. self.view.addSubview(self.stackViewToolbar2)
  546. self.stackViewToolbar2.translatesAutoresizingMaskIntoConstraints = false
  547. NSLayoutConstraint.activate([
  548. self.stackViewToolbar2.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
  549. self.stackViewToolbar2.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 10.0)
  550. ])
  551. self.stackViewToolbar2.axis = .vertical
  552. self.stackViewToolbar2.distribution = .equalSpacing
  553. self.stackViewToolbar2.alignment = .center
  554. self.stackViewToolbar2.spacing = 5
  555. view.addSubview(buttonWB)
  556. buttonWB.translatesAutoresizingMaskIntoConstraints = false
  557. buttonWB.frame.size = CGSize(width: 40.0, height: 40.0)
  558. NSLayoutConstraint.activate([
  559. buttonWB.widthAnchor.constraint(equalToConstant: 40.0),
  560. buttonWB.heightAnchor.constraint(equalToConstant: 40.0)
  561. ])
  562. buttonWB.backgroundColor = .lightGray
  563. buttonWB.setImage(UIImage(systemName: "ipad.landscape", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium, scale: .default)), for: .normal)
  564. buttonWB.circle()
  565. buttonWB.tintColor = .black
  566. buttonWB.addTarget(self, action: #selector(didTapWBButton), for: .touchUpInside)
  567. view.addSubview(buttonChat)
  568. buttonChat.translatesAutoresizingMaskIntoConstraints = false
  569. buttonChat.frame.size = CGSize(width: 40.0, height: 40.0)
  570. NSLayoutConstraint.activate([
  571. buttonChat.widthAnchor.constraint(equalToConstant: 40.0),
  572. buttonChat.heightAnchor.constraint(equalToConstant: 40.0)
  573. ])
  574. buttonChat.backgroundColor = .lightGray
  575. buttonChat.setImage(UIImage(systemName: "bubble.right", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .medium, scale: .default)), for: .normal)
  576. buttonChat.circle()
  577. buttonChat.tintColor = .black
  578. buttonChat.addTarget(self, action: #selector(didTapChatButton), for: .touchUpInside)
  579. }
  580. self.view.addSubview(poweredByView)
  581. self.poweredByView.translatesAutoresizingMaskIntoConstraints = false
  582. let constraintRightPowered = self.poweredByView.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: -10.0)
  583. let constraintBottomPowered = self.poweredByView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -10.0)
  584. NSLayoutConstraint.activate([
  585. constraintRightPowered,
  586. constraintBottomPowered,
  587. nexilisLogo.widthAnchor.constraint(equalToConstant: 30.0),
  588. nexilisLogo.heightAnchor.constraint(equalToConstant: 30.0)
  589. ])
  590. poweredByView.addArrangedSubview(poweredByLabel)
  591. poweredByView.addArrangedSubview(nexilisLogo)
  592. stackViewToolbar2.addArrangedSubview(buttonWB)
  593. stackViewToolbar2.addArrangedSubview(buttonChat)
  594. }
  595. // MARK: - Action
  596. @objc func didTapChatButton(){
  597. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  598. let members: String = SecureUserDefaults.shared.value(forKey: "membersCC") ?? ""
  599. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  600. let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal
  601. editorPersonalVC.hidesBottomBarWhenPushed = true
  602. editorPersonalVC.unique_l_pin = officer
  603. editorPersonalVC.fromNotification = true
  604. editorPersonalVC.isContactCenter = true
  605. editorPersonalVC.fPinContacCenter = members
  606. editorPersonalVC.complaintId = ticketId
  607. editorPersonalVC.onGoingCC = true
  608. editorPersonalVC.isRequestContactCenter = false
  609. editorPersonalVC.channelContactCenter = "1"
  610. editorPersonalVC.users = users
  611. editorPersonalVC.fromVCAC = true
  612. let navigationController = CustomNavigationController(rootViewController: editorPersonalVC)
  613. navigationController.modalPresentationStyle = .overCurrentContext
  614. navigationController.navigationBar.tintColor = .white
  615. navigationController.navigationBar.barTintColor = self.traitCollection.userInterfaceStyle == .dark ? .blackDarkMode : .mainColor
  616. navigationController.navigationBar.isTranslucent = false
  617. navigationController.navigationBar.overrideUserInterfaceStyle = .dark
  618. navigationController.navigationBar.barStyle = .black
  619. let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)]
  620. UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal)
  621. let textAttributes = [NSAttributedString.Key.foregroundColor:UIColor.white]
  622. navigationController.navigationBar.titleTextAttributes = textAttributes
  623. if UIApplication.shared.visibleViewController?.navigationController != nil {
  624. UIApplication.shared.visibleViewController?.navigationController?.present(navigationController, animated: true, completion: nil)
  625. } else {
  626. UIApplication.shared.visibleViewController?.present(navigationController, animated: true, completion: nil)
  627. }
  628. }
  629. @objc func didTapWBButton(){
  630. if(wbVC == nil){
  631. wbVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "wbVC") as? WhiteboardViewController
  632. if(wbRoomId.isEmpty){
  633. let me = User.getMyPin()!
  634. let tid = CoreMessage_TMessageUtil.getTID()
  635. wbRoomId = "\(me)wbvc\(tid)"
  636. wbVC!.roomId = wbRoomId
  637. var destinations = [String]()
  638. var destString = ""
  639. for d in users{
  640. destinations.append(d.pin)
  641. if destString.isEmpty{
  642. destString = d.pin
  643. } else {
  644. destString = destString + ",\(d.pin)"
  645. }
  646. }
  647. wbVC!.destinations = destinations
  648. wbVC!.sendInit()
  649. SecureUserDefaults.shared.set("\(me),\(destString)", forKey: "wb_vc")
  650. }
  651. else {
  652. self.wbTimer.invalidate()
  653. self.buttonWB.backgroundColor = .lightGray
  654. wbVC!.roomId = wbRoomId
  655. wbVC!.sendJoin()
  656. }
  657. }
  658. wbVC!.close = {
  659. DispatchQueue.main.async {
  660. if self.wbVC!.view.isDescendant(of: self.view){
  661. self.wbVC!.view.removeFromSuperview()
  662. }
  663. // self.buttonDecline.isHidden = false
  664. // self.buttonSpeaker.isHidden = false
  665. // self.buttonAddParticipant.isHidden = false
  666. // self.buttonRotate.isHidden = false
  667. // if(!self.wbRoomId.isEmpty){
  668. // DispatchQueue.main.async {
  669. // self.wbTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.runTimer), userInfo: nil, repeats: true)
  670. // }
  671. // }
  672. }
  673. }
  674. // self.buttonDecline.isHidden = true
  675. // self.buttonSpeaker.isHidden = true
  676. // self.buttonAddParticipant.isHidden = true
  677. // self.buttonRotate.isHidden = true
  678. addChild(wbVC!)
  679. wbVC!.view.translatesAutoresizingMaskIntoConstraints = false
  680. view.addSubview(wbVC!.view)
  681. onScreenConstraintWB = [
  682. wbVC!.view.topAnchor.constraint(equalTo: self.view.topAnchor),
  683. wbVC!.view.bottomAnchor.constraint(equalTo: self.view.bottomAnchor),
  684. wbVC!.view.rightAnchor.constraint(equalTo: self.view.rightAnchor),
  685. wbVC!.view.leftAnchor.constraint(equalTo: self.view.leftAnchor),
  686. ]
  687. NSLayoutConstraint.activate(onScreenConstraintWB)
  688. // Notify the child view controller that the move is complete.
  689. wbVC!.didMove(toParent: self)
  690. // self.navigationController?.setNavigationBarHidden(false, animated: true)
  691. // controller.modalPresentationStyle = .overCurrentContext
  692. // self.navigationController?.present(controller, animated: true)
  693. }
  694. @objc func didSpeaker(sender: Any?) {
  695. timerSpeaker?.invalidate()
  696. tempSpeaker = !tempSpeaker
  697. speaker.isSelected = tempSpeaker
  698. timerSpeaker = Timer.scheduledTimer(withTimeInterval: 1, repeats: false, block: {_ in
  699. if QmeraAudioViewController.bSpeakerPhone != self.tempSpeaker {
  700. QmeraAudioViewController.bSpeakerPhone = !QmeraAudioViewController.bSpeakerPhone
  701. self.tempSpeaker = QmeraAudioViewController.bSpeakerPhone
  702. DispatchQueue.global().async {
  703. QmeraAudioViewController.turnSpeakerOn()
  704. }
  705. }
  706. })
  707. }
  708. @objc func didMute(sender: Any?) {
  709. isMuted = !isMuted
  710. mic.isSelected = isMuted
  711. API.mmc(int: 1, boolean: isMuted)
  712. }
  713. @objc func didInvite(sender: Any?) {
  714. let controller = QmeraCallContactViewController()
  715. controller.isDismiss = { user in
  716. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  717. if !onGoingCC.isEmpty {
  718. DispatchQueue.global().async {
  719. _ = Nexilis.write(message: CoreMessage_TMessageBank.getCCRoomInvite(l_pin: user.pin, ticket_id: onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[2], channel: "1"))
  720. }
  721. DispatchQueue.main.async {
  722. self.isAddCall = user.pin
  723. }
  724. } else {
  725. self.users.append(user)
  726. if self.callFCM {
  727. DispatchQueue.global().async {
  728. if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getCalling(fPin: user.pin, type: "1"), timeout: 30 * 1000) {
  729. if response.isOk() {
  730. } else if response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "01" {
  731. API.initiateCCall(sParty: user.pin)
  732. Nexilis.playRingbacktoneCall()
  733. } else {
  734. Nexilis.stopRingbacktoneCall()
  735. }
  736. } else {
  737. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  738. imageView.tintColor = .white
  739. let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  740. banner.show()
  741. }
  742. }
  743. } else {
  744. API.initiateCCall(sParty: user.pin)
  745. Nexilis.playRingbacktoneCall()
  746. }
  747. }
  748. }
  749. controller.selectedUser.append(contentsOf: users)
  750. present(CustomNavigationController(rootViewController: controller), animated: true, completion: nil)
  751. }
  752. @objc func didPressEnd(sender: Any?) {
  753. if let sharedAudioPlayer = Nexilis.sharedAudioPlayer, sharedAudioPlayer.isPlaying {
  754. Nexilis.stopRingtoneCall()
  755. Nexilis.stopRingbacktoneCall()
  756. }
  757. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  758. if !onGoingCC.isEmpty {
  759. self.didEnd(sender: nil)
  760. return
  761. }
  762. let alert = LibAlertController(title: "End Audio Call".localized(), message: "Are you sure you want to end audio call?".localized(), preferredStyle: .alert)
  763. alert.addAction(UIAlertAction(title: "No".localized(), style: UIAlertAction.Style.default, handler: nil))
  764. alert.addAction(UIAlertAction(title: "Yes".localized(), style: UIAlertAction.Style.default, handler: {(_) in
  765. DispatchQueue.main.async {
  766. if self.timer == nil || self.isOutgoing {
  767. let longCall = self.timer == nil ? "0" : self.status.text ?? ""
  768. Nexilis.saveMessageCall(idCall: self.idCall, textMessage: "Outgoing audio call".localized() + " at \(longCall)", fPin: User.getMyPin() ?? "", lPin: !self.data.isEmpty ? self.data : self.user != nil ? self.user!.pin : "", timeCall: self.timeStartCall, attachment_type: MessageScope.CALL)
  769. } else if !self.isOutgoing {
  770. Nexilis.saveMessageCall(idCall: self.idCall, textMessage: "Incoming audio call".localized() + " at \(self.status.text ?? "")", fPin: !self.data.isEmpty ? self.data : self.user != nil ? self.user!.pin : "", lPin: User.getMyPin() ?? "", timeCall: self.timeStartCall, attachment_type: MessageScope.CALL)
  771. }
  772. if self.callFCM && self.timer == nil {
  773. DispatchQueue.global().async {
  774. if let _ = Nexilis.writeSync(message: CoreMessage_TMessageBank.getCancelCall(fPin: self.user!.pin, type: "1"), timeout: 30 * 1000) {
  775. } else {
  776. let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
  777. imageView.tintColor = .white
  778. let banner = FloatingNotificationBanner(title: "Unable to access servers. Try again later".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
  779. banner.show()
  780. }
  781. }
  782. }
  783. self.timer?.invalidate()
  784. self.timer = nil
  785. self.status.text = "Audio Call Ended".localized()
  786. self.end.isEnabled = false
  787. self.invite.isEnabled = false
  788. self.speaker.isEnabled = false
  789. self.mic.isEnabled = false
  790. }
  791. self.didEnd(sender: nil)
  792. }))
  793. self.present(alert, animated: true, completion: nil)
  794. }
  795. @objc func didEnd(sender: Any?) {
  796. if let sharedAudioPlayer = Nexilis.sharedAudioPlayer, sharedAudioPlayer.isPlaying {
  797. Nexilis.stopRingtoneCall()
  798. Nexilis.stopRingbacktoneCall()
  799. }
  800. if APIS.uuidCall != nil {
  801. CallManager.shared.endCall(uuid: APIS.uuidCall!) {
  802. APIS.uuidCall = nil
  803. }
  804. }
  805. poweredByView.isHidden = true
  806. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  807. if !onGoingCC.isEmpty {
  808. if sender != nil && sender is Bool {
  809. let controller = self.presentedViewController
  810. if controller != nil {
  811. controller!.dismiss(animated: true)
  812. }
  813. self.dismiss(animated: false, completion: nil)
  814. let requester = onGoingCC.components(separatedBy: ",")[0]
  815. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  816. let complaintId = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[2]
  817. let startTimeCC: String = SecureUserDefaults.shared.value(forKey: "startTimeCC") ?? ""
  818. DispatchQueue.global().async {
  819. if sender as! Bool == true {
  820. let date = "\(Date().currentTimeMillis())"
  821. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  822. do {
  823. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", cvalues: [
  824. "type" : "1",
  825. "title" : "Contact Center".localized(),
  826. "time" : startTimeCC,
  827. "f_pin" : officer,
  828. "data" : complaintId,
  829. "time_end" : date,
  830. "complaint_id" : complaintId,
  831. "members" : "",
  832. "requester": requester
  833. ], replace: true)
  834. } catch {
  835. rollback.pointee = true
  836. print("Access database error: \(error.localizedDescription)")
  837. }
  838. })
  839. }
  840. SecureUserDefaults.shared.removeValue(forKey: "onGoingCC")
  841. SecureUserDefaults.shared.removeValue(forKey: "membersCC")
  842. SecureUserDefaults.shared.removeValue(forKey: "startTimeCC")
  843. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  844. }
  845. return
  846. }
  847. let alert = LibAlertController(title: "Interaction with Call Center is in progress".localized(), message: "Are you sure you want to end the Call Center?".localized(), preferredStyle: .alert)
  848. alert.addAction(UIAlertAction(title: "No".localized(), style: UIAlertAction.Style.default, handler: nil))
  849. alert.addAction(UIAlertAction(title: "Yes".localized(), style: UIAlertAction.Style.default, handler: {(_) in
  850. self.dismiss(animated: false, completion: nil)
  851. let requester = onGoingCC.components(separatedBy: ",")[0]
  852. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  853. let complaintId = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[2]
  854. let idMe = User.getMyPin()!
  855. let startTimeCC: String = SecureUserDefaults.shared.value(forKey: "startTimeCC") ?? ""
  856. DispatchQueue.global().async {
  857. let date = "\(Date().currentTimeMillis())"
  858. Database.shared.database?.inTransaction({ (fmdb, rollback) in
  859. do {
  860. _ = try Database.shared.insertRecord(fmdb: fmdb, table: "CALL_CENTER_HISTORY", cvalues: [
  861. "type" : "1",
  862. "title" : "Contact Center".localized(),
  863. "time" : startTimeCC,
  864. "f_pin" : officer,
  865. "data" : complaintId,
  866. "time_end" : date,
  867. "complaint_id" : complaintId,
  868. "members" : "",
  869. "requester": requester
  870. ], replace: true)
  871. } catch {
  872. rollback.pointee = true
  873. print("Access database error: \(error.localizedDescription)")
  874. }
  875. })
  876. if officer == idMe {
  877. _ = Nexilis.write(message: CoreMessage_TMessageBank.endCallCenter(complaint_id: complaintId, l_pin: requester))
  878. } else {
  879. if requester == idMe {
  880. _ = Nexilis.write(message: CoreMessage_TMessageBank.endCallCenter(complaint_id: complaintId, l_pin: officer))
  881. } else {
  882. _ = Nexilis.write(message: CoreMessage_TMessageBank.leaveCCRoomInvite(ticket_id: complaintId))
  883. }
  884. }
  885. SecureUserDefaults.shared.removeValue(forKey: "onGoingCC")
  886. SecureUserDefaults.shared.removeValue(forKey: "membersCC")
  887. SecureUserDefaults.shared.removeValue(forKey: "startTimeCC")
  888. SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
  889. }
  890. API.terminateCall(sParty: nil)
  891. }))
  892. self.present(alert, animated: true, completion: nil)
  893. } else {
  894. let controller = self.presentedViewController
  895. if controller != nil {
  896. controller!.dismiss(animated: true)
  897. }
  898. API.terminateCall(sParty: nil)
  899. self.dismiss(animated: false, completion: nil)
  900. }
  901. }
  902. @objc func didReject(sender: Any?) {
  903. if self.timer == nil {
  904. Nexilis.saveMessageCall(idCall: self.idCall, textMessage: "Missed audio call".localized() + " at 0", fPin: !self.data.isEmpty ? self.data : self.user != nil ? self.user!.pin : "", lPin: User.getMyPin() ?? "", timeCall: self.timeStartCall, attachment_type: MessageScope.MISSED_CALL)
  905. }
  906. didEnd(sender: sender)
  907. }
  908. @objc func didAccept(sender: Any?) {
  909. Nexilis.stopRingtoneCall()
  910. NSLayoutConstraint.deactivate(stack.constraints)
  911. stack.subviews.forEach { subview in
  912. subview.removeFromSuperview()
  913. }
  914. ongoingView()
  915. UIView.animate(withDuration: 0.3, animations: {
  916. self.view.layoutIfNeeded()
  917. })
  918. API.receiveCCall(sParty: user?.pin)
  919. }
  920. // MARK: - Communication
  921. @objc func onReceiveMessage(notification: NSNotification) {
  922. DispatchQueue.main.async {
  923. let data:[AnyHashable : Any] = notification.userInfo!
  924. if let dataMessage = data["message"] as? TMessage {
  925. if (dataMessage.getCode() == CoreMessage_TMessageCode.PUSH_MEMBER_ROOM_CONTACT_CENTER) {
  926. let data = dataMessage.getBody(key: CoreMessage_TMessageKey.DATA)
  927. if !data.isEmpty {
  928. if let jsonArray = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [AnyObject] {
  929. var members = ""
  930. let idMe = User.getMyPin()!
  931. for json in jsonArray {
  932. if "\(json)" != idMe {
  933. if members.isEmpty {
  934. members = "\(json)"
  935. } else {
  936. members += ",\(json)"
  937. }
  938. }
  939. }
  940. SecureUserDefaults.shared.set(members, forKey: "inEditorPersonal")
  941. SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
  942. }
  943. }
  944. self.users.append(User.getData(pin: dataMessage.getPIN())!)
  945. }
  946. }
  947. }
  948. }
  949. @objc func onStatusCall(_ notification: NSNotification) {
  950. if let data = notification.userInfo,
  951. let state = data["state"] as? Int,
  952. let message = data["message"] as? String
  953. {
  954. let arrayMessage = message.split(separator: ",")
  955. if state == Nexilis.AUDIO_CALL_INCOMING {
  956. if autoAcceptAPN {
  957. API.receiveCCall(sParty: self.user?.pin)
  958. }
  959. }
  960. else if state == Nexilis.AUDIO_VIDEO_CALL_MUTED {
  961. DispatchQueue.main.async { [self] in
  962. if let pin = arrayMessage.first, let index = users.firstIndex(of: User(pin: String(pin))) {
  963. if arrayMessage[1] == "1" {
  964. users[index].isMuted = true
  965. if let profile = profiles.subviews[index] as? ProfileView {
  966. profile.imageMuted.isHidden = false
  967. }
  968. } else {
  969. users[index].isMuted = false
  970. if let profile = profiles.subviews[index] as? ProfileView {
  971. profile.imageMuted.isHidden = true
  972. }
  973. }
  974. }
  975. }
  976. } else if state == Nexilis.STREAMING_SEMINAR_ENDED { // always call turnspeaker
  977. // QmeraAudioViewController.isLoop = true
  978. // DispatchQueue.global(qos: .userInitiated).async {
  979. // repeat {
  980. // Thread.sleep(forTimeInterval : 1)
  981. // if (QmeraAudioViewController.isLoop && !API.bAudioEngineIsRunning()) {
  982. // API.turnSpeakerPhone(bSPon: QmeraAudioViewController.bSpeakerPhone!)
  983. // }
  984. // } while (QmeraAudioViewController.isLoop)
  985. // }
  986. // DispatchQueue.main.async { [self] in
  987. // QmeraAudioViewController.bSpeakerPhone = true
  988. // self.tempSpeaker = true
  989. // didSpeaker(sender: nil)
  990. // }
  991. } else if state == Nexilis.AUDIO_CALL_RINGING || (!ticketId.isEmpty && state == Nexilis.VIDEO_CALL_RINGING) {
  992. if users.count == 1 && !autoAcceptAPN {
  993. DispatchQueue.main.async {
  994. self.status.text = "Waiting for answer".localized()
  995. }
  996. }
  997. } else if state == Nexilis.AUDIO_CALL_OFFHOOK || (!ticketId.isEmpty && state == Nexilis.VIDEO_CALL_OFFHOOK) {
  998. DispatchQueue.main.async {
  999. Nexilis.stopRingbacktoneCall()
  1000. }
  1001. if users.count == 1 && firstCall {
  1002. DispatchQueue.main.async {
  1003. if !self.ticketId.isEmpty || (self.timer == nil && !self.stack.isDescendant(of: self.view)) {
  1004. NSLayoutConstraint.deactivate(self.stack.constraints)
  1005. self.stack.subviews.forEach { subview in
  1006. subview.removeFromSuperview()
  1007. }
  1008. UIView.animate(withDuration: 0.3, animations: {
  1009. self.view.layoutIfNeeded()
  1010. })
  1011. }
  1012. self.ongoingView()
  1013. let connectDate = Date()
  1014. self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
  1015. let format = Utils.callDurationFormatter.string(from: Date().timeIntervalSince(connectDate))
  1016. self.status.text = format
  1017. }
  1018. self.timer?.fire()
  1019. self.firstCall = false
  1020. }
  1021. }
  1022. if (!isOutgoing || !firstCall), users.count >= 1, let user = User.getData(pin: String(arrayMessage[1])), !users.contains(user) {
  1023. self.users.append(user)
  1024. }
  1025. users.forEach({ $0.isConnected = true })
  1026. } else if state == Nexilis.AUDIO_CALL_END || (!ticketId.isEmpty && state == Nexilis.VIDEO_CALL_END) {
  1027. DispatchQueue.main.async {
  1028. if let sharedAudioPlayer = Nexilis.sharedAudioPlayer, sharedAudioPlayer.isPlaying {
  1029. Nexilis.stopRingtoneCall()
  1030. Nexilis.stopRingbacktoneCall()
  1031. }
  1032. }
  1033. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1034. if let pin = arrayMessage.first, let index = users.firstIndex(of: User(pin: String(pin))) {
  1035. users.remove(at: index)
  1036. if !onGoingCC.isEmpty && users.count != 0 {
  1037. let requester = onGoingCC.components(separatedBy: ",")[0]
  1038. let officer = onGoingCC.isEmpty ? "" : onGoingCC.components(separatedBy: ",")[1]
  1039. if pin == requester || pin == officer {
  1040. DispatchQueue.main.async {
  1041. if !self.end.isEnabled {
  1042. return
  1043. }
  1044. if self.buttonWB.isDescendant(of: self.view){
  1045. self.buttonWB.removeFromSuperview()
  1046. }
  1047. if self.buttonChat.isDescendant(of: self.view){
  1048. self.buttonChat.removeFromSuperview()
  1049. }
  1050. if self.viewIfLoaded?.window != nil {
  1051. let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
  1052. imageView.tintColor = .white
  1053. let banner = FloatingNotificationBanner(title: "Call Center Session has ended".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .info, colors: nil, iconPosition: .center)
  1054. banner.show()
  1055. }
  1056. self.timer?.invalidate()
  1057. self.timer = nil
  1058. self.status.text = "Call Center Session has ended..."
  1059. self.end.isEnabled = false
  1060. }
  1061. QmeraAudioViewController.isLoop = false
  1062. QmeraAudioViewController.bSpeakerPhone = false
  1063. do { try AVAudioSession.sharedInstance().setActive(false) } catch {}
  1064. Nexilis.callAPNActivated = false
  1065. DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  1066. self.didEnd(sender: true)
  1067. }
  1068. return
  1069. }
  1070. } else if !onGoingCC.isEmpty && users.count == 0 {
  1071. DispatchQueue.main.async {
  1072. if !self.end.isEnabled {
  1073. return
  1074. }
  1075. if self.buttonWB.isDescendant(of: self.view){
  1076. self.buttonWB.removeFromSuperview()
  1077. }
  1078. if self.buttonChat.isDescendant(of: self.view){
  1079. self.buttonChat.removeFromSuperview()
  1080. }
  1081. if self.viewIfLoaded?.window != nil {
  1082. let imageView = UIImageView(image: UIImage(systemName: "info.circle"))
  1083. imageView.tintColor = .white
  1084. let banner = FloatingNotificationBanner(title: "Call Center Session has ended".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .info, colors: nil, iconPosition: .center)
  1085. banner.show()
  1086. }
  1087. self.timer?.invalidate()
  1088. self.timer = nil
  1089. self.status.text = "Call Center Session has ended..."
  1090. self.end.isEnabled = false
  1091. }
  1092. QmeraAudioViewController.isLoop = false
  1093. QmeraAudioViewController.bSpeakerPhone = false
  1094. do { try AVAudioSession.sharedInstance().setActive(false) } catch {}
  1095. Nexilis.callAPNActivated = false
  1096. DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  1097. self.didEnd(sender: true)
  1098. }
  1099. return
  1100. } else if users.count == 0 {
  1101. DispatchQueue.main.async {
  1102. if self.isOutgoing {
  1103. let longCall = self.timer == nil ? "0" : self.status.text ?? ""
  1104. Nexilis.saveMessageCall(idCall: self.idCall, textMessage: "Outgoing audio call".localized() + " at \(longCall)", fPin: User.getMyPin() ?? "", lPin: !self.data.isEmpty ? self.data : self.user != nil ? self.user!.pin : "", timeCall: self.timeStartCall, attachment_type: MessageScope.CALL)
  1105. } else {
  1106. if self.timer == nil {
  1107. Nexilis.saveMessageCall(idCall: self.idCall, textMessage: "Missed audio call".localized() + " at 0", fPin: !self.data.isEmpty ? self.data : self.user != nil ? self.user!.pin : "", lPin: User.getMyPin() ?? "", timeCall: self.timeStartCall, attachment_type: MessageScope.MISSED_CALL)
  1108. } else {
  1109. Nexilis.saveMessageCall(idCall: self.idCall, textMessage: "Incoming audio call".localized() + " at \(self.status.text ?? "")", fPin: !self.data.isEmpty ? self.data : self.user != nil ? self.user!.pin : "", lPin: User.getMyPin() ?? "", timeCall: self.timeStartCall, attachment_type: MessageScope.CALL)
  1110. }
  1111. }
  1112. self.timer?.invalidate()
  1113. self.timer = nil
  1114. self.status.text = "Audio Call Ended".localized()
  1115. self.end.isEnabled = false
  1116. self.invite.isEnabled = false
  1117. self.speaker.isEnabled = false
  1118. self.mic.isEnabled = false
  1119. let controller = self.presentedViewController
  1120. if controller != nil {
  1121. controller!.dismiss(animated: true)
  1122. }
  1123. }
  1124. QmeraAudioViewController.isLoop = false
  1125. QmeraAudioViewController.bSpeakerPhone = false
  1126. do { try AVAudioSession.sharedInstance().setActive(false) } catch {}
  1127. Nexilis.callAPNActivated = false
  1128. DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  1129. self.didEnd(sender: true)
  1130. }
  1131. return
  1132. } else if users.count == 1 {
  1133. if !users[0].isConnected{
  1134. DispatchQueue.main.async {
  1135. self.resetViewToOutgoing()
  1136. }
  1137. }
  1138. }
  1139. DispatchQueue.main.async{ [self] in
  1140. if users.count == 1 && !buttonWB.isEnabled {
  1141. buttonWB.isEnabled = true
  1142. }
  1143. }
  1144. }
  1145. } else if state == Nexilis.OFFLINE { // Offline
  1146. DispatchQueue.main.async {
  1147. Nexilis.stopRingtoneCall()
  1148. Nexilis.stopRingbacktoneCall()
  1149. }
  1150. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1151. if let pin = arrayMessage.first, let index = users.firstIndex(of: User(pin: String(pin))) {
  1152. users.remove(at: index)
  1153. if !onGoingCC.isEmpty && users.count != 0 {
  1154. DispatchQueue.main.async {
  1155. var members = ""
  1156. for user in self.users {
  1157. if members.isEmpty {
  1158. members = "\(user.pin)"
  1159. } else {
  1160. members = ",\(user.pin)"
  1161. }
  1162. }
  1163. SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
  1164. }
  1165. }
  1166. DispatchQueue.main.async { [self] in
  1167. if users.count == 1 && !buttonWB.isEnabled {
  1168. buttonWB.isEnabled = true
  1169. }
  1170. }
  1171. }
  1172. if users.count == 0 {
  1173. DispatchQueue.main.async {
  1174. self.status.text = "Offline..."
  1175. self.end.isEnabled = false
  1176. DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  1177. self.didEnd(sender: false)
  1178. }
  1179. }
  1180. }
  1181. } else if state == Nexilis.BUSY { // Busy
  1182. let onGoingCC: String = SecureUserDefaults.shared.value(forKey: "onGoingCC") ?? ""
  1183. if let pin = arrayMessage.first, let index = users.firstIndex(of: User(pin: String(pin))) {
  1184. users.remove(at: index)
  1185. if !onGoingCC.isEmpty && users.count != 0 {
  1186. DispatchQueue.main.async {
  1187. var members = ""
  1188. for user in self.users {
  1189. if members.isEmpty {
  1190. members = "\(user.pin)"
  1191. } else {
  1192. members = ",\(user.pin)"
  1193. }
  1194. }
  1195. SecureUserDefaults.shared.set("\(members)", forKey: "membersCC")
  1196. }
  1197. }
  1198. DispatchQueue.main.async { [self] in
  1199. if users.count == 1 && !buttonWB.isEnabled {
  1200. buttonWB.isEnabled = true
  1201. }
  1202. }
  1203. }
  1204. if users.count == 0 {
  1205. DispatchQueue.main.async {
  1206. let longCall = "0"
  1207. Nexilis.saveMessageCall(idCall: self.idCall, textMessage: "Outgoing audio call".localized() + " at \(longCall)", fPin: User.getMyPin() ?? "", lPin: !self.data.isEmpty ? self.data : self.user != nil ? self.user!.pin : "", timeCall: self.timeStartCall, attachment_type: MessageScope.CALL)
  1208. self.status.text = "Busy..."
  1209. self.end.isEnabled = false
  1210. if self.isOutgoing {
  1211. Nexilis.playBusyCall()
  1212. }
  1213. DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
  1214. self.didEnd(sender: false)
  1215. }
  1216. }
  1217. } else {
  1218. DispatchQueue.main.async {
  1219. Nexilis.stopRingtoneCall()
  1220. Nexilis.stopRingbacktoneCall()
  1221. }
  1222. }
  1223. }
  1224. }
  1225. }
  1226. }
  1227. extension QmeraAudioViewController : WhiteboardReceiver {
  1228. func incomingWB(roomId: String) {
  1229. //print("incoming wb")
  1230. self.wbTimer.invalidate()
  1231. if(wbRoomId.isEmpty){
  1232. //print("wbroom empty")
  1233. DispatchQueue.main.async {
  1234. self.wbTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.runTimer), userInfo: nil, repeats: true)
  1235. }
  1236. let me = User.getMyPin()!
  1237. var destString = ""
  1238. for d in users{
  1239. if d.pin == roomId.components(separatedBy: "wbvc")[0] {
  1240. continue
  1241. }
  1242. if destString.isEmpty{
  1243. destString = d.pin
  1244. } else {
  1245. destString = destString + ",\(d.pin)"
  1246. }
  1247. }
  1248. if destString.isEmpty {
  1249. SecureUserDefaults.shared.set("\(roomId.components(separatedBy: "wbvc")[0]),\(me)", forKey: "wb_vc")
  1250. } else {
  1251. SecureUserDefaults.shared.set("\(roomId.components(separatedBy: "wbvc")[0]),\(me),\(destString)", forKey: "wb_vc")
  1252. }
  1253. wbRoomId = roomId
  1254. }
  1255. }
  1256. func cancel(roomId: String) {
  1257. DispatchQueue.main.async {
  1258. self.wbTimer.invalidate()
  1259. self.wbBlink = false
  1260. self.buttonWB.backgroundColor = .lightGray
  1261. self.buttonWB.setNeedsDisplay()
  1262. }
  1263. wbRoomId = ""
  1264. }
  1265. @objc func runTimer(){
  1266. DispatchQueue.main.async {
  1267. self.wbBlink = !self.wbBlink
  1268. if(self.wbBlink){
  1269. //print("set wb blink on")
  1270. self.buttonWB.backgroundColor = .green
  1271. }
  1272. else {
  1273. //print("set wb blink off")
  1274. self.buttonWB.backgroundColor = .lightGray
  1275. }
  1276. self.buttonWB.setNeedsDisplay()
  1277. }
  1278. }
  1279. }