// // SecondTabViewController.swift // AppBuilder // // Created by Kevin Maulana on 30/03/22. // import UIKit import FMDB import NexilisLite import Speech import QuickLook import SDWebImage class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGestureRecognizerDelegate, UITextFieldDelegate, UICollectionViewDelegate, UICollectionViewDataSource, QLPreviewControllerDataSource { var isChooser: ((String, String) -> ())? var isAdmin: Bool = false var chats: [Chat] = [] var chatGroupMaps: [String: [Chat]] = [:] var groups: [Group] = [] var cancelSearchButton = UIBarButtonItem() var menuItem = UIBarButtonItem() var menuBroadcast = UIBarButtonItem() var voiceItem = UIBarButtonItem() var childrenMenu = [UIAction]() var groupMap: [String:Int] = [:] var isAllowSpeech = false var alertController = LibAlertController() var noData = false var loadingData = true var waitingLoading = false let textViewSearch = UITextField() let buttonImageVoiceSb = UIButton(type: .custom) let imageNewChatSb = UIImageView() let buttonImageClearSearch = UIButton(type: .custom) @IBOutlet weak var viewToolbar: UIView! @IBOutlet weak var heightToolbar: NSLayoutConstraint! var viewCategorySearch: UIScrollView! var leftTVSearch: NSLayoutConstraint! var viewCatInTV: UIView! var imageViewSearch: UIImageView! var gridImage: UICollectionView! var previewItem: NSURL? var audioPlayer: AVAudioPlayer? let UNREAD_TAG = 10 let PHOTOS_TAG = 11 let DOCUMENTS_TAG = 12 let LINKS_TAG = 13 let VIDEOS_TAG = 14 let GIFS_TAG = 15 let AUDIOS_TAG = 16 var selectedTag = 0 // override var preferredStatusBarStyle: UIStatusBarStyle { // return self.traitCollection.userInterfaceStyle == .dark ? .default : .lightContent // Change this to .default for black text color // } lazy var searchController: UISearchController = { var searchController = UISearchController(searchResultsController: nil) searchController.delegate = self searchController.searchResultsUpdater = self searchController.searchBar.autocapitalizationType = .none searchController.searchBar.delegate = self // searchController.searchBar.setMagnifyingGlassColorTo(color: self.traitCollection.userInterfaceStyle == .dark ? .white : .black) // searchController.searchBar.updateHeight(height: 30, radius: 15, borderColor: UIColor.clear.cgColor) searchController.searchBar.setImage(UIImage(), for: .search, state: .normal) searchController.searchBar.setPositionAdjustment(UIOffset(horizontal: 10, vertical: 0), for: .search) searchController.searchBar.setCustomBackgroundImage(image: UIImage(named: self.traitCollection.userInterfaceStyle == .dark ? "nx_search_bar_dark" : "nx_search_bar", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!) searchController.obscuresBackgroundDuringPresentation = false searchController.searchBar.searchTextField.attributedPlaceholder = NSAttributedString(string: "Search...".localized(), attributes: [NSAttributedString.Key.foregroundColor: UIColor.gray, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 11)]) return searchController }() lazy var segment: UISegmentedControl = { var segment = UISegmentedControl(items: ["Chats".localized(), "Forums".localized()]) segment.sizeToFit() segment.selectedSegmentIndex = 0 segment.addTarget(self, action: #selector(segmentChanged(sender:)), for: .valueChanged) segment.setTitleTextAttributes([NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 12.0 + String.offset() * 0.5)], for: .normal) return segment }() var fillteredData: [Any] = [] var fillteredMessages: [Chat] = [] var isSearchBarEmpty: Bool { return searchController.searchBar.text!.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } var isFiltering: Bool { return !textViewSearch.text!.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty // return !isSearchBarEmpty } @IBOutlet var tableView: UITableView! var speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "id")) var recognitionRequest : SFSpeechAudioBufferRecognitionRequest? var recognitionTask : SFSpeechRecognitionTask? let audioEngine = AVAudioEngine() func filterContentForSearchText(_ searchText: String) { if !searchText.isEmpty { switch segment.selectedSegmentIndex { case 1: fillteredData = self.groups.filter { $0.name.lowercased().contains(searchText.lowercased()) } default: if selectedTag == 0 { var group_id: String? if let filterGroupKey = self.chatGroupMaps.first(where: { $0.value.contains { $0.name.lowercased().contains(searchText.lowercased()) || $0.groupName.lowercased().contains(searchText.lowercased()) } } ) { group_id = filterGroupKey.key } let deepCopyChats = self.chats.map{ $0.copy() } fillteredData = deepCopyChats.filter { $0.name.lowercased().contains(searchText.lowercased()) || $0.messageText.lowercased().contains(searchText.lowercased()) || $0.groupId == group_id } if searchText.count > 1 { // fillteredMessages = Chat.getMessageFromSearch(text: searchText) // print("SEKUTT \(fillteredMessages.count)") } } else { switch(selectedTag) { case UNREAD_TAG : let deepCopyChats = self.chats.map{ $0.copy() } fillteredData = deepCopyChats.filter { $0.counter != "0" } break case PHOTOS_TAG, VIDEOS_TAG, GIFS_TAG : fillteredData = Chat.getData(isImage: selectedTag == PHOTOS_TAG, isVideo: selectedTag == VIDEOS_TAG, isGIF: selectedTag == GIFS_TAG) if fillteredData.count > 0 { if gridImage != nil && gridImage.isDescendant(of: self.view) { gridImage.removeFromSuperview() } let width = self.view.frame.width / 3 - 2 var cellSize = CGSize(width:width, height:width) if selectedTag == GIFS_TAG { cellSize = CGSize(width:self.view.frame.width / 2 - 2, height:self.view.frame.width / 2 - 62) } let layout = UICollectionViewFlowLayout() layout.scrollDirection = .vertical layout.itemSize = cellSize layout.sectionInset = UIEdgeInsets(top: 1, left: 1, bottom: 1, right: 1) layout.minimumLineSpacing = 1.0 layout.minimumInteritemSpacing = 1.0 gridImage = UICollectionView(frame: .zero, collectionViewLayout: layout) self.view.addSubview(gridImage) gridImage.anchor(top: tableView.topAnchor, left: tableView.leftAnchor, bottom: tableView.bottomAnchor, right: tableView.rightAnchor) gridImage.backgroundColor = .clear gridImage.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "gridCell") gridImage.delegate = self gridImage.dataSource = self tableView.isHidden = true } break case DOCUMENTS_TAG : fillteredData = Chat.getData(isDoc: true) break case LINKS_TAG : fillteredData = Chat.getData(isLink: true) break case AUDIOS_TAG : fillteredData = Chat.getData(isAudio: true) break default: break } } } } tableView.reloadData() } override func viewDidLoad() { super.viewDidLoad() let me = User.getMyPin()! Database.shared.database?.inTransaction({ fmdb, rollback in if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select FIRST_NAME, LAST_NAME, IMAGE_ID, USER_TYPE from BUDDY where F_PIN = '\(me)'"), cursor.next() { isAdmin = cursor.string(forColumnIndex: 3) == "23" || cursor.string(forColumnIndex: 3) == "24" cursor.close() } }) // var childrenMenu : [UIAction] = [] // // if(isAdmin){ // childrenMenu.append(UIAction(title: "Broadcast Message".localized(), image: UIImage(systemName: "envelope.open"), handler: {[weak self](_) in // let controller = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "broadcastNav") // self?.navigationController?.present(controller, animated: true, completion: nil) // })) // } // let startConvIcon = resizeImage(image: self.traitCollection.userInterfaceStyle == .dark ? UIImage(systemName: "square.and.pencil")!.withTintColor(.white) : UIImage(systemName: "square.and.pencil")!, targetSize: CGSize(width: 25, height: 25)) // let viewStartConv = UIButton(frame: CGRect(x: 0, y: 0, width: startConvIcon.size.width, height: startConvIcon.size.height)) // viewStartConv.setImage(startConvIcon, for: .normal) // viewStartConv.addTarget(self, action: #selector(startConversation), for: .touchUpInside) // let brodcastIcon = resizeImage(image: self.traitCollection.userInterfaceStyle == .dark ? UIImage(named: "ic_broadcast")!.withTintColor(.white) : UIImage(named: "ic_broadcast")!, targetSize: CGSize(width: 25, height: 25)) // let viewbrodcast = UIButton(frame: CGRect(x: 0, y: 0, width: brodcastIcon.size.width, height: brodcastIcon.size.height)) // viewbrodcast.setImage(brodcastIcon, for: .normal) // viewbrodcast.addTarget(self, action: #selector(openBroadcast), for: .touchUpInside) // // menuItem = UIBarButtonItem(customView: viewStartConv) // menuBroadcast = UIBarButtonItem(customView: viewbrodcast) // menuItem = UIBarButtonItem(image: UIImage(systemName: "square.and.pencil"), style: .plain, target: self, action: #selector(startConversation)) // menuBroadcast = UIBarButtonItem(image: UIImage(systemName: "info.bubble"), style: .plain, target: self, action: #selector(openBroadcast)) // voiceItem = UIBarButtonItem(image: UIImage(systemName: "mic.fill"), style: .plain, target: self, action: #selector(recordAudio)) definesPresentationContext = true NotificationCenter.default.addObserver(self, selector: #selector(onStatusChat(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerStatusChat), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(onReceiveMessage(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerReceiveChat), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(onReload(notification:)), name: NSNotification.Name(rawValue: "onMember"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(onReload(notification:)), name: NSNotification.Name(rawValue: "onUpdatePersonInfo"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(onReloadTab(notification:)), name: NSNotification.Name(rawValue: "onTopic"), object: nil) imageViewSearch = UIImageView(image: UIImage(named: self.traitCollection.userInterfaceStyle == .dark ? "nx_search_bar_dark" : "nx_search_bar", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!) imageViewSearch.contentMode = .scaleToFill viewToolbar.addSubview(imageViewSearch) imageViewSearch.anchor(top: viewToolbar.topAnchor, left: viewToolbar.leftAnchor, right: viewToolbar.rightAnchor, paddingTop: 5, paddingLeft: 10, paddingRight: 10, height: 35) imageViewSearch.isUserInteractionEnabled = true viewCategorySearch = UIScrollView() viewCategorySearch.showsHorizontalScrollIndicator = false viewToolbar.addSubview(viewCategorySearch) viewCategorySearch.anchor(top: imageViewSearch.bottomAnchor, left: viewToolbar.leftAnchor, right: viewToolbar.rightAnchor, paddingLeft: 10, paddingRight: 10, height: 40) viewCategorySearch.isHidden = true let groupViewCat = UIStackView() viewCategorySearch.addSubview(groupViewCat) groupViewCat.anchor(left: viewCategorySearch.leftAnchor, right: viewCategorySearch.rightAnchor, centerY: viewCategorySearch.centerYAnchor, height: 30) groupViewCat.axis = .horizontal groupViewCat.spacing = 10 for i in 0..<7 { var widthView: CGFloat = 105 var iconCat = UIImage(systemName: "bubble.right") var textCat = "Unread".localized() var tag = UNREAD_TAG if i == 1 { widthView = 100 iconCat = UIImage(systemName: "photo") textCat = "Photos".localized() tag = PHOTOS_TAG } else if i == 2 { widthView = 130 iconCat = UIImage(systemName: "doc") textCat = "Documents".localized() tag = DOCUMENTS_TAG } else if i == 3 { widthView = 80 iconCat = UIImage(systemName: "link") textCat = "Links".localized() tag = LINKS_TAG } else if i == 4 { widthView = 100 iconCat = UIImage(systemName: "video") textCat = "Videos".localized() tag = VIDEOS_TAG } else if i == 5 { widthView = 80 iconCat = UIImage(systemName: "photo.on.rectangle") textCat = "GIFs".localized() tag = GIFS_TAG } else if i == 6 { widthView = 80 iconCat = UIImage(systemName: "music.note") textCat = "Audio".localized() tag = AUDIOS_TAG } let viewCat = UIView(frame: CGRect(x: 0, y: 0, width: widthView, height: 30)) groupViewCat.addArrangedSubview(viewCat) viewCat.anchor(width: widthView, height: 30) viewCat.layer.cornerRadius = 15 viewCat.layer.borderColor = UIColor.gray.cgColor viewCat.layer.borderWidth = 0.5 viewCat.backgroundColor = .white viewCat.isUserInteractionEnabled = true viewCat.tag = tag viewCat.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(onTapCategory(_:)))) let imageIcon = UIImageView() imageIcon.image = iconCat imageIcon.tintColor = .black viewCat.addSubview(imageIcon) imageIcon.anchor(left: viewCat.leftAnchor, paddingLeft: 10, centerY: viewCat.centerYAnchor) let imageText = UILabel() imageText.text = textCat viewCat.addSubview(imageText) imageText.anchor(left: imageIcon.rightAnchor, paddingLeft: 5, centerY: viewCat.centerYAnchor) imageText.font = .systemFont(ofSize: 15) } let buttonImageSetting = UIButton(type: .custom) let imageSetting = UIImage(named: "nx_setting_sb", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)! buttonImageSetting.backgroundColor = .clear buttonImageSetting.setImage(imageSetting, for: .normal) imageViewSearch.addSubview(buttonImageSetting) buttonImageSetting.anchor(right: imageViewSearch.rightAnchor, paddingRight: 15, centerY: imageViewSearch.centerYAnchor, width: 20, height: 20) buttonImageSetting.addTarget(self, action: #selector(settingTapped), for: .touchUpInside) let imageVoiceSb = UIImage(named: "nx_mic", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)! buttonImageVoiceSb.setImage(imageVoiceSb, for: .normal) buttonImageVoiceSb.imageView?.contentMode = .scaleAspectFit imageViewSearch.addSubview(buttonImageVoiceSb) buttonImageVoiceSb.anchor(right: buttonImageSetting.leftAnchor, paddingRight: 10, centerY: imageViewSearch.centerYAnchor, width: 20, height: 20) buttonImageVoiceSb.isHidden = true buttonImageVoiceSb.addTarget(self, action: #selector(recordAudio), for: .touchUpInside) let imageClearSearch = UIImage(systemName: "xmark") buttonImageClearSearch.setImage(imageClearSearch, for: .normal) buttonImageClearSearch.imageView?.contentMode = .scaleAspectFit buttonImageClearSearch.tintColor = .gray imageViewSearch.addSubview(buttonImageClearSearch) buttonImageClearSearch.anchor(right: buttonImageVoiceSb.leftAnchor, paddingRight: 5, centerY: imageViewSearch.centerYAnchor, width: 20, height: 20) buttonImageClearSearch.isHidden = true buttonImageClearSearch.addTarget(self, action: #selector(clearSearch), for: .touchUpInside) textViewSearch.placeholder = "Search...".localized() textViewSearch.isUserInteractionEnabled = true imageViewSearch.addSubview(textViewSearch) textViewSearch.font = .systemFont(ofSize: 11 + String.offset()) textViewSearch.anchor(top: imageViewSearch.topAnchor, bottom: imageViewSearch.bottomAnchor, right: imageViewSearch.rightAnchor, paddingTop: 5, paddingBottom: 5, paddingRight: 90) leftTVSearch = textViewSearch.leftAnchor.constraint(equalTo: imageViewSearch.leftAnchor, constant: 20.0) NSLayoutConstraint.activate([ leftTVSearch ]) textViewSearch.delegate = self tableView.tableHeaderView = segment tableView.tableFooterView = UIView() if PrefsUtil.getCpaasMode() == PrefsUtil.CPAAS_MODE_DOCKED { tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 80, right: 0) } pullBuddy() navigationController?.setNavigationBarHidden(true, animated: false) let tapGesture = UITapGestureRecognizer(target: self, action: #selector(collapseDocked)) tapGesture.cancelsTouchesInView = false tapGesture.delegate = self self.view.addGestureRecognizer(tapGesture) } @objc func collapseDocked() { if ViewController.isExpandButton { ViewController.expandButton() } if textViewSearch.isFirstResponder { textViewSearch.resignFirstResponder() } } @objc func openBroadcast() { let controller = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "broadcastNav") controller.modalPresentationStyle = .fullScreen self.navigationController?.present(controller, animated: true, completion: nil) } @objc func startConversation(){ APIS.openChat() } @objc func recordAudio(){ if !isAllowSpeech { setupSpeech() } else { runVoice() } } @objc func clearSearch(){ selectedTag = 0 if gridImage != nil && gridImage.isDescendant(of: self.view) { gridImage.removeFromSuperview() tableView.isHidden = false } textViewSearch.text = "" buttonImageClearSearch.isHidden = true if !textViewSearch.isFirstResponder { viewCategorySearch.isHidden = true buttonImageVoiceSb.isHidden = true heightToolbar.constant = 45 filterContentForSearchText("") } else if heightToolbar.constant == 45 { viewCategorySearch.isHidden = false heightToolbar.constant = 85 } if leftTVSearch.constant != 20 { viewCatInTV.removeFromSuperview() leftTVSearch.constant = 20 } } func setupSpeech() { self.speechRecognizer?.delegate = self SFSpeechRecognizer.requestAuthorization { (authStatus) in var isButtonEnabled = false switch authStatus { case .authorized: isButtonEnabled = true case .denied: isButtonEnabled = false //print("User denied access to speech recognition") case .restricted: isButtonEnabled = false //print("Speech recognition restricted on this device") case .notDetermined: isButtonEnabled = false //print("Speech recognition not yet authorized") @unknown default: isButtonEnabled = false } OperationQueue.main.addOperation() { self.isAllowSpeech = isButtonEnabled if isButtonEnabled { SecureUserDefaults.shared.set(isButtonEnabled, forKey: "allowSpeech") self.runVoice() } } } } func startRecording() { // Clear all previous session data and cancel task if recognitionTask != nil { recognitionTask?.cancel() recognitionTask = nil } // Create instance of audio session to record voice let audioSession = AVAudioSession.sharedInstance() do { try audioSession.setCategory(AVAudioSession.Category.record, mode: AVAudioSession.Mode.measurement, options: AVAudioSession.CategoryOptions.defaultToSpeaker) try audioSession.setActive(true, options: .notifyOthersOnDeactivation) } catch { //print("audioSession properties weren't set because of an error.") } self.recognitionRequest = SFSpeechAudioBufferRecognitionRequest() let inputNode = audioEngine.inputNode guard let recognitionRequest = recognitionRequest else { fatalError("Unable to create an SFSpeechAudioBufferRecognitionRequest object") } recognitionRequest.shouldReportPartialResults = true self.recognitionTask = speechRecognizer?.recognitionTask(with: recognitionRequest, resultHandler: { (result, error) in var isFinal = false if result != nil { self.alertController.dismiss(animated: true) self.audioEngine.stop() self.recognitionRequest?.endAudio() isFinal = (result?.isFinal)! } if error != nil || isFinal { if error == nil { let textRes = result!.bestTranscription.formattedString self.textViewSearch.text = textRes self.filterContentForSearchText(textRes) // self.searchController.searchBar.searchTextField.text = result!.bestTranscription.formattedString // self.updateSearchResults(for: self.searchController) } else { self.audioEngine.stop() self.recognitionRequest?.endAudio() } self.voiceItem.image = UIImage(systemName: "mic.fill") inputNode.removeTap(onBus: 0) self.recognitionRequest = nil self.recognitionTask = nil } }) let recordingFormat = inputNode.outputFormat(forBus: 0) inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer, when) in self.recognitionRequest?.append(buffer) } self.audioEngine.prepare() do { try self.audioEngine.start() } catch { //print("audioEngine couldn't start because of an error.") } } func runVoice() { if !audioEngine.isRunning { self.voiceItem.image = UIImage(systemName: "mic") alertController = LibAlertController(title: "Start Recording".localized(), message: "Say something, I'm listening!".localized(), preferredStyle: .alert) self.present(alertController, animated: true) self.startRecording() } } override func viewDidAppear(_ animated: Bool) { // self.navigationController?.navigationBar.topItem?.title = "Chats".localized() + " & " + "Forums".localized() // self.navigationController?.navigationBar.setNeedsLayout() DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: { var viewController = UIApplication.shared.windows.first!.rootViewController if !(viewController is ViewController) { viewController = self.parent } if ViewController.middleButton.isHidden { ViewController.isExpandButton = false if let viewController = viewController as? ViewController { if viewController.tabBar.isHidden { viewController.tabBar.isHidden = false ViewController.alwaysHideButton = false ViewController.middleButton.isHidden = false } } } else if PrefsUtil.getCpaasMode() != PrefsUtil.CPAAS_MODE_DOCKED { DispatchQueue.main.async { if let viewController = viewController as? ViewController { if viewController.tabBar.isHidden { ViewController.alwaysHideButton = false viewController.tabBar.isHidden = false } } } } }) getData() APIS.setDataForShareExtension() } override func viewWillAppear(_ animated: Bool) { // tabBarController?.navigationItem.leftBarButtonItem = cancelSearchButton let cancelButtonAttributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black, NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16)] UIBarButtonItem.appearance().setTitleTextAttributes(cancelButtonAttributes, for: .normal) let attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.foregroundColor: self.traitCollection.userInterfaceStyle == .dark ? .white : UIColor.black, NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16)] let navBarAppearance = UINavigationBarAppearance() navBarAppearance.configureWithTransparentBackground() navBarAppearance.titleTextAttributes = attributes navigationController?.navigationBar.standardAppearance = navBarAppearance navigationController?.navigationBar.scrollEdgeAppearance = navBarAppearance navigationController?.navigationBar.backgroundColor = .clear navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) navigationController?.navigationBar.shadowImage = UIImage() navigationController?.navigationBar.isTranslucent = true navigationController?.setNavigationBarHidden(true, animated: false) navigationController?.navigationBar.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black navigationController?.navigationBar.overrideUserInterfaceStyle = self.traitCollection.userInterfaceStyle == .dark ? .dark : .light navigationController?.navigationBar.barStyle = .default // tabBarController?.navigationItem.leftBarButtonItem = voiceItem // let myData = User.getData(pin: User.getMyPin()) // if User.isOfficial(official_account: myData?.official ?? "") || User.isOfficialRegular(official_account: myData?.official ?? "") || User.isInternal(userType: myData?.userType ?? "") { // let fixedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil) // fixedSpace.width = 15.0 // tabBarController?.navigationItem.rightBarButtonItems = [menuBroadcast, fixedSpace, menuItem] // } else { // tabBarController?.navigationItem.rightBarButtonItem = menuItem // } // tabBarController?.navigationItem.searchController = searchController let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en" speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: lang ?? "en")) backgroundImage.backgroundColor = self.traitCollection.userInterfaceStyle == .dark ? .black : .white DispatchQueue.global().async { DispatchQueue.main.async { let listBg = PrefsUtil.getBackgroundLight().isEmpty && PrefsUtil.getBackgroundDark().isEmpty ? PrefsUtil.getBackground() : self.traitCollection.userInterfaceStyle == .dark ? PrefsUtil.getBackgroundDark() : PrefsUtil.getBackgroundLight() if listBg.isEmpty { return } var bgChoosen = "" let arrayBg = listBg.split(separator: ",") bgChoosen = String(arrayBg[Int.random(in: 0.. Bool in if Int(a.official) == 1 { return true } else if Int(b.official) == 1 { return false } else { return Int(a.official) ?? 0 > Int(b.official) ?? 0 } } DispatchQueue.main.async { self.tableView.reloadData() } } }) } default: Utils.inTabChats = true // searchController.searchBar.placeholder = "Search chats & messages".localized() // searchController.searchBar.searchTextField.attributedPlaceholder = NSAttributedString(string: "Search chats & messages".localized(), attributes: [NSAttributedString.Key.foregroundColor: UIColor.gray, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)]) } // filterContentForSearchText(searchController.searchBar.text!) filterContentForSearchText(self.textViewSearch.text ?? "") } // MARK: - Data source func getData() { getChats { self.getGroups { g1 in self.groupMap.removeAll() self.groups = g1 self.groups.sort { (a, b) -> Bool in if Int(a.official) == 1 { return true } else if Int(b.official) == 1 { return false } else { return Int(a.official) ?? 0 > Int(b.official) ?? 0 } } DispatchQueue.main.async { self.tableView.reloadData() self.loadingData = false } } } } func getChats(completion: @escaping ()->()) { DispatchQueue.global().async { self.chatGroupMaps.removeAll() let previousChat = self.chats let allChats = Chat.getData() var tempChats: [Chat] = [] for singleChat in allChats { guard !singleChat.groupId.isEmpty else { tempChats.append(singleChat) continue } let chatParentInPreviousChats = previousChat.first { $0.isParent && $0.groupId == singleChat.groupId } if var existingGroup = self.chatGroupMaps[singleChat.groupId] { existingGroup.insert(singleChat, at: 0) self.chatGroupMaps[singleChat.groupId] = existingGroup if let parentChatIndex = tempChats.firstIndex(where: { $0.groupId == singleChat.groupId && $0.isParent }) { if let counterParent = Int(tempChats[parentChatIndex].counter), let counterSingle = Int(singleChat.counter) { tempChats[parentChatIndex].counter = "\(counterParent + counterSingle)" } } if let parentExist = chatParentInPreviousChats, parentExist.isSelected, let indexParent = tempChats.firstIndex(where: { $0.isParent && $0.groupId == singleChat.groupId }) { tempChats.insert(singleChat, at: min(indexParent + existingGroup.count, tempChats.count)) } } else { self.chatGroupMaps[singleChat.groupId] = [singleChat] let parentChat = Chat(profile: singleChat.profile, groupName: singleChat.groupName, counter: singleChat.counter, groupId: singleChat.groupId) parentChat.isParent = true if let parentExist = chatParentInPreviousChats, parentExist.isSelected { parentChat.isSelected = true tempChats.append(parentChat) tempChats.append(singleChat) } else { tempChats.append(parentChat) } } } self.chats = tempChats completion() } } private func getGroupRecursive(fmdb: FMDatabase, id: String = "", parent: String = "") -> [Group] { var data: [Group] = [] var query = "select g.group_id, g.f_name, g.image_id, g.quote, g.created_by, g.created_date, g.parent, g.group_type, g.is_open, g.official, g.is_education, g.level, g.chat_modifier from GROUPZ g where " if id.isEmpty { query += "g.parent = '\(parent)'" } else { query += "g.group_id = '\(id)'" } query += "order by 12 asc, 13 asc, 2 asc" if let cursor = Database.shared.getRecords(fmdb: fmdb, query: query) { while cursor.next() { let group = Group( id: cursor.string(forColumnIndex: 0) ?? "", name: cursor.string(forColumnIndex: 1) ?? "", profile: cursor.string(forColumnIndex: 2) ?? "", quote: cursor.string(forColumnIndex: 3) ?? "", by: cursor.string(forColumnIndex: 4) ?? "", date: cursor.string(forColumnIndex: 5) ?? "", parent: cursor.string(forColumnIndex: 6) ?? "", chatId: "", groupType: cursor.string(forColumnIndex: 7) ?? "", isOpen: cursor.string(forColumnIndex: 8) ?? "", official: cursor.string(forColumnIndex: 9) ?? "", isEducation: cursor.string(forColumnIndex: 10) ?? "", level: cursor.string(forColumnIndex: 11) ?? "") if group.chatId.isEmpty { let lounge = Group(id: group.id, name: "Lounge".localized(), profile: "", quote: group.quote, by: group.by, date: group.date, parent: group.id, chatId: group.chatId, groupType: group.groupType, isOpen: group.isOpen, official: group.official, isEducation: group.isEducation, isLounge: true, level: group.level != "-1" ? group.level : "2") group.childs.append(lounge) } if let topicCursor = Database.shared.getRecords(fmdb: fmdb, query: "select chat_id, title, thumb from DISCUSSION_FORUM where group_id = '\(group.id)'") { while topicCursor.next() { let topic = Group(id: group.id, name: topicCursor.string(forColumnIndex: 1) ?? "", profile: topicCursor.string(forColumnIndex: 2) ?? "", quote: group.quote, by: group.by, date: group.date, parent: group.id, chatId: topicCursor.string(forColumnIndex: 0) ?? "", groupType: group.groupType, isOpen: group.isOpen, official: group.official, isEducation: group.isEducation, level: group.level != "-1" ? group.level : "2") group.childs.append(topic) } topicCursor.close() } if !group.id.isEmpty { // if group.official == "1" { // let idMe = User.getMyPin() as String? // if let cursorUser = Database.shared.getRecords(fmdb: fmdb, query: "SELECT user_type FROM BUDDY where f_pin='\(idMe!)'"), cursorUser.next() { // group.childs.append(contentsOf: getGroupRecursive(fmdb: fmdb, parent: group.id)) // cursorUser.close() // } // } else if group.official != "1"{ // group.childs.append(contentsOf: getGroupRecursive(fmdb: fmdb, parent: group.id)) // } group.childs.append(contentsOf: getGroupRecursive(fmdb: fmdb, parent: group.id)) // group.childs = group.childs.sorted(by: { $0.name < $1.name }) // let dataLounge = group.childs.filter({$0.name == "Lounge".localized()}) // group.childs = group.childs.filter({ $0.name != "Lounge".localized() }) // group.childs.insert(contentsOf: dataLounge, at: 0) } data.append(group) } cursor.close() } return data } private func getOpenGroups(listGroups: [Group], completion: @escaping ([Group]) -> ()) { while Nexilis.isProcessWriteSync { Thread.sleep(forTimeInterval: 0.5) } if let response = Nexilis.writeSync(message: CoreMessage_TMessageBank.getOpenGroups(p_account: "1,2,3,6,5,7", offset: "0", search: "")) { var dataGroups: [Group] = [] if (response.getBody(key: CoreMessage_TMessageKey.ERRCOD, default_value: "99") == "00") { let data = response.getBody(key: CoreMessage_TMessageKey.DATA) if let json = try! JSONSerialization.jsonObject(with: data.data(using: String.Encoding.utf8)!, options: []) as? [[String: Any?]] { for dataJson in json { let group = Group( id: dataJson[CoreMessage_TMessageKey.GROUP_ID] as? String ?? "", name: dataJson[CoreMessage_TMessageKey.GROUP_NAME] as? String ?? "", profile: dataJson[CoreMessage_TMessageKey.THUMB_ID] as? String ?? "", quote: dataJson[CoreMessage_TMessageKey.QUOTE] as? String ?? "", by: dataJson[CoreMessage_TMessageKey.BLOCK] as? String ?? "", date: "", parent: "", chatId: "", groupType: "NOTJOINED", isOpen: dataJson[CoreMessage_TMessageKey.IS_OPEN] as? String ?? "", official: "0", isEducation: "") dataGroups.append(group) } } } completion(dataGroups) } } private func getGroups(id: String = "", parent: String = "", completion: @escaping ([Group]) -> ()) { DispatchQueue.global().async { Database.shared.database?.inTransaction({ fmdb, rollback in completion(self.getGroupRecursive(fmdb: fmdb, id: id, parent: parent)) }) } } private func pullBuddy() { if let me = User.getMyPin() { DispatchQueue.global().async { let _ = Nexilis.write(message: CoreMessage_TMessageBank.getBatchBuddiesInfos(p_f_pin: me, last_update: 0)) } } } private func joinOpenGroup(groupId: String, flagMember: String = "0", completion: @escaping (Bool) -> ()) { DispatchQueue.global().async { var result: Bool = false let idMe = User.getMyPin() as String? if let response = Nexilis.writeAndWait(message: CoreMessage_TMessageBank.getAddGroupMember(p_group_id: groupId, p_member_pin: idMe!, p_position: "0")), response.isOk() { result = true } completion(result) } } @IBOutlet weak var backgroundImage: UIImageView! /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destination. // Pass the selected object to the new view controller. } */ } // MARK: - Table view data source extension SecondTabViewController: UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if noData || (isFiltering && fillteredData.count == 0) { return } tableView.deselectRow(at: indexPath, animated: true) switch segment.selectedSegmentIndex { case 0: let data: Chat if isFiltering { data = fillteredData[indexPath.row] as! Chat } else { data = chats[indexPath.row] } if data.isParent { expandCollapseChats(tableView: tableView, indexPath: indexPath) return } if let chooser = isChooser { if data.pin == "-999"{ return } chooser(data.messageScope, data.pin) dismiss(animated: true, completion: nil) return } if data.pin == "-997" { let smartChatVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "chatGptVC") as! ChatGPTBotView smartChatVC.hidesBottomBarWhenPushed = true smartChatVC.fromNotification = false navigationController?.show(smartChatVC, sender: nil) } else if data.messageScope == "3" { if data.pin.isEmpty { return } let editorPersonalVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorPersonalVC") as! EditorPersonal editorPersonalVC.hidesBottomBarWhenPushed = true editorPersonalVC.unique_l_pin = data.pin navigationController?.show(editorPersonalVC, sender: nil) } else { if data.pin.isEmpty { return } let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup editorGroupVC.hidesBottomBarWhenPushed = true editorGroupVC.unique_l_pin = data.pin navigationController?.show(editorGroupVC, sender: nil) } case 1: expandCollapseGroup(tableView: tableView, indexPath: indexPath) default: expandCollapseGroup(tableView: tableView, indexPath: indexPath) } } func expandCollapseChats(tableView: UITableView, indexPath: IndexPath) { let data: Chat if isFiltering || selectedTag == UNREAD_TAG { data = fillteredData[indexPath.row] as! Chat } else { data = chats[indexPath.row] } data.isSelected = !data.isSelected if data.isSelected { for dataSubChat in self.chatGroupMaps[data.groupId]! { if var indexParent = chats.firstIndex(where: { $0.isParent && $0.groupId == data.groupId }) { if isFiltering || selectedTag == UNREAD_TAG { if var indexParentFilter = fillteredData.firstIndex(where: { ($0 as! Chat).isParent && ($0 as! Chat).groupId == data.groupId }) { fillteredData.insert(dataSubChat, at: indexParentFilter + 1) indexParentFilter+=1 } } else { chats.insert(dataSubChat, at: indexParent + 1) indexParent+=1 } } } } else { if isFiltering || selectedTag == UNREAD_TAG { if var changedFillteredData = fillteredData as? [Chat] { changedFillteredData.removeAll(where: { $0.isParent == false && $0.groupId == data.groupId }) self.fillteredData = changedFillteredData } } else { chats.removeAll(where: { $0.isParent == false && $0.groupId == data.groupId }) } } tableView.reloadData() } func expandCollapseGroup(tableView: UITableView, indexPath: IndexPath) { let group: Group if isFiltering { if indexPath.row == 0 { group = fillteredData[indexPath.section] as! Group } else { if (fillteredData[indexPath.section] as! Group).childs.count > 0 { group = (fillteredData[indexPath.section] as! Group).childs[indexPath.row - 1] } else { return } } } else { if indexPath.row == 0 { group = groups[indexPath.section] } else { group = groups[indexPath.section].childs[indexPath.row - 1] } } if (checkOverrideAction(groupHolder: group)) { return; } group.isSelected = !group.isSelected if !group.isSelected{ var sects = 0 var sect = indexPath.section var id = group.id if let _ = groupMap[id] { var loooop = true repeat { let c = sect + 1 if isFiltering { if let o = self.fillteredData[c] as? Group { if o.parent == id { sects = sects + 1 sect = c id = o.id (self.fillteredData[c] as! Group).isSelected = false self.groupMap.removeValue(forKey: (self.fillteredData[c] as! Group).id) } else { loooop = false } } } else { if c < self.groups.count && self.groups[c].parent == id { sects = sects + 1 sect = c id = self.groups[c].id self.groups[c].isSelected = false self.groupMap.removeValue(forKey: self.groups[c].id) } else { loooop = false } } } while(loooop) } for i in stride(from: sects, to: 0, by: -1){ if isFiltering { self.fillteredData.remove(at: indexPath.section + i) } else { self.groups.remove(at: indexPath.section + i) } } groupMap.removeValue(forKey: group.id) } if group.groupType == "NOTJOINED" { let alert = LibAlertController(title: "Do you want to join this group?".localized(), message: "Groups : \(group.name)\nMembers: \(group.by)".localized(), preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil)) alert.addAction(UIAlertAction(title: "Join".localized(), style: .default, handler: {(_) in self.joinOpenGroup(groupId: group.id, completion: { result in if result { DispatchQueue.main.async { self.groupMap.removeAll() let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup editorGroupVC.hidesBottomBarWhenPushed = true editorGroupVC.unique_l_pin = group.id self.navigationController?.show(editorGroupVC, sender: nil) } } }) })) self.present(alert, animated: true, completion: nil) return } if group.childs.count == 0 { Database.shared.database?.inTransaction({ (fmdb, rollback) in let idMe = User.getMyPin() as String? if let cursorMember = Database.shared.getRecords(fmdb: fmdb, query: "select f_pin from GROUPZ_MEMBER where group_id = '\(group.id)' and f_pin = '\(idMe!)'"), cursorMember.next() { let groupId = group.chatId.isEmpty ? group.id : group.chatId if let chooser = isChooser { chooser("4", groupId) dismiss(animated: true, completion: nil) return } self.groupMap.removeAll() let editorGroupVC = AppStoryBoard.Palio.instance.instantiateViewController(identifier: "editorGroupVC") as! EditorGroup editorGroupVC.hidesBottomBarWhenPushed = true editorGroupVC.unique_l_pin = groupId navigationController?.show(editorGroupVC, sender: nil) cursorMember.close() } else { var viewController = UIApplication.shared.windows.first!.rootViewController if !(viewController is ViewController) { viewController = self.parent } if let viewController = viewController as? ViewController { viewController.view.makeToast("You are not a member of this group".localized(), duration: 3) } } }) } else { if indexPath.row == 0 { tableView.reloadData() } else { getGroups(id: group.id) { g in DispatchQueue.main.async { if self.isFiltering { if self.fillteredData[indexPath.section] is Group { self.groupMap[(self.fillteredData[indexPath.section] as! Group).id] = 1 self.fillteredData.insert(contentsOf: g, at: indexPath.section + 1) } } else { self.groupMap[self.groups[indexPath.section].id] = 1 self.groups.insert(contentsOf: g, at: indexPath.section + 1) } //print("groupMap: \(self.groupMap)") tableView.reloadData() self.expandCollapseGroup(tableView: tableView, indexPath: IndexPath(row: 0, section: indexPath.section + 1)) } } } } } func checkOverrideAction(groupHolder: Group) -> Bool { if groupHolder.isLounge { return false } let groupId = groupHolder.chatId.isEmpty ? groupHolder.id : groupHolder.chatId switch (groupId){ case "18d1c6cffb70215af7b49" //bpkh konsultasi , "18d1c6e37a20215af7b49" , "18d1c6f852d0215af7b49" , "18d1c6ff83a0215af7b49" , "18d1c705e970215af7b49" , "18d30db3bde0230d00c15" //ina konsultasi bot , "18d30e64ce30230d00c15" , "18d30e9b6d80230d00c15" , "18d30ee00610230d00c15" , "18d30f02f850230d00c15": APIS.openSmartChatbot(); return true; case "18d30daa4540230d00c15" //ina cc , "18d30e59a950230d00c15" , "18d30e9292b0230d00c15" , "18d30ed8e250230d00c15" , "18d30efa66c0230d00c15" , "18d35b220540215af7b49" //bpkhcc , "18d35b2f5ee0215af7b49" , "18d35b356530215af7b49" , "18d35b411510215af7b49" , "18d35b46ae90215af7b49": APIS.openContactCenter(); return true; case "18d1c6d9f330215af7b49": //bpkh haji APIS.openUrl(url: Utils.decrypt(str: "6]tov!l_opgn=hgz?ykmgv?yoro3kt?uo>yoro3kt??@yvzzn")) return true; case "18d1c6eefd40215af7b49": //bpkh bpjs APIS.openUrl(url: Utils.decrypt(str: "1>ojq`g@tkqc.cbu:tfhbq:tjmjyfo:pj/tjmjyfo::;tquui")) return true; case "18d30e711c20230d00c15": //ina bpjs APIS.openUrl(url: Utils.decrypt(str: "6]tov!l_ypvh=hgz?ykmgv?yoro3kt?sui>ykrgyomojrkyz??@yvzzn")) return true; case "18d30e47ae60230d00c15": //ina KTP, KK, SKL APIS.openUrl(url: Utils.decrypt(str: "1>ojq`g@qul.cbu:tfhbq:tjmjyfo:npd/tfmbtjhjemftu::;tquui")) return true; case "18d30eb2e910230d00c15": //SIM, SKKB, SKBN APIS.openUrl(url: Utils.decrypt(str: "4[rmt}j]qmw;fex=wiket=wmpm1ir=qsgwtxxl")) return true; case "18da1c0200f0215af7b49": //BPKH index BMI APIS.openUrl(url: Utils.decrypt(str: "4[rmt}j]mqf}1ihrm=mqf=wmpm1ir=smwtxxl")) return true; default: break; } return false } func numberOfSections(in tableView: UITableView) -> Int { if isFiltering { if segment.selectedSegmentIndex == 1 { return fillteredData.count } else if segment.selectedSegmentIndex == 0 && fillteredMessages.count > 0 { return 2 } return 1 } else { if segment.selectedSegmentIndex == 1 { return groups.count } return 1 } } func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { if isFiltering && segment.selectedSegmentIndex == 0 && section == 1 && fillteredMessages.count > 0 { return 20 } return 0.0 } func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let containerView = UIView() containerView.backgroundColor = .clear if isFiltering && segment.selectedSegmentIndex == 0 && section == 1 && fillteredMessages.count > 0 { let titleMessages = UILabel() titleMessages.text = "Messages".localized() titleMessages.font = .systemFont(ofSize: 16, weight: .medium) titleMessages.textColor = .label containerView.addSubview(titleMessages) titleMessages.anchor(left: containerView.leftAnchor, paddingLeft: 10, centerY: containerView.centerYAnchor) } return containerView } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { var value = 0 if isFiltering { if segment.selectedSegmentIndex == 1, let groups = fillteredData as? [Group] { let group = groups[section] if group.isSelected { if let _ = groupMap[group.id] { value = 1 } else { value = group.childs.count + 1 } } else { value = 1 } } else if segment.selectedSegmentIndex == 0 && section == 0 { value = fillteredData.count if value == 0 { value = 1 tableView.separatorStyle = .none } else { tableView.separatorStyle = .singleLine } } else { } return value } switch segment.selectedSegmentIndex { case 0: value = chats.count case 1: let group = groups[section] if group.isSelected { if let _ = groupMap[group.id] { value = 1 } else { value = group.childs.count + 1 } } else { value = 1 } default: value = chats.count } if value == 0 { noData = true value = 1 tableView.separatorStyle = .none } else { noData = false tableView.separatorStyle = .singleLine } return value } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var cell: UITableViewCell! switch segment.selectedSegmentIndex { case 0: cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierChat", for: indexPath) let content = cell.contentView if content.subviews.count > 0 { content.subviews.forEach { $0.removeFromSuperview() } } if noData || (isFiltering && fillteredData.count == 0) { let labelNochat = UILabel() labelNochat.text = isFiltering ? "No Result".localized() : loadingData ? "Loading Data...".localized() : "There are no conversations".localized() labelNochat.font = .systemFont(ofSize: 13 + String.offset()) labelNochat.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black content.addSubview(labelNochat) labelNochat.anchor(centerX: content.centerXAnchor, centerY: content.centerYAnchor) cell.backgroundColor = .clear cell.selectionStyle = .none return cell } let data: Chat if isFiltering { data = fillteredData[indexPath.row] as! Chat } else { if chats.count == 0 || (indexPath.row > (chats.count - 1)) { let labelNochat = UILabel() labelNochat.text = loadingData ? "Loading Data...".localized() : "There are no conversations".localized() labelNochat.font = .systemFont(ofSize: 13 + String.offset()) labelNochat.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black content.addSubview(labelNochat) labelNochat.anchor(centerX: content.centerXAnchor, centerY: content.centerYAnchor) cell.backgroundColor = .clear cell.selectionStyle = .none return cell } data = chats[indexPath.row] } if selectedTag != UNREAD_TAG && selectedTag != 0 { let title = UILabel() let subtitle = UILabel() title.textColor = .black subtitle.textColor = .gray title.font = .systemFont(ofSize: 16 + String.offset(), weight: .medium) subtitle.font = .systemFont(ofSize: 14 + String.offset()) content.addSubview(title) content.addSubview(subtitle) title.anchor(top: content.topAnchor, left: content.leftAnchor, paddingTop: 10, paddingLeft: 20) subtitle.anchor(top: title.bottomAnchor, left: content.leftAnchor, right: content.rightAnchor, paddingLeft: 20, paddingRight: 20) subtitle.numberOfLines = 2 title.text = data.name let imageArrowRight = UIImageView(image: UIImage(systemName: "chevron.right")) content.addSubview(imageArrowRight) imageArrowRight.tintColor = .gray imageArrowRight.anchor(top: content.topAnchor, right: content.rightAnchor, paddingTop: 10, paddingRight: 20) let timeView = UILabel() content.addSubview(timeView) timeView.anchor(top: content.topAnchor, right: imageArrowRight.leftAnchor, paddingTop: 10, paddingRight: 20) timeView.textColor = .gray timeView.font = UIFont.systemFont(ofSize: 16 + String.offset()) let date = Date(milliseconds: Int64(data.serverDate)!) let calendar = Calendar.current if (calendar.isDateInToday(date)) { let formatter = DateFormatter() formatter.dateFormat = "HH:mm" formatter.locale = NSLocale(localeIdentifier: "id") as Locale? timeView.text = formatter.string(from: date as Date) } else { let startOfNow = calendar.startOfDay(for: Date()) let startOfTimeStamp = calendar.startOfDay(for: date) let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp) let day = -(components.day!) if day == 1 { timeView.text = "Yesterday".localized() } else { if day < 7 { let formatter = DateFormatter() formatter.dateFormat = "EEEE" let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en" if lang == "id" { formatter.locale = NSLocale(localeIdentifier: "id") as Locale? } timeView.text = formatter.string(from: date) } else { let formatter = DateFormatter() formatter.dateFormat = "M/dd/yy" formatter.locale = NSLocale(localeIdentifier: "id") as Locale? let stringFormat = formatter.string(from: date as Date) timeView.text = stringFormat } } } cell.separatorInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0) let container = UIView() content.addSubview(container) container.anchor(top: subtitle.bottomAnchor, left: content.leftAnchor, right: content.rightAnchor, paddingTop: 5, paddingLeft: 20, paddingRight: 20, height: selectedTag == LINKS_TAG ? 75 : 60) container.backgroundColor = .lightGray.withAlphaComponent(0.3) container.layer.cornerRadius = 15 container.clipsToBounds = true container.isUserInteractionEnabled = true if selectedTag == DOCUMENTS_TAG { subtitle.text = "📄 " + "Document".localized() let imageFile = UIImageView(image: UIImage(systemName: "doc.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 45))) container.addSubview(imageFile) imageFile.tintColor = .black imageFile.anchor(top: container.topAnchor, left: container.leftAnchor, bottom: container.bottomAnchor, paddingTop: 5, paddingLeft: 5, paddingBottom: 5, width: 45) let nameFile = UILabel() container.addSubview(nameFile) nameFile.font = .systemFont(ofSize: 12 + String.offset(), weight: .medium) nameFile.textColor = .black nameFile.numberOfLines = 2 nameFile.anchor(top: container.topAnchor, left: imageFile.rightAnchor, right: container.rightAnchor, paddingTop: 5, paddingLeft: 10, paddingRight: 5) nameFile.text = data.messageText.components(separatedBy: "|")[0] let fileSub = UILabel() let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true) let arrExtFile = (data.messageText.components(separatedBy: "|")[0]).split(separator: ".") let finalExtFile = arrExtFile[arrExtFile.count - 1] if let dirPath = paths.first { let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(data.file) if FileManager.default.fileExists(atPath: fileURL.path) { if let dataFile = try? Data(contentsOf: fileURL) { var sizeOfFile = Int(dataFile.count / 1000000) if (sizeOfFile < 1) { sizeOfFile = Int(dataFile.count / 1000) if (finalExtFile.count > 4) { fileSub.text = "\(sizeOfFile) kB \u{2022} TXT" }else { fileSub.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())" } } else { if (finalExtFile.count > 4) { fileSub.text = "\(sizeOfFile) MB \u{2022} TXT" }else { fileSub.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())" } } } else { fileSub.text = "" } } else if FileEncryption.shared.isSecureExists(filename: data.file) { if let dataFile = try? FileEncryption.shared.readSecure(filename: data.file) { var sizeOfFile = Int(dataFile.count / 1000000) if (sizeOfFile < 1) { sizeOfFile = Int(dataFile.count / 1000) if (finalExtFile.count > 4) { fileSub.text = "\(sizeOfFile) kB \u{2022} TXT" }else { fileSub.text = "\(sizeOfFile) kB \u{2022} \(finalExtFile.uppercased())" } } else { if (finalExtFile.count > 4) { fileSub.text = "\(sizeOfFile) MB \u{2022} TXT" }else { fileSub.text = "\(sizeOfFile) MB \u{2022} \(finalExtFile.uppercased())" } } } else { fileSub.text = "" } } container.addSubview(fileSub) fileSub.anchor(top: nameFile.bottomAnchor, left: imageFile.rightAnchor, bottom: container.bottomAnchor, paddingLeft: 10, paddingBottom: 5) fileSub.font = .systemFont(ofSize: 10 + String.offset()) fileSub.textColor = .gray let objectTap = ObjectGesture(target: self, action: #selector(onContSearch(_:))) objectTap.file_id = data.file container.addGestureRecognizer(objectTap) } } else if selectedTag == LINKS_TAG { var text = "" var txtData = data.messageText if txtData.contains("■"){ txtData = txtData.components(separatedBy: "■")[0] txtData = txtData.trimmingCharacters(in: .whitespacesAndNewlines) } let listTextSplitBreak = txtData.components(separatedBy: "\n") let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") }) if indexFirstLinkSplitBreak != nil { let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ") let indexFirstLinkSplitSpace = listTextSplitSpace.firstIndex(where: { ($0.starts(with: "www.") && $0.components(separatedBy: ".").count > 2) || ($0.starts(with: "http://") && $0.components(separatedBy: ".").count > 1) || ($0.starts(with: "https://") && $0.components(separatedBy: ".").count > 1) }) if indexFirstLinkSplitSpace != nil { text = listTextSplitSpace[indexFirstLinkSplitSpace!] } } var dataURL = "" subtitle.text = txtData Database.shared.database?.inTransaction({ (fmdb, rollback) in do { if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'"), cursor.next() { if let data = cursor.string(forColumnIndex: 0) { dataURL = data } cursor.close() } } catch { rollback.pointee = true print("Access database error: \(error.localizedDescription)") } }) var title = "" var description = "" var imageUrl: String? var link = text let objectTap = ObjectGesture(target: self, action: #selector(onContSearch(_:))) objectTap.message_id = link container.addGestureRecognizer(objectTap) let imagePreview = UIImageView() container.addSubview(imagePreview) imagePreview.translatesAutoresizingMaskIntoConstraints = false imagePreview.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true imagePreview.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true imagePreview.topAnchor.constraint(equalTo: container.topAnchor).isActive = true imagePreview.widthAnchor.constraint(equalToConstant: 80.0).isActive = true imagePreview.image = UIImage(systemName: "link", withConfiguration: UIImage.SymbolConfiguration(pointSize: 45)) imagePreview.contentMode = .center imagePreview.clipsToBounds = true imagePreview.tintColor = .black imagePreview.backgroundColor = .gray.withAlphaComponent(0.3) let titlePreview = UILabel() container.addSubview(titlePreview) titlePreview.translatesAutoresizingMaskIntoConstraints = false titlePreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true titlePreview.topAnchor.constraint(equalTo: container.topAnchor, constant: 10.0).isActive = true titlePreview.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -5.0).isActive = true titlePreview.text = title titlePreview.font = UIFont.systemFont(ofSize: 14.0 + String.offset(), weight: .bold) titlePreview.textColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black let descPreview = UILabel() container.addSubview(descPreview) descPreview.translatesAutoresizingMaskIntoConstraints = false descPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true descPreview.topAnchor.constraint(equalTo: titlePreview.bottomAnchor).isActive = true descPreview.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -5.0).isActive = true descPreview.text = description descPreview.font = UIFont.systemFont(ofSize: 12.0 + String.offset()) descPreview.textColor = .gray descPreview.numberOfLines = 1 let linkPreview = UILabel() container.addSubview(linkPreview) linkPreview.translatesAutoresizingMaskIntoConstraints = false linkPreview.leadingAnchor.constraint(equalTo: imagePreview.trailingAnchor, constant: 5.0).isActive = true linkPreview.trailingAnchor.constraint(equalTo: container.trailingAnchor, constant: -5.0).isActive = true linkPreview.font = UIFont.systemFont(ofSize: 10.0 + String.offset()) linkPreview.textColor = .gray linkPreview.numberOfLines = 1 if !dataURL.isEmpty { if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] { title = data["title"] as! String description = data["description"] as! String imageUrl = data["imageUrl"] as? String link = data["link"] as! String if imageUrl != nil { imagePreview.loadImageAsync(with: imageUrl) imagePreview.contentMode = .scaleToFill imagePreview.clipsToBounds = true } titlePreview.text = title descPreview.text = description linkPreview.text = link linkPreview.topAnchor.constraint(equalTo: descPreview.bottomAnchor, constant: 8.0).isActive = true } } else { linkPreview.text = link linkPreview.centerYAnchor.constraint(equalTo: container.centerYAnchor).isActive = true } } else if selectedTag == AUDIOS_TAG { subtitle.text = "♫ " + "Audio".localized() let imageAudio = UIImageView() imageAudio.image = UIImage(systemName: "music.note", withConfiguration: UIImage.SymbolConfiguration(pointSize: 35)) container.addSubview(imageAudio) imageAudio.anchor(left: container.leftAnchor, paddingLeft: 10, centerY: container.centerYAnchor) imageAudio.tintColor = .black let nameAudio = UILabel() container.addSubview(nameAudio) nameAudio.anchor(left: imageAudio.rightAnchor, right: container.rightAnchor, paddingLeft: 10, paddingRight: 10, centerY: container.centerYAnchor) nameAudio.numberOfLines = 2 nameAudio.text = data.messageText.components(separatedBy: "|")[0] nameAudio.font = .systemFont(ofSize: 16 + String.offset(), weight: .medium) let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true) if let dirPath = paths.first { let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(data.audio) if !FileManager.default.fileExists(atPath: audioURL.path) && !FileEncryption.shared.isSecureExists(filename: data.audio) { Download().startHTTP(forKey: data.audio, isImage: false) { (name, progress) in guard progress == 100 else { return } tableView.reloadRows(at: [indexPath], with: .none) } } else { let objectTap = ObjectGesture(target: self, action: #selector(onContSearch(_:))) objectTap.audio_id = data.audio container.addGestureRecognizer(objectTap) } } } return cell } let imageView = UIImageView() content.addSubview(imageView) imageView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ imageView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0), imageView.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -10.0), imageView.widthAnchor.constraint(equalToConstant: 55.0), imageView.heightAnchor.constraint(equalToConstant: 55.0) ]) var leadingAnchor = imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 10.0) if data.profile.isEmpty && data.pin != "-999" && data.pin != "-997" { if data.messageScope == "3" { imageView.image = UIImage(named: "Profile---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil) } else { imageView.image = UIImage(named: "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil) } } else if data.pin == "-997" { imageView.frame = CGRect(x: 0, y: 0, width: 55.0, height: 55.0) imageView.circle() if let urlGif = Bundle.resourceBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif") { imageView.sd_setImage(with: urlGif) { (image, error, cacheType, imageURL) in if error == nil { imageView.animationImages = image?.images imageView.animationDuration = image?.duration ?? 0.0 imageView.animationRepeatCount = 0 imageView.startAnimating() } } } else if let urlGif = Bundle.resourcesMediaBundle(for: Nexilis.self).url(forResource: "pb_gpt_bot", withExtension: "gif") { imageView.sd_setImage(with: urlGif) { (image, error, cacheType, imageURL) in if error == nil { imageView.animationImages = image?.images imageView.animationDuration = image?.duration ?? 0.0 imageView.animationRepeatCount = 0 imageView.startAnimating() } } } } else { if !Utils.getIconDock().isEmpty && data.profile.isEmpty { let urlString = Utils.getUrlDock()! if let cachedImage = ImageCache.shared.image(forKey: urlString) { let imageData = cachedImage imageView.image = imageData } else { DispatchQueue.global().async{ Utils.fetchDataWithCookiesAndUserAgent(from: URL(string: urlString)!) { data, response, error in guard let data = data, error == nil else { return } DispatchQueue.main.async() { if UIImage(data: data) != nil { let imageData = UIImage(data: data)! imageView.image = imageData ImageCache.shared.save(image: imageData, forKey: urlString) } } } } } } else { if data.messageScope == "3" || data.isParent || data.pin == "-999" { getImage(name: data.profile, placeholderImage: UIImage(named: data.pin == "-999" ? "pb_button" : data.messageScope == "3" ? "Profile---Purple" : "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in imageView.image = image }) } else { leadingAnchor = imageView.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 40.0) let image = UIImage(named: "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil) imageView.image = image } } } leadingAnchor.isActive = true let titleView = UILabel() content.addSubview(titleView) titleView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ titleView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0), titleView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -40.0), ]) titleView.font = UIFont.systemFont(ofSize: 14 + String.offset(), weight: .medium) let timeView = UILabel() let viewCounter = UIView() if data.counter != "0" { timeView.textColor = .systemRed content.addSubview(viewCounter) viewCounter.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ viewCounter.widthAnchor.constraint(greaterThanOrEqualToConstant: 20), viewCounter.heightAnchor.constraint(equalToConstant: 20) ]) viewCounter.backgroundColor = .systemRed viewCounter.layer.cornerRadius = 10 viewCounter.clipsToBounds = true viewCounter.layer.borderWidth = 0.5 viewCounter.layer.borderColor = UIColor.secondaryColor.cgColor let labelCounter = UILabel() viewCounter.addSubview(labelCounter) labelCounter.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ labelCounter.centerYAnchor.constraint(equalTo: viewCounter.centerYAnchor), labelCounter.leadingAnchor.constraint(equalTo: viewCounter.leadingAnchor, constant: 2), labelCounter.trailingAnchor.constraint(equalTo: viewCounter.trailingAnchor, constant: -2), ]) labelCounter.font = UIFont.systemFont(ofSize: 11 + String.offset()) if Int(data.counter)! > 99 { labelCounter.text = "99+" } else { labelCounter.text = data.counter } labelCounter.textColor = .secondaryColor labelCounter.textAlignment = .center } if !data.isParent { titleView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0).isActive = true titleView.text = data.name content.addSubview(timeView) timeView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ timeView.topAnchor.constraint(equalTo: content.topAnchor, constant: 10.0), timeView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20.0), ]) timeView.textColor = .gray timeView.font = UIFont.systemFont(ofSize: 14 + String.offset()) let date = Date(milliseconds: Int64(data.serverDate) ?? 0) let calendar = Calendar.current if (calendar.isDateInToday(date)) { let formatter = DateFormatter() formatter.dateFormat = "HH:mm" formatter.locale = NSLocale(localeIdentifier: "id") as Locale? timeView.text = formatter.string(from: date as Date) } else { let startOfNow = calendar.startOfDay(for: Date()) let startOfTimeStamp = calendar.startOfDay(for: date) let components = calendar.dateComponents([.day], from: startOfNow, to: startOfTimeStamp) let day = -(components.day!) if day == 1 { timeView.text = "Yesterday".localized() } else { if day < 7 { let formatter = DateFormatter() formatter.dateFormat = "EEEE" let lang: String = SecureUserDefaults.shared.value(forKey: "i18n_language") ?? "en" if lang == "id" { formatter.locale = NSLocale(localeIdentifier: "id") as Locale? } timeView.text = formatter.string(from: date) } else { let formatter = DateFormatter() formatter.dateFormat = "M/dd/yy" formatter.locale = NSLocale(localeIdentifier: "id") as Locale? let stringFormat = formatter.string(from: date as Date) timeView.text = stringFormat } } } let messageView = UILabel() content.addSubview(messageView) messageView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ messageView.leadingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 10.0), messageView.topAnchor.constraint(equalTo: titleView.bottomAnchor), messageView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -40.0), ]) messageView.textColor = .gray if data.messageText.contains("■") { data.messageText = data.messageText.components(separatedBy: "■")[0] data.messageText = data.messageText.trimmingCharacters(in: .whitespacesAndNewlines) } let text = Utils.previewMessageText(chat: data) let idMe = User.getMyPin() as String? if let attributeText = text as? NSMutableAttributedString { let stringMessage = NSMutableAttributedString(string: "") if data.fpin == idMe { if data.lock == "1" { if data.messageScope == "4" { stringMessage.append(NSAttributedString(string: "You".localized() + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium)])) } stringMessage.append(("🚫 _"+"You were deleted this message".localized()+"_").richText()) } else { let imageStatus = NSTextAttachment() let status = getRealStatus(messageId: data.messageId) if status == "0" { imageStatus.image = UIImage(systemName: "xmark.circle")!.withTintColor(UIColor.red, renderingMode: .alwaysOriginal) } else if status == "1" { imageStatus.image = UIImage(systemName: "clock.arrow.circlepath")!.withTintColor(UIColor.lightGray, renderingMode: .alwaysOriginal) } else if status == "2" { imageStatus.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray) } else if (status == "3") { imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray) } else if (status == "8") { imageStatus.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal) } else { imageStatus.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue) } imageStatus.bounds = CGRect(x: 0, y: -5, width: 15, height: 15) let imageStatusString = NSAttributedString(attachment: imageStatus) stringMessage.append(imageStatusString) stringMessage.append(NSAttributedString(string: " ")) if data.messageScope == "4" { stringMessage.append(NSAttributedString(string: "You".localized() + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium)])) } stringMessage.append(attributeText) } } else { if data.messageScope == "4" { var fullname = User.getData(pin: data.fpin, lPin: data.pin)!.fullName let components = fullname.split(separator: " ") if components.count >= 2 { fullname = components.prefix(2).joined(separator: " ") } stringMessage.append(NSAttributedString(string: fullname + ": ", attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium)])) } if data.lock == "1" { stringMessage.append(("🚫 _"+"This message was deleted".localized()+"_").richText()) } else { stringMessage.append(attributeText) } } messageView.attributedText = stringMessage } messageView.numberOfLines = 2 if data.counter != "0" { viewCounter.topAnchor.constraint(equalTo: timeView.bottomAnchor, constant: 5.0).isActive = true viewCounter.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20).isActive = true } } else { titleView.centerYAnchor.constraint(equalTo: content.centerYAnchor).isActive = true titleView.text = data.groupName let iconName = (data.isSelected) ? "chevron.up.circle" : "chevron.down.circle" let imageView = UIImageView(image: UIImage(systemName: iconName)) imageView.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black content.addSubview(imageView) imageView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ imageView.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20), imageView.centerYAnchor.constraint(equalTo: content.centerYAnchor), imageView.widthAnchor.constraint(equalToConstant: 20), imageView.heightAnchor.constraint(equalToConstant: 20) ]) if data.counter != "0" { viewCounter.trailingAnchor.constraint(equalTo: imageView.leadingAnchor, constant: -5).isActive = true viewCounter.centerYAnchor.constraint(equalTo: content.centerYAnchor).isActive = true } } case 1: cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierGroup", for: indexPath) var content = cell.defaultContentConfiguration() content.textProperties.font = UIFont.systemFont(ofSize: 14 + String.offset()) let group: Group if isFiltering { if indexPath.row == 0 { group = fillteredData[indexPath.section] as! Group } else { if (fillteredData[indexPath.section] as! Group).childs.count > 0 { group = (fillteredData[indexPath.section] as! Group).childs[indexPath.row - 1] } else { return cell } } } else { if indexPath.row == 0 { if indexPath.section > (groups.count - 1) { return cell } group = groups[indexPath.section] } else { group = groups[indexPath.section].childs[indexPath.row - 1] } } if group.official == "1" && group.parent == "" { content.attributedText = self.set(image: UIImage(named: "ic_official_flag", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, with: " \(group.name)", size: 15, y: -4, colorText: self.traitCollection.userInterfaceStyle == .dark ? .white : .black) } else if group.isOpen == "1" && group.parent == "" { if self.traitCollection.userInterfaceStyle == .dark { content.attributedText = self.set(image: UIImage(systemName: "globe")!.withTintColor(.white), with: " \(group.name)", size: 15, y: -4, colorText: self.traitCollection.userInterfaceStyle == .dark ? .white : .black) } else { content.attributedText = self.set(image: UIImage(systemName: "globe")!, with: " \(group.name)", size: 15, y: -4, colorText: self.traitCollection.userInterfaceStyle == .dark ? .white : .black) } } else if group.parent == "" { if self.traitCollection.userInterfaceStyle == .dark { content.attributedText = self.set(image: UIImage(systemName: "lock.fill")!.withTintColor(.white), with: " \(group.name)", size: 15, y: -4, colorText: self.traitCollection.userInterfaceStyle == .dark ? .white : .black) } else { content.attributedText = self.set(image: UIImage(systemName: "lock.fill")!, with: " \(group.name)", size: 15, y: -4, colorText: self.traitCollection.userInterfaceStyle == .dark ? .white : .black) } } else { content.text = group.name } if group.childs.count > 0 { let iconName = (group.isSelected) ? "chevron.up.circle" : "chevron.down.circle" let imageView = UIImageView(image: UIImage(systemName: iconName)) imageView.tintColor = self.traitCollection.userInterfaceStyle == .dark ? .white : .black cell.accessoryView = imageView } else { cell.accessoryView = nil cell.accessoryType = .none } content.imageProperties.maximumSize = CGSize(width: 40, height: 40) getImage(name: group.profile, placeholderImage: UIImage(named: "Conversation---Purple", in: Bundle.resourceBundle(for: Nexilis.self), with: nil), isCircle: true, tableView: tableView, indexPath: indexPath) { result, isDownloaded, image in content.image = image } cell.contentConfiguration = content if !group.level.isEmpty { if group.level != "-1" && Int(group.level)! < 7 { cell.contentView.layoutMargins = .init(top: 0.0, left: CGFloat(25 * Int(group.level)!), bottom: 0.0, right: 0) } else if Int(group.level)! > 6 { cell.contentView.layoutMargins = .init(top: 0.0, left: CGFloat(25 * (Int(group.level)! - 6)), bottom: 0.0, right: 0) } } default: cell = tableView.dequeueReusableCell(withIdentifier: "reuseIdentifierContact", for: indexPath) var content = cell.defaultContentConfiguration() content.text = "" cell.contentConfiguration = content } cell.backgroundColor = .clear cell.separatorInset = UIEdgeInsets(top: 0, left: 60.0, bottom: 0, right: 0) return cell } @objc func onContSearch(_ sender: ObjectGesture) { if selectedTag == PHOTOS_TAG { } else if selectedTag == VIDEOS_TAG { } else if selectedTag == DOCUMENTS_TAG { let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true) if let dirPath = paths.first { let fileURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.file_id) if FileManager.default.fileExists(atPath: fileURL.path) { self.previewItem = fileURL as NSURL let previewController = QLPreviewController() let rightBarButton = UIBarButtonItem() previewController.navigationItem.rightBarButtonItem = rightBarButton previewController.dataSource = self previewController.modalPresentationStyle = .custom self.present(previewController, animated: true) } else if FileEncryption.shared.isSecureExists(filename: sender.file_id) { do { if let docData = try FileEncryption.shared.readSecure(filename: sender.file_id) { let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first! let tempPath = cachesDirectory.appendingPathComponent(sender.file_id) try docData.write(to: tempPath) self.previewItem = tempPath as NSURL let previewController = QLPreviewController() let rightBarButton = UIBarButtonItem() previewController.navigationItem.rightBarButtonItem = rightBarButton previewController.dataSource = self previewController.modalPresentationStyle = .custom self.present(previewController,animated: true) } } catch { } } } } else if selectedTag == LINKS_TAG { var stringURl = sender.message_id if stringURl.lowercased().starts(with: "www.") { stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "") } guard let url = URL(string: stringURl) else { return } UIApplication.shared.open(url) } else if selectedTag == AUDIOS_TAG { let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true) if let dirPath = paths.first { let audioURL = URL(fileURLWithPath: dirPath).appendingPathComponent(sender.audio_id) if FileManager.default.fileExists(atPath: audioURL.path) { do { if audioPlayer == nil || audioPlayer?.url != audioURL { do { try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) try AVAudioSession.sharedInstance().setActive(true) } catch { } audioPlayer = try AVAudioPlayer(contentsOf: audioURL) audioPlayer?.prepareToPlay() audioPlayer?.play() } else if audioPlayer!.isPlaying { audioPlayer?.pause() } else { audioPlayer?.play() } } catch { } } else if FileEncryption.shared.isSecureExists(filename: sender.audio_id) { do { if let audioData = try FileEncryption.shared.readSecure(filename: sender.audio_id) { let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first! let tempPath = cachesDirectory.appendingPathComponent(sender.audio_id) try audioData.write(to: tempPath) if audioPlayer == nil || audioPlayer?.url != tempPath { do { try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default) try AVAudioSession.sharedInstance().setActive(true) } catch { } audioPlayer = try AVAudioPlayer(contentsOf: tempPath) audioPlayer?.prepareToPlay() audioPlayer?.play() } else if audioPlayer!.isPlaying { audioPlayer?.pause() } else { audioPlayer?.play() } } } catch { } } } } } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { if fillteredData.count > 0 && selectedTag != UNREAD_TAG && selectedTag != 0 { if selectedTag == LINKS_TAG { return 160.0 } return 130.0 } return 75.0 } private func getRealStatus(messageId: String) -> String { var status = "1" Database.shared.database?.inTransaction({ (fmdb, rollback) in if let cursorStatus = Database.shared.getRecords(fmdb: fmdb, query: "SELECT status, f_pin FROM MESSAGE_STATUS WHERE message_id='\(messageId)'") { var listStatus: [Int] = [] while cursorStatus.next() { listStatus.append(Int(cursorStatus.string(forColumnIndex: 0)!)!) } cursorStatus.close() status = "\(listStatus.min() ?? 2)" } }) return status } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return fillteredData.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "gridCell", for: indexPath) if cell.contentView.subviews.count > 0 { cell.contentView.subviews.forEach { $0.removeFromSuperview() } } let data = fillteredData[indexPath.row] as! Chat let thumb = data.thumb let imgData = data.image let vidData = data.video let gifData = data.gif let image = UIImageView() let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true) if let dirPath = paths.first { if selectedTag == GIFS_TAG { let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(gifData) if !FileManager.default.fileExists(atPath: gifURL.path) && !FileEncryption.shared.isSecureExists(filename: gifData) { Download().startHTTP(forKey: gifData, isImage: false) { (name, progress) in guard progress == 100 else { return } collectionView.reloadItems(at: [indexPath]) } } else { let imageGif = SDAnimatedImageView() cell.contentView.addSubview(imageGif) imageGif.contentMode = .scaleAspectFill imageGif.clipsToBounds = true imageGif.translatesAutoresizingMaskIntoConstraints = false imageGif.anchor(top: cell.contentView.topAnchor, left: cell.contentView.leftAnchor, bottom: cell.contentView.bottomAnchor, right: cell.contentView.rightAnchor) if FileManager.default.fileExists(atPath: gifURL.path) { imageGif.image = SDAnimatedImage(contentsOfFile: gifURL.path) // imageGif.shouldCustomLoopCount = true // imageGif.animationRepeatCount = 4 } else if FileEncryption.shared.isSecureExists(filename: gifData){ do { let data = try FileEncryption.shared.readSecure(filename: gifData) if let imageData = SDAnimatedImage(data: data!) { imageGif.image = imageData // imageGif.shouldCustomLoopCount = true // imageGif.animationRepeatCount = 4 } } catch { print("Error reading secure file") } } } return cell } cell.contentView.addSubview(image) let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumb) let imageT : UIImage? = { if let img = Nexilis.imageCache.object(forKey: thumb as NSString) { return img } else if let img = UIImage(contentsOfFile: thumbURL.path)?.resize(target: CGSize(width: 500, height: 500)) { Nexilis.imageCache.setObject(img, forKey: thumb as NSString) return img } return nil }() if imageT != nil { image.image = imageT } let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imgData) let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(vidData) if (!FileManager.default.fileExists(atPath: imageURL.path) && !FileEncryption.shared.isSecureExists(filename: imageURL.lastPathComponent)) || (!FileManager.default.fileExists(atPath: videoURL.path) && !FileEncryption.shared.isSecureExists(filename: videoURL.lastPathComponent)) { let blurEffect = UIBlurEffect(style: UIBlurEffect.Style.light) let blurEffectView = UIVisualEffectView(effect: blurEffect) blurEffectView.frame = CGRect(x: 0, y: 0, width: image.frame.size.width, height: image.frame.size.height) blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight] image.addSubview(blurEffectView) if !imgData.isEmpty { let imageDownload = UIImageView(image: UIImage(systemName: "arrow.down.circle.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 50, weight: .bold, scale: .default))) image.addSubview(imageDownload) imageDownload.tintColor = .black.withAlphaComponent(0.3) imageDownload.translatesAutoresizingMaskIntoConstraints = false imageDownload.centerXAnchor.constraint(equalTo: image.centerXAnchor).isActive = true imageDownload.centerYAnchor.constraint(equalTo: image.centerYAnchor).isActive = true } } if (!vidData.isEmpty) { let imagePlay = UIImageView(image: UIImage(systemName: "play.fill", withConfiguration: UIImage.SymbolConfiguration(pointSize: 20, weight: .bold, scale: .default))?.imageWithInsets(insets: UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10))?.withTintColor(.white)) imagePlay.circle() image.addSubview(imagePlay) imagePlay.backgroundColor = .black.withAlphaComponent(0.3) imagePlay.translatesAutoresizingMaskIntoConstraints = false imagePlay.centerXAnchor.constraint(equalTo: image.centerXAnchor).isActive = true imagePlay.centerYAnchor.constraint(equalTo: image.centerYAnchor).isActive = true } } image.contentMode = .scaleAspectFill image.clipsToBounds = true image.anchor(top: cell.contentView.topAnchor, left: cell.contentView.leftAnchor, bottom: cell.contentView.bottomAnchor, right: cell.contentView.rightAnchor) return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { let data = fillteredData[indexPath.row] as! Chat let imgData = data.image let vidData = data.video let gifData = data.gif let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true) if let dirPath = paths.first { if selectedTag == PHOTOS_TAG { let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent(imgData) if FileManager.default.fileExists(atPath: imageURL.path) { let image = UIImage(contentsOfFile: imageURL.path) ?? UIImage() APIS.openImageNexilis(image: image) } else if FileEncryption.shared.isSecureExists(filename: imgData) { do { let data = try FileEncryption.shared.readSecure(filename: imgData) let image = UIImage(data: data!) ?? UIImage() APIS.openImageNexilis(image: image) } catch { } } else { Download().startHTTP(forKey: imgData) { (name, progress) in guard progress == 100 else { return } DispatchQueue.main.async { collectionView.reloadItems(at: [indexPath]) } } } } else if selectedTag == VIDEOS_TAG { let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(vidData) if FileManager.default.fileExists(atPath: videoURL.path) { APIS.openVideoNexilis(videoURL: videoURL) } else if FileEncryption.shared.isSecureExists(filename: vidData) { do { if let secureData = try FileEncryption.shared.readSecure(filename: vidData) { let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first! let tempPath = cachesDirectory.appendingPathComponent(vidData) try secureData.write(to: tempPath) APIS.openVideoNexilis(videoURL: tempPath) } } catch { } } else { Download().startHTTP(forKey: vidData, isImage: false) { (name, progress) in guard progress == 100 else { return } DispatchQueue.main.async { collectionView.reloadItems(at: [indexPath]) } } } } else { let gifURL = URL(fileURLWithPath: dirPath).appendingPathComponent(gifData) if FileManager.default.fileExists(atPath: gifURL.path) { do { let data = try Data(contentsOf: gifURL) APIS.openImageNexilis(image: UIImage(), data: data, isGIF: true) } catch { } } else if FileEncryption.shared.isSecureExists(filename: gifData) { do { if let secureData = try FileEncryption.shared.readSecure(filename: gifData) { APIS.openImageNexilis(image: UIImage(), data: secureData, isGIF: true) } } catch { } } } } } func numberOfPreviewItems(in controller: QLPreviewController) -> Int { return self.previewItem != nil ? 1 : 0 } func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> any QLPreviewItem { return self.previewItem! } } extension SecondTabViewController: UISearchControllerDelegate, UISearchBarDelegate, UISearchResultsUpdating { func updateSearchResults(for searchController: UISearchController) { filterContentForSearchText(searchController.searchBar.text!.trimmingCharacters(in: .whitespacesAndNewlines)) } func searchBarBookmarkButtonClicked(_ searchBar: UISearchBar) { recordAudio() } func set(image: UIImage, with text: String, size: CGFloat, y: CGFloat, colorText: UIColor = UIColor.black) -> NSAttributedString { let attachment = NSTextAttachment() attachment.image = image attachment.bounds = CGRect(x: 0, y: y, width: size, height: size) let attachmentStr = NSAttributedString(attachment: attachment) let mutableAttributedString = NSMutableAttributedString() mutableAttributedString.append(attachmentStr) let attributedStringColor = [NSAttributedString.Key.foregroundColor : colorText] let textString = NSAttributedString(string: text, attributes: attributedStringColor) mutableAttributedString.append(textString) return mutableAttributedString } func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { searchBar.showsCancelButton = true let cBtn = searchBar.value(forKey: "cancelButton") as! UIButton cBtn.setTitle("Cancel".localized(), for: .normal) } func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { searchBar.showsCancelButton = false } } extension SecondTabViewController: SFSpeechRecognizerDelegate { func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) { if available { self.isAllowSpeech = true } else { self.isAllowSpeech = false } } }