Browse Source

update fix bugs and release for 5.0.70

alqindiirsyam 12 hours ago
parent
commit
6a1689ceea

+ 2 - 1
AppBuilder/AppBuilder/AppDelegate.swift

@@ -104,7 +104,8 @@ extension AppDelegate: ConnectDelegate, UNUserNotificationCenterDelegate {
     }
     
     func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
-        APIS.showNotificationNexilis(userInfo, completionHandler)
+        APIS.showNotificationNexilis(userInfo)
+        completionHandler(.newData)
     }
     func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
         completionHandler([.banner, .sound, .badge])

+ 7 - 7
AppBuilder/AppBuilder/FourthTabViewController.swift

@@ -274,13 +274,13 @@ public class FourthTabViewController: UIViewController, UITableViewDelegate, UIT
         if isChangeProfile && Nexilis.checkingAccess(key: "can_switch_style") {
             Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "paintbrush"), title: "Switch Style".localized()))
         }
-        if Utils.getIsLoadThemeFromOther() {
-            if Item.menus["Config"]!.count > 0 {
-                Item.menus["Config"]?.insert(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()), at: 1)
-            } else {
-                Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()))
-            }
-        }
+//        if Utils.getIsLoadThemeFromOther() {
+//            if Item.menus["Config"]!.count > 0 {
+//                Item.menus["Config"]?.insert(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()), at: 1)
+//            } else {
+//                Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()))
+//            }
+//        }
         
         Item.menus["Call"] = [
             Item(icon: UIImage(systemName: "message"), title: "Notification Message(s)".localized()),

+ 42 - 65
NexilisLite/NexilisLite/Source/APIS.swift

@@ -1565,7 +1565,7 @@ public class APIS: NSObject {
     
     public static var uuidCall: UUID?
     public static var fpinCall: String?
-    public static func showNotificationNexilis(_ userInfo: [AnyHashable : Any], _ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
+    public static func showNotificationNexilis(_ userInfo: [AnyHashable : Any]) {
         DispatchQueue.main.async {
             if checkAppStateisBackground() {
                 DispatchQueue.global(qos: .userInitiated).async {
@@ -1587,14 +1587,12 @@ public class APIS: NSObject {
                                                 }
                                             })
                                             if messageExist {
-                                                completionHandler(.newData)
                                                 ackAPN(id: idAck)
                                                 return
                                             }
                                         } catch {
                                             print("error saving message: \(error)")
                                         }
-                                        completionHandler(.newData)
                                         APIS.addNotificationNexilis(messageToSave)
                                         ackAPN(id: idAck)
                                         Nexilis.saveMessage(message: messageToSave, withStatus: false, fromAPNS: true)
@@ -1630,7 +1628,6 @@ public class APIS: NSObject {
                                         print("Audio session error: \(error)")
                                     }
                                     Nexilis.playRingtoneCall()
-                                    completionHandler(.newData)
                                 } else if code == "CL02" {
                                     print("data \(data)")
                                     let callFromName = data["call-cancel-name"] as? String ?? ""
@@ -1659,12 +1656,11 @@ public class APIS: NSObject {
                                         }
                                     }
                                     Nexilis.saveMessageCall(idCall: (User.getMyPin() ?? "") + CoreMessage_TMessageUtil.getTID(), textMessage: "Missed \(textCall) call".localized() + " at 0", fPin: callFrom, lPin: (User.getMyPin() ?? ""), timeCall: String(Date().currentTimeMillis()), attachment_type: MessageScope.MISSED_CALL)
-                                    completionHandler(.newData)
                                 }
                             }
                         }
                     } else if let message_id = userInfo["message_id"] as? String {
-                        getMessageById(id: message_id, completionHandler: completionHandler)
+                        getMessageById(id: message_id)
                     }
                 }
             }
@@ -1722,7 +1718,7 @@ public class APIS: NSObject {
     }
     
     static func ackAPN(id: String) {
-        DispatchQueue.global(qos: .userInitiated).async {
+        DispatchQueue.global().async {
 //            Nexilis.sendStateToServer(s: "send ack from apn")
 //            if API.nGetCLXConnState() == 0 {
 //                do {
@@ -1745,7 +1741,7 @@ public class APIS: NSObject {
         }
     }
     
-    private static func getMessageById(id: String, retry: Int = 0, completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
+    private static func getMessageById(id: String, retry: Int = 0) {
 //        if API.nGetCLXConnState() == 0 {
 //            do {
 //                let id = Utils.getConnectionID()
@@ -1776,23 +1772,18 @@ public class APIS: NSObject {
 //                        DispatchQueue.main.async {
 //                            UIApplication.shared.applicationIconBadgeNumber = Int(APIS.getTotalCounter())
 //                        }
-//                        completionHandler(.newData)
 //                    }
 //                }
 //            } else {
 //                let ret = retry + 1
 //                if ret <= 5 {
-//                    getMessageById(id: id, retry: ret, completionHandler: completionHandler)
-//                } else {
-//                    completionHandler(.failed)
+//                    getMessageById(id: id, retry: ret)
 //                }
 //            }
 //        } else {
 //            let ret = retry + 1
 //            if ret <= 5 {
-//                getMessageById(id: id, retry: ret, completionHandler: completionHandler)
-//            } else {
-//                completionHandler(.failed)
+//                getMessageById(id: id, retry: ret)
 //            }
 //        }
         //HTTPS
@@ -1801,64 +1792,50 @@ public class APIS: NSObject {
             "message_id": id
         ]
         Utils.postDataWithCookiesAndUserAgent(from: URL(string: Utils.getDomainOpr() + "pull_notification")!, parameter: parameter, isFormData: true) { data, response, error in
-            if error != nil {
+            if let error = error {
+                print("Error: \(error.localizedDescription)")
                 let ret = retry + 1
                 if ret <= 5 {
-                    getMessageById(id: id, retry: ret, completionHandler: completionHandler)
-                } else {
-                    completionHandler(.failed)
+                    let delay = pow(2.0, Double(ret)) // 2, 4, 8, 16...
+                    DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + delay) {
+                        getMessageById(id: id, retry: ret)
+                    }
                 }
-            } else if let data = data {
-                do {
-                    if let dataString = String(data: data, encoding: .utf8) {
-                        if let jsonObj = try JSONSerialization.jsonObject(with: dataString.data(using: String.Encoding.utf8)!, options: JSONSerialization.ReadingOptions()) as? [String: Any] {
-                            let dataObj = jsonObj["data"] as? String ?? ""
-                            let message = TMessage(data: dataObj)
-                            if Utils.getSecureFolderOffline() == "0" && IncomingThread.dispatch == nil {
-                                if API.nGetCLXConnState() == 0 {
-                                    do {
-                                        let id = Utils.getConnectionID()
-                                        try API.initConnection(sAPIK: Nexilis.sAPIKey, cbiI: Callback(), sTCPAddr: Nexilis.ADDRESS, nTCPPort: Nexilis.PORT, sUserID: id, sStartWH: "09:00")
-                                    } catch {}
-                                }
-                                if FileEncryption.shared.aesKey == nil {
-                                    IncomingThread.dispatch = DispatchGroup()
-                                    IncomingThread.dispatch?.enter()
-                                    Nexilis.getFeatureAccess()
-                                    IncomingThread.dispatch?.wait()
-                                    IncomingThread.dispatch = nil
-                                }
-                            }
-//                                print("save from APIS")
-                            Nexilis.saveMessage(message: message, withStatus: false, fromAPNS: true)
-                            ackAPN(id: id)
-                            DispatchQueue.main.async {
-                                UIApplication.shared.applicationIconBadgeNumber = Int(APIS.getTotalCounter())
-                            }
-                            completionHandler(.newData)
-                        } else {
-                            let ret = retry + 1
-                            if ret <= 5 {
-                                getMessageById(id: id, retry: ret, completionHandler: completionHandler)
-                            } else {
-                                completionHandler(.failed)
-                            }
-                        }
+                return
+            }
+            
+            guard let data = data else {
+                let ret = retry + 1
+                if ret <= 5 {
+                    DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
+                        getMessageById(id: id, retry: ret)
                     }
-                } catch {
-                    let ret = retry + 1
-                    if ret <= 5 {
-                        getMessageById(id: id, retry: ret, completionHandler: completionHandler)
-                    } else {
-                        completionHandler(.failed)
+                }
+                return
+            }
+            
+            do {
+                if let jsonObj = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] {
+                    let dataObj = jsonObj["data"] as? String ?? ""
+                    let message = TMessage(data: dataObj)
+                    
+                    // simpan message
+                    Nexilis.saveMessage(message: message, withStatus: false, fromAPNS: true)
+                    ackAPN(id: id)
+                    
+                    DispatchQueue.main.async {
+                        UIApplication.shared.applicationIconBadgeNumber = Int(APIS.getTotalCounter())
                     }
+                } else {
+                    throw NSError(domain: "Invalid JSON", code: -1)
                 }
-            } else {
+            } catch {
+                print("Parsing error: \(error)")
                 let ret = retry + 1
                 if ret <= 5 {
-                    getMessageById(id: id, retry: ret, completionHandler: completionHandler)
-                } else {
-                    completionHandler(.failed)
+                    DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
+                        getMessageById(id: id, retry: ret)
+                    }
                 }
             }
         }

+ 52 - 27
NexilisLite/NexilisLite/Source/Extension.swift

@@ -1038,6 +1038,10 @@ extension String {
             ("$", [NSAttributedString.Key.font: italicFont,
                    NSAttributedString.Key.foregroundColor: UIColor.darkGray])
         ]
+        
+        if !isEditing {
+            applyParagraphStyles(to: finalText, font: font)
+        }
 
         for (sign, attributes) in rules {
             applyTextFormatting(to: finalText, sign: sign, attributes: attributes, isEditing: isEditing, boldItalicFont: boldItalicFont)
@@ -1045,10 +1049,6 @@ extension String {
         
         processMentions(in: finalText, groupID: group_id, isEditing: isEditing, listMentionInTextField: listMentionInTextField)
         
-        if !isEditing {
-            applyParagraphStyles(to: finalText, font: font)
-        }
-        
         if isSearching {
             highlightSearchText(in: finalText, searchText: textSearch)
         }
@@ -1105,34 +1105,59 @@ extension String {
     }
     
     private func applyParagraphStyles(to text: NSMutableAttributedString, font: UIFont) {
-        let fullString = text.string as NSString
-        let lines = fullString.components(separatedBy: .newlines)
-        var location = 0
-        
-        for line in lines {
-            let nsRange = NSRange(location: location, length: line.count)
-            
-            if line.trimmingCharacters(in: .whitespaces).hasPrefix("•") ||
-                line.trimmingCharacters(in: .whitespaces).range(of: #"^[0-9]+\."#, options: .regularExpression) != nil {
-                
-                let paragraphStyle = NSMutableParagraphStyle()
-                paragraphStyle.lineSpacing = 0
-                paragraphStyle.paragraphSpacing = 0
-                paragraphStyle.firstLineHeadIndent = 0
-                if line.trimmingCharacters(in: .whitespaces).hasPrefix("•") {
-                    paragraphStyle.headIndent = 12 + String.offset() - 1
+        let original = text.string
+        let lines = original.components(separatedBy: .newlines)
+        let result = NSMutableAttributedString()
+
+        // regex untuk deteksi dan menghapus leading space/tab hanya jika diikuti bullet / number.
+        let leadingPattern = #"^[ \t]+(?=(•|[0-9]+\.) )"#
+        let numPattern = #"^[0-9]+\."#
+        let leadingRegex = try? NSRegularExpression(pattern: leadingPattern, options: [])
+        let numRegex = try? NSRegularExpression(pattern: numPattern, options: [])
+
+        for (index, var line) in lines.enumerated() {
+            // jika ada leading spaces/tabs sebelum bullet/number -> hapus
+            if let regex = leadingRegex {
+                let nsLine = line as NSString
+                let fullRange = NSRange(location: 0, length: nsLine.length)
+                if let match = regex.firstMatch(in: line, options: [], range: fullRange) {
+                    line = nsLine.replacingCharacters(in: match.range, with: "")
+                }
+            }
+
+            // cek apakah setelah pembersihan ini baris diawali bullet atau number.
+            let trimmedLeading = line.trimmingCharacters(in: .whitespaces)
+            let paragraphStyle = NSMutableParagraphStyle()
+            paragraphStyle.lineSpacing = 0
+            paragraphStyle.paragraphSpacing = 0
+            paragraphStyle.firstLineHeadIndent = 0
+
+            var attributes: [NSAttributedString.Key: Any] = [
+                .font: font
+            ]
+
+            if trimmedLeading.hasPrefix("•") ||
+                (numRegex?.firstMatch(in: trimmedLeading, options: [], range: NSRange(location: 0, length: (trimmedLeading as NSString).length)) != nil) {
+                // list detected -> atur headIndent sesuai tipe
+                if trimmedLeading.hasPrefix("•") {
+                    paragraphStyle.headIndent = 12 + String.offset() - 3
                 } else {
                     paragraphStyle.headIndent = 12 + String.offset()
                 }
-                
-                text.addAttributes([
-                    .font: font,
-                    .paragraphStyle: paragraphStyle
-                ], range: nsRange)
+                attributes[.paragraphStyle] = paragraphStyle
             }
-            
-            location += line.count + 1 // +1 untuk newline
+
+            // gabungkan baris (ikutkan newline kecuali baris terakhir) dan tambahkan ke result
+            var content = line
+            if index < lines.count - 1 {
+                content += "\n"
+            }
+            let attrLine = NSAttributedString(string: content, attributes: attributes)
+            result.append(attrLine)
         }
+
+        // replace seluruh attributed string dengan yang telah dibangun ulang
+        text.setAttributedString(result)
     }
 
     private func processMentions(in text: NSMutableAttributedString, groupID: String, isEditing: Bool, listMentionInTextField: [User] = []) {

+ 14 - 4
NexilisLite/NexilisLite/Source/View/Chat/CustomTextView.swift

@@ -31,7 +31,7 @@ class CustomTextView: UITextView {
             } else {
                 self.replace(self.textRange(from: range.start, to: range.end)!, withText: " ~\(self.text(in: range)!)~")
             }
-            UIMenuController.shared.isMenuVisible = false
+            UIMenuController.shared.hide(for: self)
         }
     }
 
@@ -43,7 +43,7 @@ class CustomTextView: UITextView {
             } else {
                 self.replace(self.textRange(from: range.start, to: range.end)!, withText: " *\(self.text(in: range)!)*")
             }
-            UIMenuController.shared.isMenuVisible = false
+            UIMenuController.shared.hide(for: self)
         }
     }
     
@@ -55,7 +55,7 @@ class CustomTextView: UITextView {
             } else {
                 self.replace(self.textRange(from: range.start, to: range.end)!, withText: " ^\(self.text(in: range)!)^")
             }
-            UIMenuController.shared.isMenuVisible = false
+            UIMenuController.shared.hide(for: self)
         }
     }
     
@@ -67,7 +67,7 @@ class CustomTextView: UITextView {
             } else {
                 self.replace(self.textRange(from: range.start, to: range.end)!, withText: " _\(self.text(in: range)!)_")
             }
-            UIMenuController.shared.isMenuVisible = false
+            UIMenuController.shared.hide(for: self)
         }
     }
     
@@ -113,3 +113,13 @@ class CustomTextView: UITextView {
     }
 
 }
+
+extension UIMenuController {
+    func hide(for view: UIView) {
+        if #available(iOS 13.0, *) {
+            self.hideMenu(from: view)
+        } else {
+            self.setMenuVisible(false, animated: true)
+        }
+    }
+}

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

@@ -225,13 +225,13 @@ public class SettingTableViewController: UITableViewController, UIGestureRecogni
         if isChangeProfile && Nexilis.checkingAccess(key: "can_switch_style") {
             Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "paintbrush"), title: "Switch Style".localized()))
         }
-        if Utils.getIsLoadThemeFromOther() {
-            if Item.menus["Config"]!.count > 0 {
-                Item.menus["Config"]?.insert(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()), at: 1)
-            } else {
-                Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()))
-            }
-        }
+//        if Utils.getIsLoadThemeFromOther() {
+//            if Item.menus["Config"]!.count > 0 {
+//                Item.menus["Config"]?.insert(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()), at: 1)
+//            } else {
+//                Item.menus["Config"]?.append(Item(icon: UIImage(systemName: "iphone"), title: "Back to Company App".localized()))
+//            }
+//        }
         
         Item.menus["Call"] = [
             Item(icon: UIImage(systemName: "message"), title: "Notification Message(s)".localized()),