Просмотр исходного кода

update nuSDKService-S5-050525a.201-B15.2-T14.0.framework.zip and fix bugs for 5.0.34

alqindiirsyam 3 месяцев назад
Родитель
Сommit
8c3a97e71e
22 измененных файлов с 196 добавлено и 127 удалено
  1. 4 4
      AppBuilder/AppBuilder.xcodeproj/project.pbxproj
  2. 4 2
      AppBuilder/AppBuilder/FourthTabViewController.swift
  3. BIN
      NexilisLite/.DS_Store
  4. 1 1
      NexilisLite/NexilisLite.podspec
  5. 32 20
      NexilisLite/NexilisLite/Source/APIS.swift
  6. 12 6
      NexilisLite/NexilisLite/Source/Extension.swift
  7. 2 1
      NexilisLite/NexilisLite/Source/Nexilis.swift
  8. 4 0
      NexilisLite/NexilisLite/Source/View/Call/CreateConferenceCallController.swift
  9. 28 0
      NexilisLite/NexilisLite/Source/View/Call/QmeraAudioViewController.swift
  10. 32 1
      NexilisLite/NexilisLite/Source/View/Call/QmeraVideoViewController.swift
  11. 12 0
      NexilisLite/NexilisLite/Source/View/Chat/ChatGPTBotView.swift
  12. 24 86
      NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift
  13. 24 0
      NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift
  14. 2 0
      NexilisLite/NexilisLite/Source/View/Chat/EditorStarMessages.swift
  15. 0 1
      NexilisLite/NexilisLite/Source/View/Control/BackupRestoreView.swift
  16. 4 0
      NexilisLite/NexilisLite/Source/View/Control/GroupDetailViewController.swift
  17. 2 2
      NexilisLite/NexilisLite/Source/View/Control/SettingTableViewController.swift
  18. 2 0
      NexilisLite/NexilisLite/Source/View/Streaming/CreateSeminarViewController.swift
  19. 4 0
      NexilisLite/NexilisLite/Source/View/Streaming/QmeraCreateStreamingViewController.swift
  20. 1 1
      NexilisLite/Podfile
  21. 1 1
      StreamShield/Podfile
  22. 1 1
      StreamShield/StreamShield.podspec

+ 4 - 4
AppBuilder/AppBuilder.xcodeproj/project.pbxproj

@@ -560,7 +560,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.29;
+				MARKETING_VERSION = 5.0.34;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -596,7 +596,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 5.0.29;
+				MARKETING_VERSION = 5.0.34;
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
@@ -632,7 +632,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.29;
+				MARKETING_VERSION = 5.0.34;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -671,7 +671,7 @@
 					"@executable_path/../../Frameworks",
 				);
 				LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
-				MARKETING_VERSION = 5.0.29;
+				MARKETING_VERSION = 5.0.34;
 				OTHER_CFLAGS = "-fstack-protector-strong";
 				PRODUCT_BUNDLE_IDENTIFIER = io.nexilis.appbuilder.AppBuilderShare;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 4 - 2
AppBuilder/AppBuilder/FourthTabViewController.swift

@@ -225,7 +225,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                                         
                                     }
                                 } else {
-                                    Download().start(forKey: image!) { (name, progress) in
+                                    Download().startHTTP(forKey: image!) { (name, progress) in
                                         guard progress == 100 else {
                                             return
                                         }
@@ -240,6 +240,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                                                         }
                                                         let image = UIImage(data: data)
                                                         Item.menus["Personal"]?[0].icon = image?.circleMasked
+                                                        self.tableView.reloadData()
                                                         if !imageSignIn.isEmpty {
                                                             var dataImage: [AnyHashable : Any] = [:]
                                                             dataImage["name"] = imageSignIn
@@ -295,7 +296,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                                     
                                 }
                             } else {
-                                Download().start(forKey: imageSignIn) { (name, progress) in
+                                Download().startHTTP(forKey: imageSignIn) { (name, progress) in
                                     guard progress == 100 else {
                                         return
                                     }
@@ -309,6 +310,7 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
                                                     }
                                                     let image = UIImage(data: data)
                                                     Item.menus["Personal"]?[0].icon = image?.circleMasked
+                                                    self.tableView.reloadData()
                                                     if !imageSignIn.isEmpty {
                                                         var dataImage: [AnyHashable : Any] = [:]
                                                         dataImage["name"] = imageSignIn

BIN
NexilisLite/.DS_Store


+ 1 - 1
NexilisLite/NexilisLite.podspec

@@ -24,7 +24,7 @@ Pod::Spec.new do |spec|
   spec.resource_bundles = { 'NexilisLite' => ['NexilisLite/Resource/**/*']}
   spec.swift_version = '5.5.1'
   spec.dependency 'FMDB', '~> 2.7.12'
-  spec.dependency 'nuSDKService', '4.0.21'
+  spec.dependency 'nuSDKService', '4.0.22'
   spec.dependency 'NotificationBannerSwift'
   spec.dependency 'Alamofire', '~> 5.10.2'
   spec.dependency 'SDWebImage', '~> 5.20.0'

+ 32 - 20
NexilisLite/NexilisLite/Source/APIS.swift

@@ -1600,7 +1600,9 @@ public class APIS: NSObject {
             preferredStyle: .alert
         )
         
-        alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: nil))
+        alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { _ in
+            isAlertPresented = false
+        }))
         
         alertController.addAction(UIAlertAction(title: "Go to Settings".localized(), style: .default, handler: { _ in
             isAlertPresented = false
@@ -1614,30 +1616,40 @@ public class APIS: NSObject {
         }
     }
     
+    private static var alertControllerExpired: LibAlertController!
     public static func showExpiredVersion() {
-        let alertController = LibAlertController(
-            title: "Update Available".localized(),
-            message: "A new version is now available. Please update to the latest version to enjoy new features and important improvements.".localized(),
-            preferredStyle: .alert
-        )
-        
-        alertController.addAction(UIAlertAction(title: "Later".localized(), style: .cancel, handler: nil))
-        
-        alertController.addAction(UIAlertAction(title: "Update Now".localized(), style: .default, handler: { _ in
-            if APIS.appNm == "OneApp" {
-                let appStoreURL = URL(string: "https://apps.apple.com/app/id6741251571")!
-                UIApplication.shared.open(appStoreURL)
+        func showAl() {
+            alertControllerExpired = LibAlertController(
+                title: "Update Available".localized(),
+                message: "A new version is now available. Please update to the latest version to enjoy new features and important improvements.".localized(),
+                preferredStyle: .alert
+            )
+            alertControllerExpired.addAction(UIAlertAction(title: "Later".localized(), style: .cancel, handler: nil))
+            
+            alertControllerExpired.addAction(UIAlertAction(title: "Update Now".localized(), style: .default, handler: { _ in
+                if APIS.appNm == "OneApp" {
+                    let appStoreURL = URL(string: "https://apps.apple.com/app/id6741251571")!
+                    UIApplication.shared.open(appStoreURL)
+                } else {
+                    let appStoreURL = URL(string: "https://apps.apple.com/app/")!
+                    UIApplication.shared.open(appStoreURL)
+                }
+            }))
+            
+            if UIApplication.shared.visibleViewController?.navigationController != nil {
+                UIApplication.shared.visibleViewController?.navigationController?.present(alertControllerExpired, animated: true, completion: nil)
             } else {
-                let appStoreURL = URL(string: "https://apps.apple.com/app/")!
-                UIApplication.shared.open(appStoreURL)
+                UIApplication.shared.visibleViewController?.present(alertControllerExpired, animated: true, completion: nil)
+            }
+        }
+        if alertControllerExpired != nil {
+            alertControllerExpired.dismiss(animated: true) {
+                showAl()
             }
-        }))
-        
-        if UIApplication.shared.visibleViewController?.navigationController != nil {
-            UIApplication.shared.visibleViewController?.navigationController?.present(alertController, animated: true, completion: nil)
         } else {
-            UIApplication.shared.visibleViewController?.present(alertController, animated: true, completion: nil)
+            showAl()
         }
+        
     }
     
     private static func openAppSettings() {

+ 12 - 6
NexilisLite/NexilisLite/Source/Extension.swift

@@ -863,15 +863,21 @@ extension String {
             let range = match.range(at: 1)
             let username = (text.string as NSString).substring(with: range)
             if isEditing {
-                if let _ = listMentionInTextField.firstIndex(where: { $0.fullName == username }) {
-                    let fullRange = match.range(at: 0)
-                    text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: fullRange.lowerBound, length: 1))
-                    text.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: range)
-                    text.addAttribute(.font, value: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium), range: fullRange)
+                if let indexLM = listMentionInTextField.firstIndex(where: { $0.fullName.trimmingCharacters(in: .whitespaces).contains(username) }) {
+                    let fullNameText = listMentionInTextField[indexLM].fullName.trimmingCharacters(in: .whitespaces)
+                    let endRange = range.lowerBound + fullNameText.count
+                    if endRange <= text.string.count {
+                        let fullRange = match.range(at: 0)
+                        if fullRange.lowerBound == ((Int(listMentionInTextField[indexLM].ex_block ?? "0") ?? 0) - fullNameText.count){
+                            text.addAttribute(.foregroundColor, value: UIColor.gray, range: NSRange(location: fullRange.lowerBound, length: 1))
+                            text.addAttribute(.foregroundColor, value: UIColor.mentionColor, range: NSRange(location: range.lowerBound, length: fullNameText.count))
+                            text.addAttribute(.font, value: UIFont.systemFont(ofSize: 12 + String.offset(), weight: .medium), range: NSRange(location: fullRange.lowerBound, length: fullNameText.count))
+                        }
+                    }
                 }
             } else {
                 if let member = Member.getMember(f_pin: username) {
-                    let fullName = "\(member.firstName) \(member.lastName)".trimmingCharacters(in: .whitespaces)
+                    let fullName = "\(member.fullName)".trimmingCharacters(in: .whitespaces)
                     text.replaceCharacters(in: range, with: fullName)
                     if !groupID.isEmpty, Member.getMemberInGroup(f_pin: username, group_id: groupID) != nil {
                         let newRange = (text.string as NSString).range(of: "@\(fullName)")

+ 2 - 1
NexilisLite/NexilisLite/Source/Nexilis.swift

@@ -18,7 +18,7 @@ import SDWebImage
 import CryptoKit
 
 public class Nexilis: NSObject {
-    public static var cpaasVersion = "5.0.32"
+    public static var cpaasVersion = "5.0.34"
     public static var sAPIKey = ""
     
     public static var ADDRESS = ""
@@ -128,6 +128,7 @@ public class Nexilis: NSObject {
     private static var ringbacktonePlayer: AVAudioPlayer?
     private static var busyPlayer: AVAudioPlayer?
     static var sharedAudioPlayer: AVAudioPlayer?
+    static var firstCall = true
     
     private func createDelegate() {
         //print("createDelegate...")

+ 4 - 0
NexilisLite/NexilisLite/Source/View/Call/CreateConferenceCallController.swift

@@ -526,13 +526,17 @@ public class CreateConferenceCallController: UITableViewController {
         switch sections[indexPath.section] {
         case .users:
             if (editingStyle == .delete) {
+                tableView.beginUpdates()
                 users.remove(at: indexPath.row - 1)
                 tableView.deleteRows(at: [indexPath], with: .automatic)
+                tableView.endUpdates()
             }
         case .groups:
             if (editingStyle == .delete) {
+                tableView.beginUpdates()
                 groups.remove(at: indexPath.row - 1)
                 tableView.deleteRows(at: [indexPath], with: .automatic)
+                tableView.endUpdates()
             }
         default:
             return

+ 28 - 0
NexilisLite/NexilisLite/Source/View/Call/QmeraAudioViewController.swift

@@ -1085,6 +1085,34 @@ class QmeraAudioViewController: UIViewController {
 //                    self.tempSpeaker = true
 //                    didSpeaker(sender: nil)
 //                }
+                if #available(iOS 15.0, *), Nexilis.firstCall {
+                    DispatchQueue.main.async {
+                        self.speaker.isEnabled = false
+                    }
+                    QmeraAudioViewController.isLoop = true
+                    DispatchQueue.global(qos: .userInitiated).async {
+                        var countLoop = 0
+                        repeat {
+                            Thread.sleep(forTimeInterval : 0.5)
+                            if (QmeraAudioViewController.isLoop && !API.bAudioEngineIsRunning()) {
+                                API.restartAudioEngine()
+                                Nexilis.firstCall = false
+                                DispatchQueue.main.async {
+                                    self.speaker.isEnabled = true
+                                }
+                                break
+                            }
+                            countLoop = countLoop + 1
+                            if countLoop == 3 {
+                                Nexilis.firstCall = false
+                                DispatchQueue.main.async {
+                                    self.speaker.isEnabled = true
+                                }
+                                break
+                            }
+                        } while (QmeraAudioViewController.isLoop)
+                    }
+                }
             } else if state == Nexilis.AUDIO_CALL_RINGING || (!ticketId.isEmpty && state == Nexilis.VIDEO_CALL_RINGING) {
                 if users.count == 1 && !autoAcceptAPN {
                     DispatchQueue.main.async {

+ 32 - 1
NexilisLite/NexilisLite/Source/View/Call/QmeraVideoViewController.swift

@@ -1322,7 +1322,38 @@ class QmeraVideoViewController: UIViewController {
 //                    } while (QmeraVideoViewController.isLoop)
 //                }
 //            })
-            self.setSpeaker()
+            if #available(iOS 15.0, *), Nexilis.firstCall {
+                DispatchQueue.main.async {
+                    self.buttonSpeaker.isEnabled = false
+                }
+                QmeraVideoViewController.isLoop = true
+                DispatchQueue.global(qos: .userInitiated).async {
+                    var countLoop = 0
+                    repeat {
+                        Thread.sleep(forTimeInterval : 1)
+                        if (QmeraVideoViewController.isLoop && !API.bAudioEngineIsRunning()) {
+                            API.restartAudioEngine()
+                            Nexilis.firstCall = false
+                            self.setSpeaker()
+                            DispatchQueue.main.async {
+                                self.buttonSpeaker.isEnabled = true
+                            }
+                            break
+                        }
+                        countLoop = countLoop + 1
+                        if countLoop == 5 {
+                            Nexilis.firstCall = false
+                            self.setSpeaker()
+                            DispatchQueue.main.async {
+                                self.buttonSpeaker.isEnabled = true
+                            }
+                            break
+                        }
+                    } while (QmeraVideoViewController.isLoop)
+                }
+            } else {
+                self.setSpeaker()
+            }
         }
         else if (state == Nexilis.VIDEO_CALL_OFFHOOK) {
             DispatchQueue.main.async {

+ 12 - 0
NexilisLite/NexilisLite/Source/View/Chat/ChatGPTBotView.swift

@@ -259,8 +259,10 @@ public class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
         }
         row["chat_date"] = "Today".localized()
         row["blog_id"] = "0"
+        self.tableChatView.beginUpdates()
         self.dataMessages.append(row)
         self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"] as! String == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
+        self.tableChatView.endUpdates()
         self.tableChatView.scrollToBottom()
     }
     
@@ -340,17 +342,23 @@ public class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
         }
         row["chat_date"] = "Today".localized()
         if loadingResponse {
+            self.tableChatView.beginUpdates()
             dataMessages.insert(row, at: dataMessages.count - 2)
             tableChatView.insertRows(at: [IndexPath(row: dataMessages.filter({ $0["chat_date"] as! String == dataDates[dataDates.count - 1]}).count - 2, section: dataDates.count - 1)], with: .none)
+            self.tableChatView.endUpdates()
         } else {
             row["is_loading"] = false
+            self.tableChatView.beginUpdates()
             dataMessages.append(row)
             tableChatView.insertRows(at: [IndexPath(row: dataMessages.filter({ $0["chat_date"] as! String == dataDates[dataDates.count - 1]}).count - 1, section: dataDates.count - 1)], with: .none)
+            self.tableChatView.endUpdates()
             row["is_loading"] = true
             row["f_pin"] = dataPerson["f_pin"]!!
             row["l_pin"] = idMe
+            self.tableChatView.beginUpdates()
             dataMessages.append(row)
             tableChatView.insertRows(at: [IndexPath(row: dataMessages.filter({ $0["chat_date"] as! String == dataDates[dataDates.count - 1]}).count - 1, section: dataDates.count - 1)], with: .none)
+            self.tableChatView.endUpdates()
         }
         var gptRow : [String: String] = [:]
         gptRow["role"] = row["f_pin"] as! String == "-997" ? "assistant" : "user"
@@ -480,8 +488,10 @@ public class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
                                 row["chat_date"] = "Today".localized()
                                 row["blog_id"] = "0"
                                 self.counter += 1
+                                self.tableChatView.beginUpdates()
                                 self.dataMessages.append(row)
                                 self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"] as! String == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
+                                self.tableChatView.endUpdates()
                                 self.tableChatView.scrollToBottom()
                                 NotificationCenter.default.post(name: NSNotification.Name(rawValue: "reloadTabChats"), object: nil, userInfo: nil)
                                 self.loadingResponse = false
@@ -833,8 +843,10 @@ public class ChatGPTBotView: UIViewController, UIGestureRecognizerDelegate {
                     row["chat_date"] = "Today".localized()
                     row["blog_id"] = chatData[CoreMessage_TMessageKey.BLOG_ID]
                     self.counter += 1
+                    self.tableChatView.beginUpdates()
                     self.dataMessages.append(row)
                     self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"] as! String == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
+                    self.tableChatView.endUpdates()
                     if chatData[CoreMessage_TMessageKey.FORMAT] == "1" {
                         self.sendReadMessageStatus(chat_id: "", f_pin: chatData[CoreMessage_TMessageKey.F_PIN]!, message_scope_id: chatData[CoreMessage_TMessageKey.MESSAGE_SCOPE_ID]!, message_id: chatData[CoreMessage_TMessageKey.MESSAGE_ID]!)
                         self.tableChatView.scrollToBottom()

+ 24 - 86
NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -1133,8 +1133,10 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                         self.listTimerCredential[row["message_id"]  as? String ?? ""] = 60
                     }
                     self.counter += 1
+                    self.tableChatView.beginUpdates()
                     self.dataMessages.append(row)
                     self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"]  as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .fade)
+                    self.tableChatView.endUpdates()
                     if row["credential"] != nil && row["credential"]  as? String ?? "" == "1" {
                         var timer = Timer()
                         var minute = 60
@@ -1838,9 +1840,9 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
                 guard let exBlockStr = mention.ex_block, let exBlock = Int(exBlockStr) else {
                     continue // skip if ex_block is nil or not an integer
                 }
-                let nameWithMention = ("@" + mention.firstName + " " + mention.lastName).trimmingCharacters(in: .whitespaces)
+                let nameWithMention = ("@" + mention.fullName).trimmingCharacters(in: .whitespaces)
                 let pinString = "@\(mention.pin)"
-                let upperBound = exBlock + diff - 1
+                let upperBound = exBlock + diff
                 let lowerBound = upperBound - nameWithMention.count + 1
                 guard lowerBound >= 0, upperBound < message_text.count else {
                     continue // prevent index out-of-range
@@ -1900,8 +1902,10 @@ public class EditorGroup: UIViewController, CLLocationManagerDelegate {
             tableChatView.insertSections(IndexSet(integer: dataDates.count - 1), with: .fade)
         }
         row["chat_date"] = "Today".localized()
+        self.tableChatView.beginUpdates()
         dataMessages.append(row)
         tableChatView.insertRows(at: [IndexPath(row: dataMessages.filter({ $0["chat_date"]  as? String ?? "" == dataDates[dataDates.count - 1]}).count - 1, section: dataDates.count - 1)], with: .fade)
+        self.tableChatView.endUpdates()
         if credential == "1" {
             var timer = Timer()
             var minute = 60
@@ -2513,84 +2517,20 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
     public func textViewDidChangeSelection(_ textView: UITextView) {
         lastPositionCursorMention = textView.selectedRange.location
 
-        if lastPositionCursorMention > 0 {
-            let fulltextForMention = textView.text.prefix(lastPositionCursorMention)
-            var isShowMention = false
-            var listHaveToRemoved: [User] = []
-            var continueCheckMention = true
-
-            for mention in listMentionInTextField where mention.ex_block?.isEmpty == false {
-                let nameWithMention = "@\(mention.firstName) \(mention.lastName)".trimmingCharacters(in: .whitespaces)
-
-                guard let blockPosition = Int(mention.ex_block ?? ""), blockPosition >= 0 else { continue }
-
-                guard !textView.text.isEmpty else { continue }
-
-                let textCount = textView.text.count
-                let lowerOffset = max(0, blockPosition - nameWithMention.count)
-                let upperOffset = min(textCount, blockPosition)
-
-                guard lowerOffset < textCount, upperOffset <= textCount else { continue }
-
-                let lowerBound = textView.text.index(textView.text.startIndex, offsetBy: lowerOffset)
-                let upperBound = textView.text.index(textView.text.startIndex, offsetBy: upperOffset)
-                let range = lowerBound..<upperBound
-                
-                if textView.text[range] == nameWithMention {
-                    if lastPositionCursorMention >= textView.text.distance(from: textView.text.startIndex, to: lowerBound) + 1,
-                       lastPositionCursorMention <= textView.text.distance(from: textView.text.startIndex, to: upperBound) {
-                        continueCheckMention = false
-                        break
-                    }
-                } else {
-                    let exOffmpValue = Int(mention.ex_offmp ?? "") ?? 0
-
-                    let offset = mention.ex_offmp?.isEmpty == false ? listMentionWithText.count - exOffmpValue : listMentionWithText.count
-
-                    let safeOffset = max(-textView.text.distance(from: lowerBound, to: textView.text.endIndex),
-                                         min(offset, textView.text.distance(from: upperBound, to: textView.text.endIndex)))
-
-                    if let adjustedLowerBound = textView.text.index(lowerBound, offsetBy: safeOffset, limitedBy: textView.text.endIndex),
-                       let adjustedUpperBound = textView.text.index(upperBound, offsetBy: safeOffset, limitedBy: textView.text.endIndex) {
-                        let adjustedRange = adjustedLowerBound..<adjustedUpperBound
-                        
-                        if textView.text[adjustedRange] == nameWithMention {
-                            if lastPositionCursorMention >= textView.text.distance(from: textView.text.startIndex, to: adjustedLowerBound) + 1,
-                               lastPositionCursorMention <= textView.text.distance(from: textView.text.startIndex, to: adjustedUpperBound) {
-                                continueCheckMention = false
-                                break
-                            }
-                            mention.ex_block = "\(textView.text.distance(from: textView.text.startIndex, to: adjustedUpperBound))"
-                            mention.ex_offmp = "\(textView.text.count)"
-                        } else {
-                            listHaveToRemoved.append(mention)
-                        }
-                    }
-                }
-            }
-            
-            if continueCheckMention {
-                let lines = fulltextForMention.split(separator: "\n")
-                if let lastLineIndex = lines.lastIndex(where: { !$0.isEmpty }) {
-                    let words = lines[lastLineIndex].split(separator: " ")
-                    if let lastWordIndex = words.lastIndex(where: { !$0.isEmpty }) {
-                        let mentionText = words[lastWordIndex]
-                        let lastChar = fulltextForMention.last
-                        if lastChar != "\n" && lastChar != " " {
-                            if mentionText.starts(with: "@") || (mentionText.count >= 2 && (self.textFieldSend.textColor != UIColor.lightGray || heightTableEditMention != nil)) {
-                                showMention(text: mentionText.starts(with: "@") ? String(mentionText.dropFirst()) : String(mentionText))
-                                isShowMention = true
-                            }
-                        }
+        let fulltextForMention = textView.text.prefix(lastPositionCursorMention)
+        
+        let lines = fulltextForMention.split(separator: "\n")
+        if let lastLineIndex = lines.lastIndex(where: { !$0.isEmpty }) {
+            let words = lines[lastLineIndex].split(separator: " ")
+            if let lastWordIndex = words.lastIndex(where: { !$0.isEmpty }) {
+                let mentionText = words[lastWordIndex]
+                let lastChar = fulltextForMention.last
+                if lastChar != "\n" && lastChar != " " {
+                    if mentionText.starts(with: "@") || (mentionText.count >= 2 && (self.textFieldSend.textColor != UIColor.lightGray || heightTableEditMention != nil)) {
+                        showMention(text: mentionText.starts(with: "@") ? String(mentionText.dropFirst()) : String(mentionText))
                     }
                 }
             }
-            
-            if !isShowMention {
-                hideMention()
-            }
-        } else {
-            hideMention()
         }
 
         var nowTextFieldSend = self.textFieldSend
@@ -2649,7 +2589,6 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
         let cursorPositionIndent = textView.selectedRange.location
 
         // Prevent moving cursor before the 2-space indent
-        let lines = text.components(separatedBy: "\n")
         var adjustedCursorPosition = cursorPositionIndent
         
         for line in lines {
@@ -3019,11 +2958,11 @@ extension EditorGroup: UITextViewDelegate, CustomTextViewPasteDelegate {
         if text.isEmpty {
             if listMentionInTextField.count > 0 {
                 for i in 0..<listMentionInTextField.count {
-                    if lastPositionCursorMention == Int(listMentionInTextField[i].ex_block!)! {
+                    if lastPositionCursorMention == Int(listMentionInTextField[i].ex_block!)! + 1 {
                         let fulltextForMention = textView.text.substring(from: 0, to: lastPositionCursorMention - 1)
                         let diff = textView.text.count - fulltextForMention.count
                         var text = textView.text ?? ""
-                        let nameMention = (listMentionInTextField[i].firstName + " " + listMentionInTextField[i].lastName).trimmingCharacters(in: .whitespaces)
+                        let nameMention = listMentionInTextField[i].fullName.trimmingCharacters(in: .whitespaces)
                         let rangeReplacement = NSRange(location: lastPositionCursorMention - nameMention.count - 1, length: nameMention.count + 1)
                         let replacementText = ""
                         
@@ -3575,7 +3514,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                                 if let range = oldText.range(of: result) {
                                     indexAt = oldText.distance(from: oldText.startIndex, to: range.lowerBound)
                                 }
-                                fixUser?.ex_block = "\(indexAt + fixUser!.fullName.count + 1)"
+                                fixUser?.ex_block = "\(indexAt + fixUser!.fullName.count)"
                                 listMentionWithText.append(fixUser!)
                                 listMentionInTextField.append(fixUser!)
                                 oldTextForTextview = oldTextForTextview.replacingOccurrences(of: result, with: "@\(fixUser!.fullName)")
@@ -3629,7 +3568,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             constraintHeighteditTextView = editTextView.heightAnchor.constraint(equalToConstant: 40)
             constraintBottomeditTextView.isActive = true
             constraintHeighteditTextView.isActive = true
-            editTextView.attributedText = oldTextForTextview.richText(isEditing: true, group_id: self.dataGroup["group_id"]  as? String ?? "")
+            editTextView.attributedText = oldTextForTextview.richText(isEditing: true, group_id: self.dataGroup["group_id"]  as? String ?? "", listMentionInTextField: listMentionInTextField)
             editTextView.becomeFirstResponder()
             
             buttonSendEdit.setImage(resizeImage(image: self.traitCollection.userInterfaceStyle == .dark ? UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(.blackDarkMode) : UIImage(named: "Send-(White)", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal), for: .normal)
@@ -3647,7 +3586,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
                         }
                         let nameWithMention = ("@" + mention.firstName + " " + mention.lastName).trimmingCharacters(in: .whitespaces)
                         let pinString = "@\(mention.pin)"
-                        let upperBound = exBlock + diff - 1
+                        let upperBound = exBlock + diff
                         let lowerBound = upperBound - nameWithMention.count + 1
                         guard lowerBound >= 0, upperBound < newText.count else {
                             continue // prevent index out-of-range
@@ -4472,7 +4411,8 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                         }
                         
                         var text = nowTextField.text ?? ""
-                        let nameMention = (listMentionWithText[indexPath.row].firstName + " " + listMentionWithText[indexPath.row].lastName).trimmingCharacters(in: .whitespaces)
+                        let nameMention = listMentionWithText[indexPath.row].fullName.trimmingCharacters(in: .whitespaces)
+                        listMentionInTextField.last?.ex_block = "\(fulltextForMention.distance(from: fulltextForMention.startIndex, to: rangeLastWord.lowerBound) + nameMention.count)" //upperbound
                         let replacementText = "@\(nameMention)"
                         
                         // Replace the old text with the new text using the replaceSubrange(_:with:) method
@@ -4483,8 +4423,6 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource, AVAudioPlayer
                         let newPosition = nowTextField.position(from: nowTextField.beginningOfDocument, offset: nowTextField.text.count - diff)
                         nowTextField.selectedTextRange = nowTextField.textRange(from: newPosition!, to: newPosition!)
                         
-                        listMentionInTextField.last?.ex_block = "\(nowTextField.text.count - diff - addSpaceAfterReplacement.count)" //upperBound
-                        
                         hideMention()
                         return
                     }

+ 24 - 0
NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -1061,6 +1061,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     if tempData.count != 0 && (dataMessages.firstIndex(where: { $0["message_id"] as? String == tempData[0]["message_id"] as? String }) == nil) {
                         let lastIndex = tempData.count - 1
                         for i in 0..<tempData.count {
+                            self.tableChatView.beginUpdates()
                             dataMessages.insert(tempData[lastIndex - i], at: 0)
                             if dataMessages.firstIndex(where: { $0["chat_date"] as? String == tempData[lastIndex - i]["chat_date"] as? String }) != nil {
                                 tableChatView.insertRows(at: [IndexPath(row: 0, section: currentIndexpath!.section)], with: .top)
@@ -1068,6 +1069,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                                 tableChatView.insertSections(IndexSet(integer: 0), with: .top)
                                 tableChatView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .top)
                             }
+                            self.tableChatView.endUpdates()
                         }
                     }
                     cursorData.close()
@@ -1510,7 +1512,9 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     self.changeAppBar()
                     self.setRightButtonItem()
                     self.dateStartCC = "\(Date().currentTimeMillis())"
+                    self.tableChatView.beginUpdates()
                     self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
+                    self.tableChatView.endUpdates()
                     self.tableChatView.scrollToBottom()
                     SecureUserDefaults.shared.removeValue(forKey: "waitingRequestCC")
                     if dataMessage.getBody(key: CoreMessage_TMessageKey.CHANNEL) != "0" {
@@ -1676,8 +1680,10 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     if row["credential"] != nil && row["credential"]  as? String ?? "" == "1" {
                         self.listTimerCredential[row["message_id"]  as? String ?? ""] = 60
                     }
+                    self.tableChatView.beginUpdates()
                     self.dataMessages.append(row)
                     self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"]  as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
+                    self.tableChatView.endUpdates()
                     if row["credential"] != nil && row["credential"]  as? String ?? "" == "1" {
                         var timer = Timer()
                         var minute = 60
@@ -1952,8 +1958,10 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     self.tableChatView.insertSections(IndexSet(integer: self.dataDates.count - 1), with: .none)
                 }
                 row["chat_date"] = "Today".localized()
+                self.tableChatView.beginUpdates()
                 dataMessages.append(row)
                 self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.filter({ $0["chat_date"]  as? String ?? "" == self.dataDates[self.dataDates.count - 1]}).count - 1, section: self.dataDates.count - 1)], with: .none)
+                self.tableChatView.endUpdates()
                 cursorData.close()
             }
         })
@@ -2748,8 +2756,10 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             tableChatView.insertSections(IndexSet(integer: dataDates.count - 1), with: .none)
         }
         row["chat_date"] = "Today".localized()
+        self.tableChatView.beginUpdates()
         dataMessages.append(row)
         tableChatView.insertRows(at: [IndexPath(row: dataMessages.filter({ $0["chat_date"]  as? String ?? "" == dataDates[dataDates.count - 1]}).count - 1, section: dataDates.count - 1)], with: .none)
+        self.tableChatView.endUpdates()
         if credential == "1" {
             var timer = Timer()
             var minute = 60
@@ -2992,7 +3002,9 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
             if dataMessages[Int(level)!]["attachment_flag"] == nil || dataMessages[Int(level)!]["attachment_flag"]  as? String ?? "" != "503" {
                 dataMessages.append(row)
                 self.nowSelectedCategoryCC = id!
+                self.tableChatView.beginUpdates()
                 tableChatView.insertRows(at: [IndexPath(row: dataMessages.count - 1, section: 0)], with: .none)
+                self.tableChatView.endUpdates()
             }
         } else {
             if id == self.nowSelectedCategoryCC {
@@ -3002,8 +3014,10 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     let categoryCC = dataMessages[dataMessages.count - 2]["category_cc"] as! [CategoryCC]
                     self.nowSelectedCategoryCC = categoryCC[0].parent
                 }
+                self.tableChatView.beginUpdates()
                 tableChatView.deleteRows(at: [IndexPath(row: dataMessages.count - 1, section: 0)], with: .none)
                 dataMessages.remove(at: dataMessages.count - 1)
+                self.tableChatView.endUpdates()
             } else {
                 return
             }
@@ -3078,7 +3092,9 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         row["category_cc"] = "Please wait while we connect you\nto one of our service representatives".localized()
         dataMessages.append(row)
         nowSelectedCategoryCC = "CantReturn"
+        self.tableChatView.beginUpdates()
         tableChatView.insertRows(at: [IndexPath(row: 0, section: 0)], with: .none)
+        self.tableChatView.endUpdates()
         requestContactCenter(channel: Int(channelContactCenter)!, service_id: serviceIdCC, row: row)
     }
     
@@ -3101,8 +3117,10 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         row["message_id"] = ""
         row["chat_date"] = "Today".localized()
         self.nowSelectedCategoryCC = "endCC"
+        self.tableChatView.beginUpdates()
         dataMessages.append(row)
         tableChatView.insertRows(at: [IndexPath(row: Int(level)!, section: 0)], with: .none)
+        self.tableChatView.endUpdates()
         self.tableChatView.scrollToBottom()
 //        DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
 //            self.dismiss(animated: true)
@@ -3182,7 +3200,9 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     DispatchQueue.main.async {
                         self.dataMessages.append(row)
                         self.nowSelectedCategoryCC = "CantReturn"
+                        self.tableChatView.beginUpdates()
                         self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
+                        self.tableChatView.endUpdates()
                         self.tableChatView.scrollToBottom()
                     }
                 }
@@ -3204,7 +3224,9 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                             row["category_cc"] = data
                             self.dataMessages.append(row)
                             self.channelContactCenter = "\(channel)"
+                            self.tableChatView.beginUpdates()
                             self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
+                            self.tableChatView.endUpdates()
                             self.tableChatView.scrollToBottom()
                         } else {
                             self.fPinContacCenter = data
@@ -3225,7 +3247,9 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                         row["category_cc"] = data
                         self.dataMessages.append(row)
                         self.channelContactCenter = "\(channel)"
+                        self.tableChatView.beginUpdates()
                         self.tableChatView.insertRows(at: [IndexPath(row: self.dataMessages.count - 1, section: 0)], with: .none)
+                        self.tableChatView.endUpdates()
                         self.tableChatView.scrollToBottom()
                     }
                 }

+ 2 - 0
NexilisLite/NexilisLite/Source/View/Chat/EditorStarMessages.swift

@@ -1348,12 +1348,14 @@ public class EditorStarMessages: UIViewController, UITableViewDataSource, UITabl
             if idx != nil{
                 self.dataMessages[idx!]["is_stared"] = "0"
             }
+            self.tableChatView.beginUpdates()
             self.dataMessages.remove(at: idx!)
             self.tableChatView.deleteRows(at: [indexPath!], with: .fade)
             if self.dataMessages.filter({ $0["chat_date"] as! String == dataMessages[indexPath!.row]["chat_date"] as! String }).count == 0 {
                 self.dataDates.remove(at: indexPath!.section)
                 self.tableChatView.deleteSections(IndexSet(integer: indexPath!.section), with: .fade)
             }
+            self.tableChatView.endUpdates()
         })
         let forward = UIAction(title: "Forward".localized(), image: UIImage(systemName: "arrowshape.turn.up.right.fill"), handler: {(_) in
             let navigationController = AppStoryBoard.Palio.instance.instantiateViewController(withIdentifier: "contactChatNav") as! UINavigationController

+ 0 - 1
NexilisLite/NexilisLite/Source/View/Control/BackupRestoreView.swift

@@ -111,7 +111,6 @@ public class BackupRestoreView: UIViewController, UITableViewDataSource, UITable
                     
                     valueLastBackup = dayLastBackup.localized() + ", " + timeLastBackup
                     valuesizeBackup = Units(bytes: Int64(filesize)!).getReadableUnit()
-                    
                     tableView.reloadRows(at: [IndexPath(row: 0, section: 0)], with: .none)
                 } else {
                     valueLastBackup = "-"

+ 4 - 0
NexilisLite/NexilisLite/Source/View/Control/GroupDetailViewController.swift

@@ -485,8 +485,10 @@ class GroupDetailViewController: UITableViewController, UITextFieldDelegate {
                                 data["topicId"] = topic.chatId
                                 NotificationCenter.default.post(name: NSNotification.Name(rawValue: "onTopic"), object: nil, userInfo: data)
                                 DispatchQueue.main.async {
+                                    tableView.beginUpdates()
                                     tableView.deleteRows(at: [indexPath], with: .none)
                                     g.topics.remove(at: index)
+                                    tableView.endUpdates()
                                 }
                             }
                         }
@@ -610,8 +612,10 @@ class GroupDetailViewController: UITableViewController, UITextFieldDelegate {
                             self.exitGroup(pin: member.pin) { result in
                                 if result, let index = g.members.firstIndex(of: member) {
                                     DispatchQueue.main.async {
+                                        tableView.beginUpdates()
                                         tableView.deleteRows(at: [indexPath], with: .none)
                                         g.members.remove(at: index)
+                                        tableView.endUpdates()
                                     }
                                 }
                             }

+ 2 - 2
NexilisLite/NexilisLite/Source/View/Control/SettingTableViewController.swift

@@ -189,7 +189,7 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                                             
                                         }
                                     } else {
-                                        Download().start(forKey: image!) { (name, progress) in
+                                        Download().startHTTP(forKey: image!) { (name, progress) in
                                             guard progress == 100 else {
                                                 return
                                             }
@@ -261,7 +261,7 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
                                         
                                     }
                                 } else {
-                                    Download().start(forKey: imageSignIn) { (name, progress) in
+                                    Download().startHTTP(forKey: imageSignIn) { (name, progress) in
                                         guard progress == 100 else {
                                             return
                                         }

+ 2 - 0
NexilisLite/NexilisLite/Source/View/Streaming/CreateSeminarViewController.swift

@@ -493,8 +493,10 @@ public class CreateSeminarViewController: UITableViewController {
         switch sections[indexPath.section] {
         case .participants:
             if (editingStyle == .delete) {
+                tableView.beginUpdates()
                 users.remove(at: indexPath.row - 1)
                 tableView.deleteRows(at: [indexPath], with: .automatic)
+                tableView.endUpdates()
             }
         default:
             return

+ 4 - 0
NexilisLite/NexilisLite/Source/View/Streaming/QmeraCreateStreamingViewController.swift

@@ -573,13 +573,17 @@ public class QmeraCreateStreamingViewController: UITableViewController {
         switch sections[indexPath.section] {
         case .users:
             if (editingStyle == .delete) {
+                tableView.beginUpdates()
                 users.remove(at: indexPath.row - 1)
                 tableView.deleteRows(at: [indexPath], with: .automatic)
+                tableView.endUpdates()
             }
         case .groups:
             if (editingStyle == .delete) {
+                tableView.beginUpdates()
                 groups.remove(at: indexPath.row - 1)
                 tableView.deleteRows(at: [indexPath], with: .automatic)
+                tableView.endUpdates()
             }
         default:
             return

+ 1 - 1
NexilisLite/Podfile

@@ -7,7 +7,7 @@ target 'NexilisLite' do
 
   # Pods for NexilisLite
 
-  pod 'nuSDKService', '4.0.21'
+  pod 'nuSDKService', '4.0.22'
   pod 'FMDB', '~> 2.7.12'
   pod 'NotificationBannerSwift', :git => 'https://github.com/Daltron/NotificationBanner.git', :tag => '4.0.0'
   pod 'Alamofire', '~> 5.10.2'

+ 1 - 1
StreamShield/Podfile

@@ -6,6 +6,6 @@ target 'StreamShield' do
   use_frameworks!
 
   # Pods for StreamShield
-  pod 'nuSDKService', '~> 4.0.21'
+  pod 'nuSDKService', '~> 4.0.22'
 
 end

+ 1 - 1
StreamShield/StreamShield.podspec

@@ -22,7 +22,7 @@ Pod::Spec.new do |spec|
   spec.source_files = 'StreamShield/Source/**/*'
   spec.resource_bundles = { 'StreamShield' => ['StreamShield/Resource/**/*']}
   spec.swift_version = '5.5.1'
-  spec.dependency 'nuSDKService', '~> 4.0.21'
+  spec.dependency 'nuSDKService', '~> 4.0.22'
   spec.ios.vendored_frameworks = "StreamShield.framework"
   spec.pod_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64', 'ENABLE_BITCODE' => 'NO' }
   spec.user_target_xcconfig = { 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'arm64', 'ENABLE_BITCODE' => 'NO' }