Kaynağa Gözat

add action in view grouping image

alqindiirsyam 2 yıl önce
ebeveyn
işleme
76750085f5

+ 3 - 2
appbuilder-ios/DigiXLite/DigiXLite.podspec

@@ -8,7 +8,7 @@
 
 Pod::Spec.new do |spec|
   spec.name         = "DigiXLite"
-  spec.version      = "2.2.3"
+  spec.version      = "2.2.1"
   spec.summary      = "DigiXLite Framework"
   spec.description  = <<-DESC
   DigiXLite Framework, embed Contact Center, Live Streaming, Push Notifications, Instant Messaging, Video and VoIP Calling features into your mobile apps within minutes...
@@ -18,7 +18,7 @@ Pod::Spec.new do |spec|
   spec.license      = "MIT"
   spec.author       = { "Yayan D Wicaksono" => "ya2n.wicaksono@gmail.com" }
   spec.ios.deployment_target = "14.0"
-#  spec.source       = { :http => 'https://newuniverse.io/UCPaaSiOS/releases/download/DigiXLite/v2.2.3/DigiXLite.zip' }
+#  spec.source       = { :http => 'https://newuniverse.io/UCPaaSiOS/releases/download/DigiXLite/v2.2.1/DigiXLite.zip' }
   spec.source       = { :path => '.' }
   spec.source_files = 'DigiXLite/Source/**/*'
   spec.resource_bundles = { 'DigiXLite' => ['DigiXLite/Resource/**/*']}
@@ -31,6 +31,7 @@ Pod::Spec.new do |spec|
   spec.dependency 'Toast-Swift', '~> 5.0.1'
   spec.dependency 'ZIPFoundation', '~> 0.9.16'
   spec.dependency 'SwiftLinkPreview', '~> 3.4.0'
+  spec.dependency 'Popover'
 # spec.dependency 'iOS-WebP'
 #  spec.preserve_path = 'DigiXLite.framework'
 #  spec.xcconfig = { 'OTHER_LDFLAGS' => '-framework DigiXLite' }

+ 1 - 1
appbuilder-ios/DigiXLite/DigiXLite.xcodeproj/xcuserdata/akhmadalqindiirsyam.xcuserdatad/xcschemes/xcschememanagement.plist

@@ -12,7 +12,7 @@
 		<key>DigiXLiteResources.xcscheme_^#shared#^_</key>
 		<dict>
 			<key>orderHint</key>
-			<integer>14</integer>
+			<integer>15</integer>
 		</dict>
 		<key>NexilisLiteResources.xcscheme_^#shared#^_</key>
 		<dict>

BIN
appbuilder-ios/DigiXLite/DigiXLite.xcworkspace/xcuserdata/akhmadalqindiirsyam.xcuserdatad/UserInterfaceState.xcuserstate


+ 17 - 3
appbuilder-ios/DigiXLite/DigiXLite/Source/View/Chat/EditorPersonal.swift

@@ -1129,7 +1129,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                         }
                     }
                     if row["image_id"] != nil && !(row["image_id"] as! String).isEmpty && (row["message_text"] as! String).isEmpty && (row["reff_id"] as! String).isEmpty && (row["credential"] as! String) != "1" && (row["read_receipts"] as! String) != "8" {
-                        tempImages.append(ImageGrouping(messageId: row["message_id"] as! String, thumbId: row["thumb_id"] as! String, imageId: row["image_id"] as! String, status: row["status"] as! String, time: row["server_date"] as! String, lPin: row["l_pin"] as! String))
+                        tempImages.append(ImageGrouping(messageId: row["message_id"] as! String, thumbId: row["thumb_id"] as! String, imageId: row["image_id"] as! String, status: row["status"] as! String, time: row["server_date"] as! String, lPin: row["l_pin"] as! String, dataMessage: row, dataPerson: dataPerson))
                     } else if tempImages.count >= 4 {
                         groupImages[tempImages[0].messageId] = tempImages
                         var tempDataOnGrouping: [[String: Any?]] =  []
@@ -3789,11 +3789,20 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             }
             cancelAction()
         } else if forwardSession {
-            let dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
+            var dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
             let countSelected = dataMessages.count
             if countSelected == 0 {
                 return
             }
+            for i in 0..<countSelected {
+                if let groupingImages = groupImages[dataMessages[i]["message_id"] as! String] {
+                    var tempData = dataMessages
+                    tempData.remove(at: i)
+                    tempData.insert(contentsOf: dataMessagesOnGrouping[dataMessages[i]["message_id"] as! String]!, at: i)
+                    tempData.insert(dataMessages[i], at: i)
+                    dataMessages = tempData
+                }
+            }
             contactChatNav.modalPresentationStyle = .custom
             contactChatNav.navigationBar.tintColor = .white
             contactChatNav.navigationBar.barTintColor = .mainColor
@@ -6315,13 +6324,18 @@ public class ImageGrouping {
     public var status = ""
     public var time = ""
     public var lPin = ""
+    public var dataMessage: [String: Any?] = [:]
+    public var dataPerson: [String: String?] = [:]
     
-    public init(messageId: String, thumbId: String, imageId: String, status: String, time: String, lPin: String) {
+    public init(messageId: String, thumbId: String, imageId: String, status: String, time: String, lPin: String, dataMessage: [String: Any?], dataPerson: [String: String?]) {
         self.messageId = messageId
         self.thumbId = thumbId
         self.imageId = imageId
         self.status = status
         self.time = time
         self.lPin = lPin
+        self.dataMessage = dataMessage
+        self.dataPerson = dataPerson
     }
 }
+

+ 120 - 1
appbuilder-ios/DigiXLite/DigiXLite/Source/View/Chat/ListGroupImages.swift

@@ -6,6 +6,7 @@
 //
 
 import UIKit
+import Popover
 
 class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDelegate {
     var listGroupingImages: [ImageGrouping]!
@@ -13,6 +14,14 @@ class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDeleg
     var titleName: String!
     let tableViewImages = UITableView()
     var isInitiator = false
+    var copySession = false
+    var forwardSession = false
+    var deleteSession = false
+    var tableViewPopOver = UITableView()
+    var popover: Popover!
+    var startYVisible: CGFloat!
+    var endYVisible: CGFloat!
+    var indexSelected = 0
 
     override func viewDidLoad() {
         super.viewDidLoad()
@@ -31,7 +40,9 @@ class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDeleg
         self.view.addSubview(tableViewImages)
         tableViewImages.anchor(top: self.view.safeAreaLayoutGuide.topAnchor, left: self.view.safeAreaLayoutGuide.leftAnchor, bottom: self.view.safeAreaLayoutGuide.bottomAnchor, right: self.view.safeAreaLayoutGuide.rightAnchor)
         
-        tableViewImages.scrollToRow(at: IndexPath(row: imageTapped, section: 0), at: .top, animated: false)
+        DispatchQueue.main.async {[self] in
+            tableViewImages.scrollToRow(at: IndexPath(row: imageTapped, section: 0), at: .top, animated: false)
+        }
         
         let center: NotificationCenter = NotificationCenter.default
         center.addObserver(self, selector: #selector(onStatusChat(notification:)), name: NSNotification.Name(rawValue: DigiX.listenerStatusChat), object: nil)
@@ -52,10 +63,38 @@ class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDeleg
     }
     
     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if tableView == tableViewPopOver {
+            return 5
+        }
         return listGroupingImages.count
     }
     
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        if tableView == tableViewPopOver {
+            let cell = tableView.dequeueReusableCell(withIdentifier: "cellPopOver", for: indexPath as IndexPath)
+            var content = cell.defaultContentConfiguration()
+            content.textProperties.font = UIFont.systemFont(ofSize: 14)
+            content.imageProperties.tintColor = .black
+            switch indexPath.row {
+            case 0:
+                content.image = UIImage(systemName: "star.fill")
+                content.text = "Star".localized()
+            case 1:
+                content.image = UIImage(systemName: "arrowshape.turn.up.left.fill")
+                content.text = "Reply".localized()
+            case 2:
+                content.image = UIImage(systemName: "arrowshape.turn.up.right.fill")
+                content.text = "Forward".localized()
+            case 3:
+                content.image = UIImage(systemName: "info.circle.fill")
+                content.text = "Info".localized()
+            default:
+                content.image = UIImage(systemName: "trash.fill")
+                content.text = "Delete".localized()
+            }
+            cell.contentConfiguration = content
+            return cell
+        }
         let cell = tableView.dequeueReusableCell(withIdentifier: "cellGrupingImages", for: indexPath as IndexPath)
         cell.contentView.subviews.forEach({ $0.removeFromSuperview() })
         cell.backgroundColor = .clear
@@ -65,6 +104,14 @@ class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDeleg
         cell.contentView.addSubview(containerImages)
         containerImages.anchor(top: cell.contentView.topAnchor, left: cell.contentView.leftAnchor, bottom: cell.contentView.bottomAnchor, right: cell.contentView.rightAnchor, paddingBottom: 15, height: UIScreen.main.bounds.height - 104)
         
+        if !copySession && !forwardSession && !deleteSession {
+            let longPressRecognizer = LongPressImageVIew(target: self, action: #selector(handleLongPress(_:)))
+            longPressRecognizer.imageView = containerImages
+            longPressRecognizer.index = indexPath.row
+            containerImages.isUserInteractionEnabled = true
+            containerImages.addGestureRecognizer(longPressRecognizer)
+        }
+        
         let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
         let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
         let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
@@ -118,6 +165,73 @@ class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDeleg
         }
         return cell
     }
+    
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        if tableView == tableViewPopOver {
+            popover.dismiss()
+            switch indexPath.row {
+            case 0:
+                popover.dismiss()
+            case 1:
+                popover.dismiss()
+            case 2:
+                popover.dismiss()
+            case 3:
+                popover.dismiss()
+                let messageInfoVC = MessageInfo()
+                messageInfoVC.data = listGroupingImages[indexSelected].dataMessage
+                messageInfoVC.dataPerson = listGroupingImages[indexPath.row].dataPerson
+                self.navigationController?.pushViewController(messageInfoVC, animated: true)
+            default :
+                popover.dismiss()
+            }
+        }
+    }
+    
+    func scrollViewDidScroll(_ scrollView: UIScrollView) {
+        let visibleRect = CGRect(origin: scrollView.contentOffset, size: scrollView.bounds.size)
+        let visibleTableViewRect = tableViewImages.convert(visibleRect, from: tableViewImages)
+
+        let startY = visibleTableViewRect.origin.y
+        let endY = startY + visibleTableViewRect.size.height
+        startYVisible = startY
+        endYVisible = endY
+    }
+    
+    @objc func handleLongPress(_ gestureRecognizer: LongPressImageVIew) {
+        if gestureRecognizer.state == .began {
+            indexSelected = gestureRecognizer.index
+            let contentOffset = tableViewImages.contentOffset
+            let location = gestureRecognizer.location(in: tableViewImages)
+            let xTouch = location.x - contentOffset.x
+            var yTouch = location.y - contentOffset.y + 100
+            
+            let boundary = startYVisible != nil ? (endYVisible - startYVisible) / 2 - 50 : (UIScreen.main.bounds.height - 64) / 2 - 50
+            let yTouchDiff = startYVisible != nil ? location.y - startYVisible : location.y - 0.0
+            
+            tableViewPopOver = UITableView(frame: CGRect(x: 0, y: 10, width: 140, height: 220))
+            popover = Popover()
+            if yTouchDiff >= boundary {
+                yTouch = location.y - contentOffset.y + 20
+                tableViewPopOver = UITableView(frame: CGRect(x: 0, y: 0, width: 140, height: 220))
+                popover.popoverType = .up
+            }
+            
+            tableViewPopOver.register(UITableViewCell.self, forCellReuseIdentifier: "cellPopOver")
+            tableViewPopOver.dataSource = self
+            tableViewPopOver.delegate = self
+            tableViewPopOver.layoutMargins = UIEdgeInsets.zero
+            tableViewPopOver.separatorInset = UIEdgeInsets.zero
+            tableViewPopOver.isScrollEnabled = false
+            
+            let viewTable = UITableView(frame: CGRect(x: 0, y: 0, width: 140, height: 220))
+            viewTable.addSubview(tableViewPopOver)
+            
+            let touchPoint = CGPoint( x: xTouch, y: yTouch)
+            popover.show(viewTable, point: touchPoint)
+            UINotificationFeedbackGenerator().notificationOccurred(.success)
+        }
+    }
 }
 
 class CenteredTitleSubtitleView: UIView {
@@ -162,3 +276,8 @@ class CenteredTitleSubtitleView: UIView {
         subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor).isActive = true
     }
 }
+
+class LongPressImageVIew: UILongPressGestureRecognizer {
+    var imageView = UIImageView()
+    var index = 0
+}

+ 2 - 1
appbuilder-ios/DigiXLite/DigiXLite/Source/View/Chat/MessageInfo.swift

@@ -700,7 +700,8 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource
                 let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
                 if let dirPath = paths.first {
                     let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumbChat)
-                    let image    = UIImage(contentsOfFile: thumbURL.path)
+//                    let image    = UIImage(contentsOfFile: thumbURL.path)
+                    let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
                     imageThumb.image = image
                     
                     let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(videoChat)

+ 1 - 0
appbuilder-ios/DigiXLite/Podfile

@@ -15,6 +15,7 @@ target 'DigiXLite' do
   pod 'Toast-Swift', '~> 5.0.1'
   pod 'ZIPFoundation', '~> 0.9.16'
   pod 'SwiftLinkPreview', '~> 3.4.0'
+  pod 'Popover'
 
   target 'DigiXLiteTests' do
     # Pods for testing

+ 3 - 2
appbuilder-ios/NexilisLite/NexilisLite.podspec

@@ -8,7 +8,7 @@
 
 Pod::Spec.new do |spec|
   spec.name         = "NexilisLite"
-  spec.version      = "2.2.3"
+  spec.version      = "2.2.1"
   spec.summary      = "NexilisLite Framework"
   spec.description  = <<-DESC
   NexilisLite Framework, embed Contact Center, Live Streaming, Push Notifications, Instant Messaging, Video and VoIP Calling features into your mobile apps within minutes...
@@ -18,7 +18,7 @@ Pod::Spec.new do |spec|
   spec.license      = "MIT"
   spec.author       = { "Yayan D Wicaksono" => "ya2n.wicaksono@gmail.com" }
   spec.ios.deployment_target = "14.0"
-#  spec.source       = { :http => 'https://newuniverse.io/UCPaaSiOS/releases/download/NexilisLiteiOS/v2.2.3/NexilisLite.zip' }
+#  spec.source       = { :http => 'https://newuniverse.io/UCPaaSiOS/releases/download/NexilisLite/v2.2.1/NexilisLite.zip' }
   spec.source       = { :path => '.' }
   spec.source_files = 'NexilisLite/Source/**/*'
   spec.resource_bundles = { 'NexilisLite' => ['NexilisLite/Resource/**/*']}
@@ -31,6 +31,7 @@ Pod::Spec.new do |spec|
   spec.dependency 'Toast-Swift', '~> 5.0.1'
   spec.dependency 'ZIPFoundation', '~> 0.9.16'
   spec.dependency 'SwiftLinkPreview', '~> 3.4.0'
+  spec.dependency 'Popover'
 # spec.dependency 'iOS-WebP'
 #  spec.preserve_path = 'NexilisLite.framework'
 #  spec.xcconfig = { 'OTHER_LDFLAGS' => '-framework NexilisLite' }

+ 37 - 33
appbuilder-ios/NexilisLite/NexilisLite.xcodeproj/project.pbxproj

@@ -85,7 +85,6 @@
 		CD1E724E2A0BA86100BF871F /* VerifyEmail.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71A02A0BA86100BF871F /* VerifyEmail.swift */; };
 		CD1E724F2A0BA86100BF871F /* SetInternalCSAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71A12A0BA86100BF871F /* SetInternalCSAccount.swift */; };
 		CD1E72502A0BA86100BF871F /* BroadcastVariantViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71A22A0BA86100BF871F /* BroadcastVariantViewController.swift */; };
-		CD1E72512A0BA86100BF871F /* MessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71A32A0BA86100BF871F /* MessageInfo.swift */; };
 		CD1E72522A0BA86100BF871F /* ImageVideoPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71A42A0BA86100BF871F /* ImageVideoPicker.swift */; };
 		CD1E72532A0BA86100BF871F /* BackupRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71A52A0BA86100BF871F /* BackupRestoreView.swift */; };
 		CD1E72542A0BA86100BF871F /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1E71A62A0BA86100BF871F /* ProfileViewController.swift */; };
@@ -187,6 +186,8 @@
 		CD46A0BB2A0CE320009E4C87 /* sticker_10000000_8.png in Resources */ = {isa = PBXBuildFile; fileRef = CD46A0162A0CE2DE009E4C87 /* sticker_10000000_8.png */; };
 		CD46A0BF2A0CE4FD009E4C87 /* NexilisLite.podspec in Resources */ = {isa = PBXBuildFile; fileRef = CD46A0BE2A0CE4FD009E4C87 /* NexilisLite.podspec */; };
 		CD46A0C52A0D0D5D009E4C87 /* MyArchive.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD46A0C42A0D0D5D009E4C87 /* MyArchive.swift */; };
+		CD5A73AE2A77642D000541A5 /* MessageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD5A73AC2A77642D000541A5 /* MessageInfo.swift */; };
+		CD5A73AF2A77642D000541A5 /* ListGroupImages.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD5A73AD2A77642D000541A5 /* ListGroupImages.swift */; };
 		CD9551592A664BDA00AF6476 /* Poppins-LightItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD9551402A664BD400AF6476 /* Poppins-LightItalic.otf */; };
 		CD95515A2A664BDA00AF6476 /* Poppins-MediumItalic.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD95513F2A664BD400AF6476 /* Poppins-MediumItalic.otf */; };
 		CD95515B2A664BDA00AF6476 /* Poppins-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = CD9551412A664BD400AF6476 /* Poppins-Bold.otf */; };
@@ -330,7 +331,6 @@
 		CD1E71A02A0BA86100BF871F /* VerifyEmail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VerifyEmail.swift; sourceTree = "<group>"; };
 		CD1E71A12A0BA86100BF871F /* SetInternalCSAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetInternalCSAccount.swift; sourceTree = "<group>"; };
 		CD1E71A22A0BA86100BF871F /* BroadcastVariantViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BroadcastVariantViewController.swift; sourceTree = "<group>"; };
-		CD1E71A32A0BA86100BF871F /* MessageInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInfo.swift; sourceTree = "<group>"; };
 		CD1E71A42A0BA86100BF871F /* ImageVideoPicker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageVideoPicker.swift; sourceTree = "<group>"; };
 		CD1E71A52A0BA86100BF871F /* BackupRestoreView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackupRestoreView.swift; sourceTree = "<group>"; };
 		CD1E71A62A0BA86100BF871F /* ProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
@@ -440,6 +440,8 @@
 		CD46A01A2A0CE2DE009E4C87 /* Palio.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Palio.storyboard; sourceTree = "<group>"; };
 		CD46A0BE2A0CE4FD009E4C87 /* NexilisLite.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = NexilisLite.podspec; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
 		CD46A0C42A0D0D5D009E4C87 /* MyArchive.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyArchive.swift; sourceTree = "<group>"; };
+		CD5A73AC2A77642D000541A5 /* MessageInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInfo.swift; sourceTree = "<group>"; };
+		CD5A73AD2A77642D000541A5 /* ListGroupImages.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListGroupImages.swift; sourceTree = "<group>"; };
 		CD9551332A664BD400AF6476 /* Poppins-SemiBoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-SemiBoldItalic.otf"; sourceTree = "<group>"; };
 		CD9551342A664BD400AF6476 /* Poppins-ExtraBoldItalic.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Poppins-ExtraBoldItalic.otf"; sourceTree = "<group>"; };
 		CD9551352A664BD400AF6476 /* SIL Open Font License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "SIL Open Font License.txt"; sourceTree = "<group>"; };
@@ -649,13 +651,15 @@
 		CD1E716D2A0BA86100BF871F /* Chat */ = {
 			isa = PBXGroup;
 			children = (
-				CD1E716E2A0BA86100BF871F /* EditorStarMessages.swift */,
-				CD1E716F2A0BA86100BF871F /* PreviewAttachmentImageVideo.xib */,
+				CD1E71742A0BA86100BF871F /* CustomTextView.swift */,
+				CD1E71722A0BA86100BF871F /* EditorGroup.swift */,
 				CD1E71702A0BA86100BF871F /* EditorPersonal.swift */,
+				CD1E716E2A0BA86100BF871F /* EditorStarMessages.swift */,
 				CD1E71712A0BA86100BF871F /* FormEditor.swift */,
-				CD1E71722A0BA86100BF871F /* EditorGroup.swift */,
+				CD5A73AD2A77642D000541A5 /* ListGroupImages.swift */,
+				CD5A73AC2A77642D000541A5 /* MessageInfo.swift */,
 				CD1E71732A0BA86100BF871F /* PreviewAttachmentImageVideo.swift */,
-				CD1E71742A0BA86100BF871F /* CustomTextView.swift */,
+				CD1E716F2A0BA86100BF871F /* PreviewAttachmentImageVideo.xib */,
 			);
 			path = Chat;
 			sourceTree = "<group>";
@@ -699,42 +703,41 @@
 		CD1E71892A0BA86100BF871F /* Control */ = {
 			isa = PBXGroup;
 			children = (
-				CD1E718A2A0BA86100BF871F /* GroupCreateViewController.swift */,
+				CD1E71AB2A0BA86100BF871F /* AddFriendTableViewController.swift */,
+				CD1E71AD2A0BA86100BF871F /* AudienceViewController.swift */,
+				CD1E719B2A0BA86100BF871F /* BackupRestoreOption.swift */,
+				CD1E71A52A0BA86100BF871F /* BackupRestoreView.swift */,
 				CD1E718B2A0BA86100BF871F /* BroadcastMembersTableViewCell.swift */,
-				CD1E718C2A0BA86100BF871F /* QRScannerView.swift */,
-				CD1E718D2A0BA86100BF871F /* GroupDescViewController.swift */,
-				CD1E718E2A0BA86100BF871F /* HistoryBroadcastViewController.swift */,
-				CD1E718F2A0BA86100BF871F /* CheckConnection.swift */,
-				CD1E71902A0BA86100BF871F /* SignUpSignIn.swift */,
-				CD1E71912A0BA86100BF871F /* GroupDetailViewController.swift */,
+				CD1E71A72A0BA86100BF871F /* BroadcastMembersTableViewController.swift */,
+				CD1E719A2A0BA86100BF871F /* BroadcastModeViewController.swift */,
+				CD1E71A22A0BA86100BF871F /* BroadcastVariantViewController.swift */,
+				CD1E71A82A0BA86100BF871F /* BroadcastViewController.swift */,
+				CD1E71942A0BA86100BF871F /* ChangeDeviceViewController.swift */,
 				CD1E71922A0BA86100BF871F /* ChangeNamePassswordViewController.swift */,
+				CD1E719D2A0BA86100BF871F /* ChangeNameTableViewController.swift */,
+				CD1E71962A0BA86100BF871F /* ChangePasswordViewController.swift */,
+				CD1E718F2A0BA86100BF871F /* CheckConnection.swift */,
 				CD1E71932A0BA86100BF871F /* ContactChatViewController.swift */,
-				CD1E71942A0BA86100BF871F /* ChangeDeviceViewController.swift */,
 				CD1E71952A0BA86100BF871F /* DocumentPicker.swift */,
-				CD1E71962A0BA86100BF871F /* ChangePasswordViewController.swift */,
+				CD1E718A2A0BA86100BF871F /* GroupCreateViewController.swift */,
+				CD1E718D2A0BA86100BF871F /* GroupDescViewController.swift */,
+				CD1E71912A0BA86100BF871F /* GroupDetailViewController.swift */,
+				CD1E71A92A0BA86100BF871F /* GroupMemberViewController.swift */,
 				CD1E71972A0BA86100BF871F /* GroupNameViewController.swift */,
 				CD1E71982A0BA86100BF871F /* GroupTopicViewController.swift */,
-				CD1E71992A0BA86100BF871F /* SettingTableViewController.swift */,
-				CD1E719A2A0BA86100BF871F /* BroadcastModeViewController.swift */,
-				CD1E719B2A0BA86100BF871F /* BackupRestoreOption.swift */,
+				CD1E718E2A0BA86100BF871F /* HistoryBroadcastViewController.swift */,
 				CD1E719C2A0BA86100BF871F /* HistoryCCViewController.swift */,
-				CD1E719D2A0BA86100BF871F /* ChangeNameTableViewController.swift */,
-				CD1E719E2A0BA86100BF871F /* NotificationSound.swift */,
-				CD1E719F2A0BA86100BF871F /* TypeViewController.swift */,
-				CD1E71A02A0BA86100BF871F /* VerifyEmail.swift */,
-				CD1E71A12A0BA86100BF871F /* SetInternalCSAccount.swift */,
-				CD1E71A22A0BA86100BF871F /* BroadcastVariantViewController.swift */,
-				CD1E71A32A0BA86100BF871F /* MessageInfo.swift */,
 				CD1E71A42A0BA86100BF871F /* ImageVideoPicker.swift */,
-				CD1E71A52A0BA86100BF871F /* BackupRestoreView.swift */,
+				CD1E719E2A0BA86100BF871F /* NotificationSound.swift */,
 				CD1E71A62A0BA86100BF871F /* ProfileViewController.swift */,
-				CD1E71A72A0BA86100BF871F /* BroadcastMembersTableViewController.swift */,
-				CD1E71A82A0BA86100BF871F /* BroadcastViewController.swift */,
-				CD1E71A92A0BA86100BF871F /* GroupMemberViewController.swift */,
-				CD1E71AA2A0BA86100BF871F /* SetOfficerBNI.swift */,
-				CD1E71AB2A0BA86100BF871F /* AddFriendTableViewController.swift */,
+				CD1E718C2A0BA86100BF871F /* QRScannerView.swift */,
 				CD1E71AC2A0BA86100BF871F /* ScannerViewController.swift */,
-				CD1E71AD2A0BA86100BF871F /* AudienceViewController.swift */,
+				CD1E71A12A0BA86100BF871F /* SetInternalCSAccount.swift */,
+				CD1E71AA2A0BA86100BF871F /* SetOfficerBNI.swift */,
+				CD1E71992A0BA86100BF871F /* SettingTableViewController.swift */,
+				CD1E71902A0BA86100BF871F /* SignUpSignIn.swift */,
+				CD1E719F2A0BA86100BF871F /* TypeViewController.swift */,
+				CD1E71A02A0BA86100BF871F /* VerifyEmail.swift */,
 			);
 			path = Control;
 			sourceTree = "<group>";
@@ -1171,12 +1174,12 @@
 			files = (
 				CD1E72272A0BA86100BF871F /* QmeraStreamingViewController.swift in Sources */,
 				CD1E72522A0BA86100BF871F /* ImageVideoPicker.swift in Sources */,
-				CD1E72512A0BA86100BF871F /* MessageInfo.swift in Sources */,
 				CD1E724E2A0BA86100BF871F /* VerifyEmail.swift in Sources */,
 				CDDF46762A2DD81300049A19 /* SeminarViewController.swift in Sources */,
 				CD1E721A2A0BA86100BF871F /* NotifSound.swift in Sources */,
 				CD1E724F2A0BA86100BF871F /* SetInternalCSAccount.swift in Sources */,
 				CD1E721C2A0BA86100BF871F /* ContactCallViewController.swift in Sources */,
+				CD5A73AE2A77642D000541A5 /* MessageInfo.swift in Sources */,
 				CD1E723B2A0BA86100BF871F /* GroupDescViewController.swift in Sources */,
 				CD1E725C2A0BA86100BF871F /* Utils.swift in Sources */,
 				CD1E72232A0BA86100BF871F /* EditorGroup.swift in Sources */,
@@ -1211,6 +1214,7 @@
 				CD1E724A2A0BA86100BF871F /* HistoryCCViewController.swift in Sources */,
 				CD1E722A2A0BA86100BF871F /* StreamingViewController.swift in Sources */,
 				CD1E72132A0BA86100BF871F /* Database.swift in Sources */,
+				CD5A73AF2A77642D000541A5 /* ListGroupImages.swift in Sources */,
 				CD1E720F2A0BA86100BF871F /* Download.swift in Sources */,
 				CD1E725D2A0BA86100BF871F /* WhiteboardDelegate.swift in Sources */,
 				CD1E721B2A0BA86100BF871F /* Nexilis.swift in Sources */,

+ 16 - 3
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -1130,7 +1130,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                         }
                     }
                     if row["image_id"] != nil && !(row["image_id"] as! String).isEmpty && (row["message_text"] as! String).isEmpty && (row["reff_id"] as! String).isEmpty && (row["credential"] as! String) != "1" && (row["read_receipts"] as! String) != "8" {
-                        tempImages.append(ImageGrouping(messageId: row["message_id"] as! String, thumbId: row["thumb_id"] as! String, imageId: row["image_id"] as! String, status: row["status"] as! String, time: row["server_date"] as! String, lPin: row["l_pin"] as! String))
+                        tempImages.append(ImageGrouping(messageId: row["message_id"] as! String, thumbId: row["thumb_id"] as! String, imageId: row["image_id"] as! String, status: row["status"] as! String, time: row["server_date"] as! String, lPin: row["l_pin"] as! String, dataMessage: row, dataPerson: dataPerson))
                     } else if tempImages.count >= 4 {
                         groupImages[tempImages[0].messageId] = tempImages
                         var tempDataOnGrouping: [[String: Any?]] =  []
@@ -3800,11 +3800,20 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             }
             cancelAction()
         } else if forwardSession {
-            let dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
+            var dataMessages = self.dataMessages.filter({ $0["isSelected"] as! Bool == true })
             let countSelected = dataMessages.count
             if countSelected == 0 {
                 return
             }
+            for i in 0..<countSelected {
+                if let groupingImages = groupImages[dataMessages[i]["message_id"] as! String] {
+                    var tempData = dataMessages
+                    tempData.remove(at: i)
+                    tempData.insert(contentsOf: dataMessagesOnGrouping[dataMessages[i]["message_id"] as! String]!, at: i)
+                    tempData.insert(dataMessages[i], at: i)
+                    dataMessages = tempData
+                }
+            }
             contactChatNav.modalPresentationStyle = .custom
             contactChatNav.navigationBar.tintColor = .white
             contactChatNav.navigationBar.barTintColor = .mainColor
@@ -6327,13 +6336,17 @@ public class ImageGrouping {
     public var status = ""
     public var time = ""
     public var lPin = ""
+    public var dataMessage: [String: Any?] = [:]
+    public var dataPerson: [String: String?] = [:]
     
-    public init(messageId: String, thumbId: String, imageId: String, status: String, time: String, lPin: String) {
+    public init(messageId: String, thumbId: String, imageId: String, status: String, time: String, lPin: String, dataMessage: [String: Any?], dataPerson: [String: String?]) {
         self.messageId = messageId
         self.thumbId = thumbId
         self.imageId = imageId
         self.status = status
         self.time = time
         self.lPin = lPin
+        self.dataMessage = dataMessage
+        self.dataPerson = dataPerson
     }
 }

+ 119 - 1
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/ListGroupImages.swift

@@ -6,6 +6,7 @@
 //
 
 import UIKit
+import Popover
 
 class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDelegate {
     var listGroupingImages: [ImageGrouping]!
@@ -13,6 +14,14 @@ class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDeleg
     var titleName: String!
     let tableViewImages = UITableView()
     var isInitiator = false
+    var copySession = false
+    var forwardSession = false
+    var deleteSession = false
+    var tableViewPopOver = UITableView()
+    var popover: Popover!
+    var startYVisible: CGFloat!
+    var endYVisible: CGFloat!
+    var indexSelected = 0
 
     override func viewDidLoad() {
         super.viewDidLoad()
@@ -31,7 +40,9 @@ class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDeleg
         self.view.addSubview(tableViewImages)
         tableViewImages.anchor(top: self.view.safeAreaLayoutGuide.topAnchor, left: self.view.safeAreaLayoutGuide.leftAnchor, bottom: self.view.safeAreaLayoutGuide.bottomAnchor, right: self.view.safeAreaLayoutGuide.rightAnchor)
         
-        tableViewImages.scrollToRow(at: IndexPath(row: imageTapped, section: 0), at: .top, animated: false)
+        DispatchQueue.main.async {[self] in
+            tableViewImages.scrollToRow(at: IndexPath(row: imageTapped, section: 0), at: .top, animated: false)
+        }
         
         let center: NotificationCenter = NotificationCenter.default
         center.addObserver(self, selector: #selector(onStatusChat(notification:)), name: NSNotification.Name(rawValue: Nexilis.listenerStatusChat), object: nil)
@@ -52,10 +63,38 @@ class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDeleg
     }
     
     func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if tableView == tableViewPopOver {
+            return 5
+        }
         return listGroupingImages.count
     }
     
     func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        if tableView == tableViewPopOver {
+            let cell = tableView.dequeueReusableCell(withIdentifier: "cellPopOver", for: indexPath as IndexPath)
+            var content = cell.defaultContentConfiguration()
+            content.textProperties.font = UIFont.systemFont(ofSize: 14)
+            content.imageProperties.tintColor = .black
+            switch indexPath.row {
+            case 0:
+                content.image = UIImage(systemName: "star.fill")
+                content.text = "Star".localized()
+            case 1:
+                content.image = UIImage(systemName: "arrowshape.turn.up.left.fill")
+                content.text = "Reply".localized()
+            case 2:
+                content.image = UIImage(systemName: "arrowshape.turn.up.right.fill")
+                content.text = "Forward".localized()
+            case 3:
+                content.image = UIImage(systemName: "info.circle.fill")
+                content.text = "Info".localized()
+            default:
+                content.image = UIImage(systemName: "trash.fill")
+                content.text = "Delete".localized()
+            }
+            cell.contentConfiguration = content
+            return cell
+        }
         let cell = tableView.dequeueReusableCell(withIdentifier: "cellGrupingImages", for: indexPath as IndexPath)
         cell.contentView.subviews.forEach({ $0.removeFromSuperview() })
         cell.backgroundColor = .clear
@@ -65,6 +104,14 @@ class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDeleg
         cell.contentView.addSubview(containerImages)
         containerImages.anchor(top: cell.contentView.topAnchor, left: cell.contentView.leftAnchor, bottom: cell.contentView.bottomAnchor, right: cell.contentView.rightAnchor, paddingBottom: 15, height: UIScreen.main.bounds.height - 104)
         
+        if !copySession && !forwardSession && !deleteSession {
+            let longPressRecognizer = LongPressImageVIew(target: self, action: #selector(handleLongPress(_:)))
+            longPressRecognizer.imageView = containerImages
+            longPressRecognizer.index = indexPath.row
+            containerImages.isUserInteractionEnabled = true
+            containerImages.addGestureRecognizer(longPressRecognizer)
+        }
+        
         let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
         let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
         let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
@@ -119,7 +166,73 @@ class ListGroupImages: UIViewController, UITableViewDataSource, UITableViewDeleg
         }
         return cell
     }
+    
+    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        if tableView == tableViewPopOver {
+            popover.dismiss()
+            switch indexPath.row {
+            case 0:
+                popover.dismiss()
+            case 1:
+                popover.dismiss()
+            case 2:
+                popover.dismiss()
+            case 3:
+                popover.dismiss()
+                let messageInfoVC = MessageInfo()
+                messageInfoVC.data = listGroupingImages[indexSelected].dataMessage
+                messageInfoVC.dataPerson = listGroupingImages[indexPath.row].dataPerson
+                self.navigationController?.pushViewController(messageInfoVC, animated: true)
+            default :
+                popover.dismiss()
+            }
+        }
+    }
+    
+    func scrollViewDidScroll(_ scrollView: UIScrollView) {
+        let visibleRect = CGRect(origin: scrollView.contentOffset, size: scrollView.bounds.size)
+        let visibleTableViewRect = tableViewImages.convert(visibleRect, from: tableViewImages)
 
+        let startY = visibleTableViewRect.origin.y
+        let endY = startY + visibleTableViewRect.size.height
+        startYVisible = startY
+        endYVisible = endY
+    }
+    
+    @objc func handleLongPress(_ gestureRecognizer: LongPressImageVIew) {
+        if gestureRecognizer.state == .began {
+            indexSelected = gestureRecognizer.index
+            let contentOffset = tableViewImages.contentOffset
+            let location = gestureRecognizer.location(in: tableViewImages)
+            let xTouch = location.x - contentOffset.x
+            var yTouch = location.y - contentOffset.y + 100
+            
+            let boundary = startYVisible != nil ? (endYVisible - startYVisible) / 2 - 50 : (UIScreen.main.bounds.height - 64) / 2 - 50
+            let yTouchDiff = startYVisible != nil ? location.y - startYVisible : location.y - 0.0
+            
+            tableViewPopOver = UITableView(frame: CGRect(x: 0, y: 10, width: 140, height: 220))
+            popover = Popover()
+            if yTouchDiff >= boundary {
+                yTouch = location.y - contentOffset.y + 20
+                tableViewPopOver = UITableView(frame: CGRect(x: 0, y: 0, width: 140, height: 220))
+                popover.popoverType = .up
+            }
+            
+            tableViewPopOver.register(UITableViewCell.self, forCellReuseIdentifier: "cellPopOver")
+            tableViewPopOver.dataSource = self
+            tableViewPopOver.delegate = self
+            tableViewPopOver.layoutMargins = UIEdgeInsets.zero
+            tableViewPopOver.separatorInset = UIEdgeInsets.zero
+            tableViewPopOver.isScrollEnabled = false
+            
+            let viewTable = UITableView(frame: CGRect(x: 0, y: 0, width: 140, height: 220))
+            viewTable.addSubview(tableViewPopOver)
+            
+            let touchPoint = CGPoint( x: xTouch, y: yTouch)
+            popover.show(viewTable, point: touchPoint)
+            UINotificationFeedbackGenerator().notificationOccurred(.success)
+        }
+    }
 }
 
 class CenteredTitleSubtitleView: UIView {
@@ -164,3 +277,8 @@ class CenteredTitleSubtitleView: UIView {
         subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor).isActive = true
     }
 }
+
+class LongPressImageVIew: UILongPressGestureRecognizer {
+    var imageView = UIImageView()
+    var index = 0
+}

+ 5 - 2
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/MessageInfo.swift

@@ -700,7 +700,8 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource
                 let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
                 if let dirPath = paths.first {
                     let thumbURL = URL(fileURLWithPath: dirPath).appendingPathComponent(thumbChat)
-                    let image    = UIImage(contentsOfFile: thumbURL.path)
+//                    let image    = UIImage(contentsOfFile: thumbURL.path)
+                    let image = UIGraphicsRenderer.renderImageAt(url: thumbURL as NSURL, size: CGSize(width: 250, height: 250))
                     imageThumb.image = image
                     
                     let videoURL = URL(fileURLWithPath: dirPath).appendingPathComponent(videoChat)
@@ -1030,9 +1031,11 @@ class MessageInfo: UIViewController, UITableViewDelegate, UITableViewDataSource
             let day = -(components.day!)
             if day == 1 {
                 return "Yesterday".localized()
+            } else if day < 7 {
+                return "\(day) " + "days".localized() + " " + "ago".localized()
             } else {
                 let formatter = DateFormatter()
-                formatter.dateFormat = "dd/MM/yy"
+                formatter.dateFormat = "dd MMMM yyyy"
                 formatter.locale = NSLocale(localeIdentifier: "id") as Locale?
                 let stringFormat = formatter.string(from: date as Date)
                 return stringFormat

+ 1 - 0
appbuilder-ios/NexilisLite/Podfile

@@ -15,6 +15,7 @@ target 'NexilisLite' do
   pod 'Toast-Swift', '~> 5.0.1'
   pod 'ZIPFoundation', '~> 0.9.16'
   pod 'SwiftLinkPreview', '~> 3.4.0'
+  pod 'Popover'
 
   target 'NexilisLiteTests' do
     # Pods for testing

+ 5 - 1
appbuilder-ios/NexilisLite/Podfile.lock

@@ -8,6 +8,7 @@ PODS:
     - MarqueeLabel (~> 4.3.0)
     - SnapKit (~> 5.6.0)
   - nuSDKService (1.0.0)
+  - Popover (1.3.0)
   - SDWebImage (5.15.8):
     - SDWebImage/Core (= 5.15.8)
   - SDWebImage/Core (5.15.8)
@@ -21,6 +22,7 @@ DEPENDENCIES:
   - FMDB (~> 2.7.5)
   - NotificationBannerSwift (~> 3.2.1)
   - nuSDKService (~> 1.0.0)
+  - Popover
   - SDWebImage (~> 5.15.7)
   - SwiftLinkPreview (~> 3.4.0)
   - Toast-Swift (~> 5.0.1)
@@ -33,6 +35,7 @@ SPEC REPOS:
     - MarqueeLabel
     - NotificationBannerSwift
     - nuSDKService
+    - Popover
     - SDWebImage
     - SnapKit
     - SwiftLinkPreview
@@ -45,12 +48,13 @@ SPEC CHECKSUMS:
   MarqueeLabel: da4b9cf619fe70c75d7bab4580b0b4a0b868aa6b
   NotificationBannerSwift: dce54ded532b26e30cd8e7f4d80e124a0f2ba7d1
   nuSDKService: b92c0f1c47f502f808e4f4134881fcde687e46e9
+  Popover: 10e1d9528f81d9504df984b7b3f491292bc1822d
   SDWebImage: cb032eba469c54e0000e78bcb0a13cdde0a52798
   SnapKit: e01d52ebb8ddbc333eefe2132acf85c8227d9c25
   SwiftLinkPreview: c1bc5b2cb3cd42f2865137c1a61f5d788dcf5c9c
   Toast-Swift: 9b6a70f28b3bf0b96c40d46c0c4b9d6639846711
   ZIPFoundation: d170fa8e270b2a32bef9dcdcabff5b8f1a5deced
 
-PODFILE CHECKSUM: 3f56ca150f6ffc0bfb86b363d4183eb6c9e68195
+PODFILE CHECKSUM: 0b7b9265bedca3b4485f2678aa8f7bbb907ceaa2
 
 COCOAPODS: 1.12.1