alqindiirsyam 3 năm trước cách đây
mục cha
commit
12889a4b7d
23 tập tin đã thay đổi với 487 bổ sung49 xóa
  1. 2 2
      appbuilder-ios/AppBuilder/AppBuilder/SecondTabViewController.swift
  2. 21 0
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/ack_icon.imageset/Contents.json
  3. BIN
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/ack_icon.imageset/ack_icon.png
  4. 21 0
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/ack_icon_gray.imageset/Contents.json
  5. BIN
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/ack_icon_gray.imageset/ack_icon_gray.png
  6. 21 0
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/confidential_icon.imageset/Contents.json
  7. BIN
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/confidential_icon.imageset/confidential_icon.png
  8. 21 0
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/confidential_icon_gray.imageset/Contents.json
  9. BIN
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/confidential_icon_gray.imageset/confidential_icon_gray.png
  10. 21 0
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/message_status_ack.imageset/Contents.json
  11. BIN
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/message_status_ack.imageset/message_status_ack.png
  12. BIN
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button.imageset/pb_button.png
  13. BIN
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button_call.imageset/pb_button_call.png
  14. BIN
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button_cc.imageset/pb_button_cc.png
  15. BIN
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button_chat.imageset/pb_button_chat.png
  16. BIN
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button_post.imageset/pb_button_post.png
  17. BIN
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button_stream.imageset/pb_button_stream.png
  18. 41 5
      appbuilder-ios/NexilisLite/NexilisLite/Resource/Palio.storyboard
  19. 20 2
      appbuilder-ios/NexilisLite/NexilisLite/Resource/PreviewAttachmentImageVideo.xib
  20. 3 0
      appbuilder-ios/NexilisLite/NexilisLite/Resource/id.lproj/Localizable.strings
  21. 146 30
      appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift
  22. 149 7
      appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift
  23. 21 3
      appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.xib

+ 2 - 2
appbuilder-ios/AppBuilder/AppBuilder/SecondTabViewController.swift

@@ -311,8 +311,8 @@ class SecondTabViewController: UIViewController, UIScrollViewDelegate, UIGesture
     }
     
     @objc func onReceiveMessage(notification: NSNotification) {
-        DispatchQueue.main.async {
-            self.getChats {
+        self.getChats {
+            DispatchQueue.main.async {
                 self.tableView.reloadData()
             }
         }

+ 21 - 0
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/ack_icon.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "filename" : "ack_icon.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/ack_icon.imageset/ack_icon.png


+ 21 - 0
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/ack_icon_gray.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "filename" : "ack_icon_gray.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/ack_icon_gray.imageset/ack_icon_gray.png


+ 21 - 0
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/confidential_icon.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "filename" : "confidential_icon.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/confidential_icon.imageset/confidential_icon.png


+ 21 - 0
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/confidential_icon_gray.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "filename" : "confidential_icon_gray.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/confidential_icon_gray.imageset/confidential_icon_gray.png


+ 21 - 0
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/message_status_ack.imageset/Contents.json

@@ -0,0 +1,21 @@
+{
+  "images" : [
+    {
+      "filename" : "message_status_ack.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/message_status_ack.imageset/message_status_ack.png


BIN
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button.imageset/pb_button.png


BIN
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button_call.imageset/pb_button_call.png


BIN
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button_cc.imageset/pb_button_cc.png


BIN
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button_chat.imageset/pb_button_chat.png


BIN
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button_post.imageset/pb_button_post.png


BIN
appbuilder-ios/NexilisLite/NexilisLite/Resource/Assets.xcassets/pb_button_stream.imageset/pb_button_stream.png


+ 41 - 5
appbuilder-ios/NexilisLite/NexilisLite/Resource/Palio.storyboard

@@ -4,6 +4,7 @@
     <dependencies>
         <deployment identifier="iOS"/>
         <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="20020"/>
+        <capability name="Image references" minToolsVersion="12.0"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="System colors in document resources" minToolsVersion="11.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@@ -2500,7 +2501,7 @@
                                 <rect key="frame" x="0.0" y="776" width="414" height="60"/>
                                 <subviews>
                                     <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" showsVerticalScrollIndicator="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="Zwh-BN-IXY" customClass="CustomTextView" customModule="NexilisLite" customModuleProvider="target">
-                                        <rect key="frame" x="20" y="0.0" width="374" height="40"/>
+                                        <rect key="frame" x="65" y="0.0" width="329" height="40"/>
                                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                         <constraints>
                                             <constraint firstAttribute="height" constant="40" id="BM5-gS-VIN"/>
@@ -2518,15 +2519,31 @@
                                         </constraints>
                                         <state key="normal" image="Send-(White)"/>
                                     </button>
+                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gM4-Aa-Q5w">
+                                        <rect key="frame" x="20" y="0.0" width="40" height="40"/>
+                                        <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                        <constraints>
+                                            <constraint firstAttribute="width" constant="40" id="UWO-1I-dsC"/>
+                                            <constraint firstAttribute="height" constant="40" id="lHn-s4-3R4"/>
+                                        </constraints>
+                                        <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                        <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                        <state key="normal">
+                                            <imageReference key="image" image="gearshape.fill" catalog="system" symbolScale="large"/>
+                                        </state>
+                                    </button>
                                 </subviews>
                                 <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                 <constraints>
                                     <constraint firstItem="Zwh-BN-IXY" firstAttribute="top" secondItem="yLm-Hp-3ZU" secondAttribute="top" id="43A-hi-qkj"/>
                                     <constraint firstAttribute="bottom" secondItem="04I-YH-6sq" secondAttribute="bottom" constant="20" id="4Z5-vU-2FX"/>
                                     <constraint firstAttribute="trailing" secondItem="04I-YH-6sq" secondAttribute="trailing" constant="20" id="DOu-YD-eDT"/>
+                                    <constraint firstAttribute="bottom" secondItem="gM4-Aa-Q5w" secondAttribute="bottom" constant="20" id="L9b-1q-AaM"/>
+                                    <constraint firstItem="Zwh-BN-IXY" firstAttribute="leading" secondItem="gM4-Aa-Q5w" secondAttribute="trailing" constant="5" id="Qcf-9J-0Pw"/>
                                     <constraint firstAttribute="trailing" secondItem="Zwh-BN-IXY" secondAttribute="trailing" constant="20" id="dSb-xp-sPu"/>
-                                    <constraint firstItem="Zwh-BN-IXY" firstAttribute="leading" secondItem="yLm-Hp-3ZU" secondAttribute="leading" constant="20" id="jx0-o5-Ja0"/>
+                                    <constraint firstItem="Zwh-BN-IXY" firstAttribute="leading" secondItem="yLm-Hp-3ZU" secondAttribute="leading" constant="65" id="jx0-o5-Ja0"/>
                                     <constraint firstAttribute="bottom" secondItem="Zwh-BN-IXY" secondAttribute="bottom" constant="20" id="pR1-gQ-hpK"/>
+                                    <constraint firstItem="gM4-Aa-Q5w" firstAttribute="leading" secondItem="yLm-Hp-3ZU" secondAttribute="leading" constant="20" id="zM0-HA-9gq"/>
                                 </constraints>
                             </view>
                         </subviews>
@@ -2547,6 +2564,7 @@
                     </view>
                     <navigationItem key="navigationItem" id="ttN-eC-x85"/>
                     <connections>
+                        <outlet property="buttonAckConfidential" destination="gM4-Aa-Q5w" id="hsS-6J-JrH"/>
                         <outlet property="buttonSendChat" destination="04I-YH-6sq" id="vDa-0e-kJ9"/>
                         <outlet property="buttonSendFile" destination="2e8-Dd-Gfc" id="OL0-9W-Aal"/>
                         <outlet property="buttonSendImage" destination="A4V-Nn-duA" id="aFJ-JY-0Wp"/>
@@ -2658,7 +2676,7 @@
                                 <rect key="frame" x="0.0" y="776" width="414" height="60"/>
                                 <subviews>
                                     <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="bW3-fS-6Qv" customClass="CustomTextView" customModule="NexilisLite" customModuleProvider="target">
-                                        <rect key="frame" x="20" y="0.0" width="374" height="40"/>
+                                        <rect key="frame" x="65" y="0.0" width="329" height="40"/>
                                         <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                         <constraints>
                                             <constraint firstAttribute="height" constant="40" id="8Zi-nS-Q7O"/>
@@ -2676,15 +2694,31 @@
                                         </constraints>
                                         <state key="normal" image="Send-(White)"/>
                                     </button>
+                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KSG-YH-EbU">
+                                        <rect key="frame" x="20" y="0.0" width="40" height="40"/>
+                                        <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                        <constraints>
+                                            <constraint firstAttribute="width" constant="40" id="ftZ-Pv-8VZ"/>
+                                            <constraint firstAttribute="height" constant="40" id="gxc-0S-LGi"/>
+                                        </constraints>
+                                        <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                        <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                                        <state key="normal">
+                                            <imageReference key="image" image="gearshape.fill" catalog="system" symbolScale="large"/>
+                                        </state>
+                                    </button>
                                 </subviews>
                                 <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                                 <constraints>
                                     <constraint firstAttribute="trailing" secondItem="bW3-fS-6Qv" secondAttribute="trailing" constant="20" id="A5C-HP-gPH"/>
+                                    <constraint firstItem="bW3-fS-6Qv" firstAttribute="leading" secondItem="KSG-YH-EbU" secondAttribute="trailing" constant="5" id="GIm-9J-0dD"/>
                                     <constraint firstAttribute="bottom" secondItem="wLj-Ys-hxi" secondAttribute="bottom" constant="20" id="K4x-jy-PGW"/>
                                     <constraint firstAttribute="bottom" secondItem="bW3-fS-6Qv" secondAttribute="bottom" constant="20" id="NO5-lx-Don"/>
+                                    <constraint firstAttribute="bottom" secondItem="KSG-YH-EbU" secondAttribute="bottom" constant="20" id="Pa0-Id-LcE"/>
+                                    <constraint firstItem="KSG-YH-EbU" firstAttribute="leading" secondItem="fnQ-OG-1Lg" secondAttribute="leading" constant="20" id="ac0-0Z-NJu"/>
                                     <constraint firstItem="bW3-fS-6Qv" firstAttribute="top" secondItem="fnQ-OG-1Lg" secondAttribute="top" id="rwD-sI-rSc"/>
                                     <constraint firstAttribute="trailing" secondItem="wLj-Ys-hxi" secondAttribute="trailing" constant="20" id="v5x-A6-oiC"/>
-                                    <constraint firstItem="bW3-fS-6Qv" firstAttribute="leading" secondItem="fnQ-OG-1Lg" secondAttribute="leading" constant="20" id="xBy-8v-aCZ"/>
+                                    <constraint firstItem="bW3-fS-6Qv" firstAttribute="leading" secondItem="fnQ-OG-1Lg" secondAttribute="leading" constant="65" id="xBy-8v-aCZ"/>
                                 </constraints>
                             </view>
                         </subviews>
@@ -2704,6 +2738,7 @@
                         </constraints>
                     </view>
                     <connections>
+                        <outlet property="buttonAckConfidential" destination="KSG-YH-EbU" id="FGv-HW-Zgg"/>
                         <outlet property="buttonSendChat" destination="wLj-Ys-hxi" id="gcG-Se-fhm"/>
                         <outlet property="buttonSendFile" destination="4tF-ju-a54" id="Aem-TA-veP"/>
                         <outlet property="buttonSendImage" destination="f7n-Wy-Asx" id="DzS-5q-v5l"/>
@@ -2722,7 +2757,7 @@
                 </viewController>
                 <placeholder placeholderIdentifier="IBFirstResponder" id="e94-v4-EpE" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
             </objects>
-            <point key="canvasLocation" x="1710" y="66"/>
+            <point key="canvasLocation" x="1708.6956521739132" y="65.625"/>
         </scene>
         <!--Group Name View Controller-->
         <scene sceneID="ZZM-Uc-bB1">
@@ -2890,6 +2925,7 @@
         <image name="Voice-Record" width="500" height="500"/>
         <image name="arrow.triangle.2.circlepath.camera.fill" catalog="system" width="128" height="94"/>
         <image name="eye.fill" catalog="system" width="128" height="78"/>
+        <image name="gearshape.fill" catalog="system" width="128" height="121"/>
         <image name="ic_internal" width="128" height="128"/>
         <image name="message.circle.fill" catalog="system" width="128" height="121"/>
         <image name="message.fill" catalog="system" width="128" height="113"/>

+ 20 - 2
appbuilder-ios/NexilisLite/NexilisLite/Resource/PreviewAttachmentImageVideo.xib

@@ -11,6 +11,7 @@
     <objects>
         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PreviewAttachmentImageVideo" customModule="NexilisLite" customModuleProvider="target">
             <connections>
+                <outlet property="buttonAckConfidential" destination="m6t-Me-xFW" id="36g-1Y-Jgr"/>
                 <outlet property="buttonCancel" destination="kDJ-TL-vNL" id="Xo6-YX-9ma"/>
                 <outlet property="buttonSend" destination="fNr-UI-Smq" id="m00-6U-HAF"/>
                 <outlet property="constraintButtonSend" destination="CSE-Zv-Ou4" id="McW-YL-LYy"/>
@@ -35,7 +36,7 @@
                             <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                         </imageView>
                         <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="TU8-ei-nsO" customClass="CustomTextView" customModule="NexilisLite" customModuleProvider="target">
-                            <rect key="frame" x="20" y="758" width="374" height="40"/>
+                            <rect key="frame" x="65" y="758" width="329" height="40"/>
                             <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
                             <constraints>
                                 <constraint firstAttribute="height" constant="40" id="4X4-Hj-TzA"/>
@@ -44,18 +45,34 @@
                             <fontDescription key="fontDescription" type="system" pointSize="14"/>
                             <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
                         </textView>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="m6t-Me-xFW">
+                            <rect key="frame" x="20" y="758" width="40" height="40"/>
+                            <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <constraints>
+                                <constraint firstAttribute="width" constant="40" id="osz-OV-n7L"/>
+                                <constraint firstAttribute="height" constant="40" id="rF0-lI-vgS"/>
+                            </constraints>
+                            <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                            <state key="normal">
+                                <imageReference key="image" image="gearshape.fill" catalog="system" symbolScale="large"/>
+                            </state>
+                        </button>
                     </subviews>
                     <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <constraints>
                         <constraint firstAttribute="trailing" secondItem="TU8-ei-nsO" secondAttribute="trailing" constant="20" id="22v-0k-rSm"/>
                         <constraint firstItem="1kP-2x-Lvs" firstAttribute="top" secondItem="fUG-ls-mSU" secondAttribute="top" id="BRw-Qf-Iem"/>
-                        <constraint firstItem="TU8-ei-nsO" firstAttribute="leading" secondItem="fUG-ls-mSU" secondAttribute="leading" constant="20" id="EgI-40-Ggf"/>
+                        <constraint firstItem="TU8-ei-nsO" firstAttribute="leading" secondItem="fUG-ls-mSU" secondAttribute="leading" constant="65" id="EgI-40-Ggf"/>
                         <constraint firstAttribute="trailing" secondItem="1kP-2x-Lvs" secondAttribute="trailing" id="Fbf-E3-xAu"/>
                         <constraint firstItem="1kP-2x-Lvs" firstAttribute="leading" secondItem="fUG-ls-mSU" secondAttribute="leading" id="IhT-0L-tSH"/>
                         <constraint firstAttribute="bottom" secondItem="1kP-2x-Lvs" secondAttribute="bottom" id="LnA-sx-iQR"/>
                         <constraint firstItem="1kP-2x-Lvs" firstAttribute="height" secondItem="fUG-ls-mSU" secondAttribute="height" id="Vg8-Eq-NiL"/>
                         <constraint firstItem="1kP-2x-Lvs" firstAttribute="width" secondItem="fUG-ls-mSU" secondAttribute="width" id="bXj-2J-Lqd"/>
                         <constraint firstAttribute="bottom" secondItem="TU8-ei-nsO" secondAttribute="bottom" constant="20" id="cJn-uR-0Xn"/>
+                        <constraint firstItem="m6t-Me-xFW" firstAttribute="leading" secondItem="fUG-ls-mSU" secondAttribute="leading" constant="20" id="qbe-53-bQC"/>
+                        <constraint firstItem="TU8-ei-nsO" firstAttribute="leading" secondItem="m6t-Me-xFW" secondAttribute="trailing" constant="5" id="ujD-Il-wdU"/>
+                        <constraint firstAttribute="bottom" secondItem="m6t-Me-xFW" secondAttribute="bottom" constant="20" id="zqw-Ge-f8S"/>
                     </constraints>
                 </scrollView>
                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kDJ-TL-vNL" userLabel="Button Cancel">
@@ -97,6 +114,7 @@
     </objects>
     <resources>
         <image name="Send-(White)" width="500" height="500"/>
+        <image name="gearshape.fill" catalog="system" width="128" height="121"/>
         <image name="xmark" catalog="system" width="128" height="113"/>
     </resources>
 </document>

+ 3 - 0
appbuilder-ios/NexilisLite/NexilisLite/Resource/id.lproj/Localizable.strings

@@ -130,3 +130,6 @@
 "Access Admin / Internal Features" = "Akses Fitur Admin / Internal";
 "Access Admin Features" = "Akses Fitur Admin";
 "Access Internal Features" = "Akses Fitur Internal";
+"Message Mode" = "Modus Pesan";
+"Confidential Message" = "Pesan Rahasia";
+"Confirmation Message" = "Pesan Konfirmasi";

+ 146 - 30
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -11,6 +11,8 @@ import AVFoundation
 import QuickLook
 import ReadabilityKit
 import Photos
+import NotificationBannerSwift
+import nuSDKService
 
 public class EditorGroup: UIViewController {
     @IBOutlet var viewButton: UIView!
@@ -27,6 +29,7 @@ public class EditorGroup: UIViewController {
     @IBOutlet var constraintTopTextField: NSLayoutConstraint!
     @IBOutlet var constraintBottomAttachment: NSLayoutConstraint!
     @IBOutlet var viewTextfield: UIView!
+    @IBOutlet weak var buttonAckConfidential: UIButton!
     public var dataGroup: [String: Any?] = [:]
     public var dataTopic: [String: Any?] = [:]
     var dataMessages: [[String: Any?]] = []
@@ -49,6 +52,7 @@ public class EditorGroup: UIViewController {
     let labelCounter = UILabel()
     let containerActionGroup = UIView()
     var removed = false
+    var isAck = false
     var copySession = false
     var forwardSession = false
     var deleteSession = false
@@ -97,6 +101,8 @@ public class EditorGroup: UIViewController {
         
         buttonSendChat.circle()
         buttonSendChat.addTarget(self, action: #selector(sendTapped), for: .touchUpInside)
+        buttonAckConfidential.circle()
+        buttonAckConfidential.addTarget(self, action: #selector(showChooserACKConfidential), for: .touchUpInside)
         textFieldSend.layer.cornerRadius = textFieldSend.maxCornerRadius()
         textFieldSend.layer.borderWidth = 1.0
         textFieldSend.text = "Send message".localized()
@@ -1042,6 +1048,34 @@ public class EditorGroup: UIViewController {
         }
     }
     
+    @objc func showChooserACKConfidential() {
+        let alertController = UIAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet)
+        let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
+        let ackAction = UIAlertAction(title: "Confirmation Message".localized(), style: .default, handler: { (UIAlertAction) in
+            if !self.isAck {
+                self.isAck = true
+                self.buttonAckConfidential.setImage(imageAck, for: .normal)
+            }
+        })
+        ackAction.setValue(imageAck, forKey: "image")
+        alertController.addAction(ackAction)
+        alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in
+            self.isAck = false
+            self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
+        }))
+        self.present(alertController, animated: true, completion: nil)
+    }
+    
+    public func setAckConfidential(isAck: Bool, isConfidential: Bool) {
+        self.isAck = isAck
+        let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
+        if isAck {
+            buttonAckConfidential.setImage(imageAck, for: .normal)
+        } else {
+            self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
+        }
+    }
+    
     @objc func sendTapped() {
         sendChat(message_text: textFieldSend.text!, viewController: self)
     }
@@ -1066,7 +1100,11 @@ public class EditorGroup: UIViewController {
         }
         let message_text = message_text.trimmingCharacters(in: .whitespacesAndNewlines)
         let idMe = UserDefaults.standard.string(forKey: "me") as String?
-        let message = CoreMessage_TMessageBank.sendMessage(l_pin: dataGroup["group_id"] as! String, message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: dataTopic["chat_id"] as! String, is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: "")
+        var opposite_pin = self.dataGroup["group_id"] as! String
+        if (self.dataTopic["chat_id"] as! String != "") {
+            opposite_pin = self.dataTopic["chat_id"] as! String
+        }
+        let message = CoreMessage_TMessageBank.sendMessage(l_pin: dataGroup["group_id"] as! String, message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: dataTopic["chat_id"] as! String, is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin)
         Nexilis.addQueueMessage(message: message)
         let messageId = String(message.mBodies[CoreMessage_TMessageKey.MESSAGE_ID]!)
         var row: [String: Any?] = [:]
@@ -1378,6 +1416,8 @@ extension EditorGroup: ImageVideoPickerDelegate, PreviewAttachmentImageVideoDele
             }
             previewImageVC.modalPresentationStyle = .custom
             previewImageVC.delegate = self
+            previewImageVC.isGroup = true
+            previewImageVC.isAck = self.isAck
             self.present(previewImageVC, animated: true, completion: nil)
         }
     }
@@ -2377,7 +2417,11 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         let timeMessage = UILabel()
         cellMessage.contentView.addSubview(timeMessage)
         timeMessage.translatesAutoresizingMaskIntoConstraints = false
-        timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+        if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
+            timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+        } else {
+            timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+        }
         
         let messageText = UILabel()
         containerMessage.addSubview(messageText)
@@ -2458,7 +2502,11 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             
             containerMessage.topAnchor.constraint(equalTo: cellMessage.contentView.topAnchor, constant: 5).isActive = true
             containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cellMessage.contentView.leadingAnchor, constant: 60).isActive = true
-            containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
+                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+            } else {
+                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+            }
             containerMessage.trailingAnchor.constraint(equalTo: profileMessage.leadingAnchor, constant: -5).isActive = true
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
             containerMessage.layer.cornerRadius = 10.0
@@ -2563,7 +2611,11 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             }
             
             containerMessage.leadingAnchor.constraint(equalTo: profileMessage.trailingAnchor, constant: 5).isActive = true
-            containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
+                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+            } else {
+                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+            }
             containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cellMessage.contentView.trailingAnchor, constant: -60).isActive = true
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
             if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1") {
@@ -2612,6 +2664,30 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
             imageStared.tintColor = .systemYellow
         }
         
+        if dataMessages[indexPath.row]["read_receipts"] as? String == "8" {
+            let imageAckView = UIImageView()
+            var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            if dataMessages[indexPath.row]["status"] as? String == "8" {
+                imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            }
+            imageAckView.image = imageAck
+            cellMessage.contentView.addSubview(imageAckView)
+            imageAckView.translatesAutoresizingMaskIntoConstraints = false
+            imageAckView.widthAnchor.constraint(equalToConstant: 30).isActive = true
+            imageAckView.heightAnchor.constraint(equalToConstant: 30).isActive = true
+            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
+                imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+                imageAckView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
+            } else {
+                imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+                imageAckView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
+                let tap = ObjectGesture(target: self, action: #selector(tapAck(_:)))
+                tap.indexPath = indexPath
+                imageAckView.addGestureRecognizer(tap)
+                imageAckView.isUserInteractionEnabled = true
+            }
+        }
+        
         messageText.numberOfLines = 0
         messageText.lineBreakMode = .byWordWrapping
         containerMessage.addSubview(messageText)
@@ -3108,6 +3184,46 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         return cellMessage
     }
     
+    @objc func tapAck(_ sender: ObjectGesture) {
+        let indexPath = sender.indexPath
+        let dataMessages = self.dataMessages.filter({ $0["chat_date"] as! String == dataDates[indexPath.section]})
+        if dataMessages[indexPath.row]["status"] as! String == "8" {
+            return
+        }
+        if !CheckConnection.isConnectedToNetwork()  || API.nGetCLXConnState() == 0 {
+            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+            imageView.tintColor = .white
+            let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+            banner.show()
+            return
+        }
+        DispatchQueue.global().async {
+            var opposite_pin = self.dataGroup["group_id"] as! String
+            if (self.dataTopic["chat_id"] as! String != "") {
+                opposite_pin = self.dataTopic["chat_id"] as! String
+            }
+            let result = Nexilis.write(message: CoreMessage_TMessageBank.getAckLocationMessage(f_pin: dataMessages[indexPath.row]["f_pin"] as! String, message_id: dataMessages[indexPath.row]["message_id"] as! String, l_pin: opposite_pin, server_date: "\(Date().millisecondsSince1970)", message_scope_id: dataMessages[indexPath.row]["message_scope_id"] as! String, longitude: "", latitude: "", description: ""))
+            if result != nil {
+                Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                        "status" : "8"
+                    ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"] as! String)'")
+                })
+                DispatchQueue.main.async {
+                    if let index = self.dataMessages.firstIndex(where: {$0["message_id"] as? String == dataMessages[indexPath.row]["message_id"] as? String}) {
+                        self.dataMessages[index]["status"] = "8"
+                        let section = self.dataDates.firstIndex(of: self.dataMessages[index]["chat_date"] as! String)
+                        let row = self.dataMessages.filter({$0["chat_date"] as! String == self.dataMessages[index]["chat_date"] as! String}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[indexPath.row]["message_id"] as? String})
+                        if row != nil && section != nil {
+                            self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
+                        }
+                        self.showToast(message: "Confirmation Success.".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
+                    }
+                }
+            }
+        }
+    }
+    
     @objc func contentMessageTapped(_ sender: ObjectGesture) {
         let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
         let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
@@ -3339,32 +3455,32 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
         UIApplication.shared.open(url)
     }
     
-    // public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
-    //     if copySession || forwardSession || deleteSession {
-    //         return nil
-    //     }
-    //     let idMe = UserDefaults.standard.string(forKey: "me") as String?
-    //     if (dataMessages[indexPath.row]["f_pin"] as? String != idMe) {
-    //         return nil
-    //     }
-    //     let messageInfoVC = MessageInfo()
-    //     self.navigationController?.show(messageInfoVC, sender: nil)
-    //     return UISwipeActionsConfiguration()
-    // }
-    
-    // public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
-    //     if copySession || forwardSession || deleteSession {
-    //         return nil
-    //     }
-    //     let action = UIContextualAction(style: .normal,
-    //                                     title: "") { [weak self] (action, view, completionHandler) in
-    //                                         self?.handleReply(indexPath: indexPath)
-    //                                         completionHandler(true)
-    //     }
-    //     action.backgroundColor = .white
-    //     action.image = UIImage(systemName: "arrowshape.turn.up.left.fill")?.withTintColor(.black, renderingMode: .alwaysOriginal)
-    //     return UISwipeActionsConfiguration(actions: [action])
-    // }
+//    public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
+//        if copySession || forwardSession || deleteSession {
+//            return nil
+//        }
+//        let idMe = UserDefaults.standard.string(forKey: "me") as String?
+//        if (dataMessages[indexPath.row]["f_pin"] as? String != idMe) {
+//            return nil
+//        }
+//        let messageInfoVC = MessageInfo()
+//        self.navigationController?.show(messageInfoVC, sender: nil)
+//        return UISwipeActionsConfiguration()
+//    }
+//
+//    public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
+//        if copySession || forwardSession || deleteSession {
+//            return nil
+//        }
+//        let action = UIContextualAction(style: .normal,
+//                                        title: "") { [weak self] (action, view, completionHandler) in
+//                                            self?.handleReply(indexPath: indexPath)
+//                                            completionHandler(true)
+//        }
+//        action.backgroundColor = .white
+//        action.image = UIImage(systemName: "arrowshape.turn.up.left.fill")?.withTintColor(.black, renderingMode: .alwaysOriginal)
+//        return UISwipeActionsConfiguration(actions: [action])
+//    }
     
     private func handleReply(indexPath: IndexPath) {
         self.deleteReplyView()

+ 149 - 7
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -29,6 +29,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     @IBOutlet var constraintTopTextField: NSLayoutConstraint!
     @IBOutlet var constraintBottomAttachment: NSLayoutConstraint!
     @IBOutlet var viewTextfield: UIView!
+    @IBOutlet weak var buttonAckConfidential: UIButton!
     public var dataPerson: [String: String?] = [:]
     var dataMessages: [[String: Any?]] = []
     var dataDates: [String] = []
@@ -60,6 +61,8 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     let containerMultpileSelectSession = UIView()
     let containerAction = UIView()
     var removed = false
+    var isConfidential = false
+    var isAck = false
     let viewSticker = UIView()
     let containerLink = UIView()
     let containerPreviewReply = UIView()
@@ -110,6 +113,8 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         
         buttonSendChat.circle()
         buttonSendChat.addTarget(self, action: #selector(sendTapped), for: .touchUpInside)
+        buttonAckConfidential.circle()
+        buttonAckConfidential.addTarget(self, action: #selector(showChooserACKConfidential), for: .touchUpInside)
         textFieldSend.layer.cornerRadius = textFieldSend.maxCornerRadius()
         textFieldSend.layer.borderWidth = 1.0
         textFieldSend.text = "Send message".localized()
@@ -149,7 +154,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         
         if dataMessageForward != nil {
             for i in 0..<dataMessageForward!.count {
-                sendChat(message_scope_id: "3", status: "2", message_text: dataMessageForward![i]["message_text"] as! String, credential: "0", attachment_flag: dataMessageForward![i]["attachment_flag"] as! String, ex_blog_id: "", message_large_text: "", ex_format: "", image_id: dataMessageForward![i]["image_id"] as! String, audio_id: dataMessageForward![i]["audio_id"] as! String, video_id: dataMessageForward![i]["video_id"] as! String, file_id: dataMessageForward![i]["file_id"] as! String, thumb_id: dataMessageForward![i]["thumb_id"] as! String, reff_id: "", read_receipts: "", chat_id: "", is_call_center: "0", call_center_id: "", viewController: self)
+                sendChat(message_scope_id: "3", status: "2", message_text: dataMessageForward![i]["message_text"] as! String, credential: "0", attachment_flag: dataMessageForward![i]["attachment_flag"] as! String, ex_blog_id: "", message_large_text: "", ex_format: "", image_id: dataMessageForward![i]["image_id"] as! String, audio_id: dataMessageForward![i]["audio_id"] as! String, video_id: dataMessageForward![i]["video_id"] as! String, file_id: dataMessageForward![i]["file_id"] as! String, thumb_id: dataMessageForward![i]["thumb_id"] as! String, reff_id: "", read_receipts: dataMessageForward![i]["read_receipts"] as! String, chat_id: "", is_call_center: "0", call_center_id: "", viewController: self)
             }
             dataMessageForward = nil
         }
@@ -701,7 +706,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     row["video_id"] = cursorData.string(forColumnIndex: 8)
                     row["image_id"] = cursorData.string(forColumnIndex: 9)
                     row["thumb_id"] = cursorData.string(forColumnIndex: 10)
-                    row["read_receipts"] = cursorData.int(forColumnIndex: 11)
+                    row["read_receipts"] = cursorData.string(forColumnIndex: 11)
                     row["chat_id"] = cursorData.string(forColumnIndex: 12)
                     row["file_id"] = cursorData.string(forColumnIndex: 13)
                     row["attachment_flag"] = cursorData.string(forColumnIndex: 14)
@@ -1040,7 +1045,11 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
                     } else {
                         row["thumb_id"] = ""
                     }
-                    row["read_receipts"] = 0
+                    if (chatData.keys.contains(CoreMessage_TMessageKey.READ_RECEIPTS)) {
+                        row["read_receipts"] = chatData[CoreMessage_TMessageKey.READ_RECEIPTS]
+                    } else {
+                        row["read_receipts"] = ""
+                    }
                     row["chat_id"] = ""
                     if (chatData.keys.contains(CoreMessage_TMessageKey.FILE_ID)) {
                         row["file_id"] = chatData[CoreMessage_TMessageKey.FILE_ID]
@@ -1477,6 +1486,54 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         sendChat(message_text: textFieldSend.text!, viewController: self)
     }
     
+    @objc func showChooserACKConfidential() {
+        let alertController = UIAlertController(title: "Message Mode".localized(), message: "Select".localized() + " " + "Message Mode".localized(), preferredStyle: .actionSheet)
+        let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
+        let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
+        let confidentialAction = UIAlertAction(title: "Confidential Message".localized(), style: .default, handler: { (UIAlertAction) in
+            if !self.isConfidential {
+                self.isConfidential = true
+                self.buttonAckConfidential.setImage(imageConfidential, for: .normal)
+            }
+            if self.isAck {
+                self.isAck = false
+            }
+        })
+        let ackAction = UIAlertAction(title: "Confirmation Message".localized(), style: .default, handler: { (UIAlertAction) in
+            if !self.isAck {
+                self.isAck = true
+                self.buttonAckConfidential.setImage(imageAck, for: .normal)
+            }
+            if self.isConfidential {
+                self.isConfidential = false
+            }
+        })
+        confidentialAction.setValue(imageConfidential, forKey: "image")
+        ackAction.setValue(imageAck, forKey: "image")
+        alertController.addAction(confidentialAction)
+        alertController.addAction(ackAction)
+        alertController.addAction(UIAlertAction(title: "Cancel".localized(), style: .cancel, handler: { (UIAlertAction) in
+            self.isConfidential = false
+            self.isAck = false
+            self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
+        }))
+        self.present(alertController, animated: true, completion: nil)
+    }
+    
+    public func setAckConfidential(isAck: Bool, isConfidential: Bool) {
+        self.isConfidential = isConfidential
+        self.isAck = isAck
+        let imageConfidential = resizeImage(image: UIImage(named: "confidential_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
+        let imageAck = resizeImage(image: UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!, targetSize: CGSize(width: 30, height: 30)).withRenderingMode(.alwaysOriginal)
+        if isAck {
+            buttonAckConfidential.setImage(imageAck, for: .normal)
+        } else if isConfidential {
+            buttonAckConfidential.setImage(imageConfidential, for: .normal)
+        } else {
+            self.buttonAckConfidential.setImage(UIImage(systemName: "gearshape.fill", withConfiguration: UIImage.SymbolConfiguration(scale: .large))?.withTintColor(.white).withRenderingMode(.alwaysTemplate), for: .normal)
+        }
+    }
+    
     @objc func addRoom(sender: UIBarButtonItem) {
         let controller = QmeraCallContactViewController()
         controller.isDismiss = { user in
@@ -1670,6 +1727,14 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         if isContactCenter {
             opposite_pin = fPinContacCenter
         }
+        var credential = credential
+        if isConfidential {
+            credential = "1"
+        }
+        var read_receipts = read_receipts
+        if isAck {
+            read_receipts = "8"
+        }
         sendTyping(l_pin: l_pin, isTyping: true)
         let message = CoreMessage_TMessageBank.sendMessage(l_pin: l_pin, message_scope_id: message_scope_id, status: status, message_text: message_text, credential: credential, attachment_flag: attachment_flag, ex_blog_id: ex_blog_id, message_large_text: message_large_text, ex_format: ex_format, image_id: image_id, audio_id: audio_id, video_id: video_id, file_id: file_id, thumb_id: thumb_id, reff_id: reff_id, read_receipts: read_receipts, chat_id: chat_id, is_call_center: is_call_center, call_center_id: call_center_id, opposite_pin: opposite_pin)
         Nexilis.addQueueMessage(message: message)
@@ -1686,7 +1751,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
         row["video_id"] = video_id
         row["image_id"] = image_id
         row["thumb_id"] = thumb_id
-        row["read_receipts"] = 0
+        row["read_receipts"] = read_receipts
         row["chat_id"] = chat_id
         row["file_id"] = file_id
         row["attachment_flag"] = attachment_flag
@@ -2165,6 +2230,8 @@ extension EditorPersonal: PreviewAttachmentImageVideoDelegate {
             }
             previewImageVC.modalPresentationStyle = .custom
             previewImageVC.delegate = self
+            previewImageVC.isAck = self.isAck
+            previewImageVC.isConfidential = self.isConfidential
             self.present(previewImageVC, animated: true, completion: nil)
         }
     }
@@ -3435,7 +3502,11 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         let timeMessage = UILabel()
         cellMessage.contentView.addSubview(timeMessage)
         timeMessage.translatesAutoresizingMaskIntoConstraints = false
-        timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+        if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
+            timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+        } else {
+            timeMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+        }
         
         let statusMessage = UIImageView()
         
@@ -3488,7 +3559,11 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         
         if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
             containerMessage.leadingAnchor.constraint(greaterThanOrEqualTo: cellMessage.contentView.leadingAnchor, constant: 60).isActive = true
-            containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
+                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+            } else {
+                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+            }
             if isContactCenter {
                 containerMessage.topAnchor.constraint(equalTo: nameSender.bottomAnchor).isActive = true
                 containerMessage.trailingAnchor.constraint(equalTo: profileMessage.leadingAnchor, constant: -5).isActive = true
@@ -3519,6 +3594,8 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                     statusMessage.image = UIImage(named: "checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
                 } else if (dataMessages[indexPath.row]["status"]! as! String == "3") {
                     statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.lightGray)
+                } else if (dataMessages[indexPath.row]["status"]! as! String == "8") {
+                    statusMessage.image = UIImage(named: "message_status_ack", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
                 } else {
                     statusMessage.image = UIImage(named: "double-checklist", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withTintColor(UIColor.systemBlue)
                 }
@@ -3576,7 +3653,11 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
                     containerMessage.leadingAnchor.constraint(equalTo: cellMessage.contentView.leadingAnchor, constant: 15).isActive = true
                 }
             }
-            containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+            if (dataMessages[indexPath.row]["read_receipts"] as? String) == "8" {
+                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -40).isActive = true
+            } else {
+                containerMessage.bottomAnchor.constraint(equalTo: cellMessage.contentView.bottomAnchor, constant: -5).isActive = true
+            }
             containerMessage.trailingAnchor.constraint(lessThanOrEqualTo: cellMessage.contentView.trailingAnchor, constant: -60).isActive = true
             containerMessage.widthAnchor.constraint(greaterThanOrEqualToConstant: 46).isActive = true
             if dataMessages[indexPath.row]["attachment_flag"] as? String == "11" && dataMessages[indexPath.row]["reff_id"]as? String == "" && (dataMessages[indexPath.row]["lock"] == nil || dataMessages[indexPath.row]["lock"] as! String != "1") {
@@ -3609,6 +3690,30 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
             imageStared.tintColor = .systemYellow
         }
         
+        if dataMessages[indexPath.row]["read_receipts"] as? String == "8" {
+            let imageAckView = UIImageView()
+            var imageAck = UIImage(named: "ack_icon_gray", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            if dataMessages[indexPath.row]["status"] as? String == "8" {
+                imageAck = UIImage(named: "ack_icon", in: Bundle.resourceBundle(for: Nexilis.self), with: nil)!.withRenderingMode(.alwaysOriginal)
+            }
+            imageAckView.image = imageAck
+            cellMessage.contentView.addSubview(imageAckView)
+            imageAckView.translatesAutoresizingMaskIntoConstraints = false
+            imageAckView.widthAnchor.constraint(equalToConstant: 30).isActive = true
+            imageAckView.heightAnchor.constraint(equalToConstant: 30).isActive = true
+            if (dataMessages[indexPath.row]["f_pin"] as? String == idMe) {
+                imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+                imageAckView.trailingAnchor.constraint(equalTo: containerMessage.leadingAnchor, constant: 30).isActive = true
+            } else {
+                imageAckView.topAnchor.constraint(equalTo: containerMessage.bottomAnchor, constant: 5).isActive = true
+                imageAckView.leadingAnchor.constraint(equalTo: containerMessage.trailingAnchor, constant: -30).isActive = true
+                let tap = ObjectGesture(target: self, action: #selector(tapAck(_:)))
+                tap.indexPath = indexPath
+                imageAckView.addGestureRecognizer(tap)
+                imageAckView.isUserInteractionEnabled = true
+            }
+        }
+        
         let messageText = UILabel()
         messageText.numberOfLines = 0
         messageText.lineBreakMode = .byWordWrapping
@@ -4122,6 +4227,42 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
         return cellMessage
     }
     
+    @objc func tapAck(_ sender: ObjectGesture) {
+        let indexPath = sender.indexPath
+        let dataMessages = self.dataMessages.filter({ $0["chat_date"] as! String == dataDates[indexPath.section]})
+        if dataMessages[indexPath.row]["status"] as! String == "8" {
+            return
+        }
+        if !CheckConnection.isConnectedToNetwork()  || API.nGetCLXConnState() == 0 {
+            let imageView = UIImageView(image: UIImage(systemName: "xmark.circle.fill"))
+            imageView.tintColor = .white
+            let banner = FloatingNotificationBanner(title: "Check your connection".localized(), subtitle: nil, titleFont: UIFont.systemFont(ofSize: 16), titleColor: nil, titleTextAlign: .left, subtitleFont: nil, subtitleColor: nil, subtitleTextAlign: nil, leftView: imageView, rightView: nil, style: .danger, colors: nil, iconPosition: .center)
+            banner.show()
+            return
+        }
+        DispatchQueue.global().async {
+            let result = Nexilis.write(message: CoreMessage_TMessageBank.getAckLocationMessage(f_pin: dataMessages[indexPath.row]["f_pin"] as! String, message_id: dataMessages[indexPath.row]["message_id"] as! String, l_pin: dataMessages[indexPath.row]["l_pin"] as! String, server_date: "\(Date().millisecondsSince1970)", message_scope_id: dataMessages[indexPath.row]["message_scope_id"] as! String, longitude: "", latitude: "", description: ""))
+            if result != nil {
+                Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                    _ = Database.shared.updateRecord(fmdb: fmdb, table: "MESSAGE", cvalues: [
+                        "status" : "8"
+                    ], _where: "message_id = '\(dataMessages[indexPath.row]["message_id"] as! String)'")
+                })
+                DispatchQueue.main.async {
+                    if let index = self.dataMessages.firstIndex(where: {$0["message_id"] as? String == dataMessages[indexPath.row]["message_id"] as? String}) {
+                        self.dataMessages[index]["status"] = "8"
+                        let section = self.dataDates.firstIndex(of: self.dataMessages[index]["chat_date"] as! String)
+                        let row = self.dataMessages.filter({$0["chat_date"] as! String == self.dataMessages[index]["chat_date"] as! String}).firstIndex(where: { $0["message_id"] as? String == self.dataMessages[indexPath.row]["message_id"] as? String})
+                        if row != nil && section != nil {
+                            self.tableChatView.reloadRows(at: [IndexPath(row: row!, section: section!)], with: .none)
+                        }
+                        self.showToast(message: "Confirmation Success.".localized(), font: UIFont.systemFont(ofSize: 12, weight: .medium), controller: self)
+                    }
+                }
+            }
+        }
+    }
+    
 //    public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
 //        let velocity : CGPoint = gestureRecognizer.location(in: tableChatView)
 //        if velocity.x < 0 {
@@ -4442,6 +4583,7 @@ extension EditorPersonal: UITableViewDelegate, UITableViewDataSource {
 //    }
     
     private func handleReply(indexPath: IndexPath) {
+        let dataMessages = self.dataMessages.filter({ $0["chat_date"] as! String == dataDates[indexPath.section]})
         self.deleteReplyView()
         self.textFieldSend.becomeFirstResponder()
         self.reffId = dataMessages[indexPath.row]["message_id"] as? String

+ 21 - 3
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/PreviewAttachmentImageVideo.xib

@@ -11,6 +11,7 @@
     <objects>
         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="PreviewAttachmentImageVideo" customModule="NexilisLite" customModuleProvider="target">
             <connections>
+                <outlet property="buttonAckConfidential" destination="m6t-Me-xFW" id="36g-1Y-Jgr"/>
                 <outlet property="buttonCancel" destination="kDJ-TL-vNL" id="Xo6-YX-9ma"/>
                 <outlet property="buttonSend" destination="fNr-UI-Smq" id="m00-6U-HAF"/>
                 <outlet property="constraintButtonSend" destination="CSE-Zv-Ou4" id="McW-YL-LYy"/>
@@ -35,7 +36,7 @@
                             <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                         </imageView>
                         <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" showsHorizontalScrollIndicator="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="TU8-ei-nsO" customClass="CustomTextView" customModule="NexilisLite" customModuleProvider="target">
-                            <rect key="frame" x="20" y="758" width="374" height="40"/>
+                            <rect key="frame" x="65" y="758" width="329" height="40"/>
                             <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
                             <constraints>
                                 <constraint firstAttribute="height" constant="40" id="4X4-Hj-TzA"/>
@@ -44,18 +45,34 @@
                             <fontDescription key="fontDescription" type="system" pointSize="14"/>
                             <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
                         </textView>
+                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="m6t-Me-xFW">
+                            <rect key="frame" x="20" y="758" width="40" height="40"/>
+                            <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <constraints>
+                                <constraint firstAttribute="width" constant="40" id="osz-OV-n7L"/>
+                                <constraint firstAttribute="height" constant="40" id="rF0-lI-vgS"/>
+                            </constraints>
+                            <color key="tintColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            <inset key="imageEdgeInsets" minX="0.0" minY="0.0" maxX="2.2250738585072014e-308" maxY="0.0"/>
+                            <state key="normal">
+                                <imageReference key="image" image="gearshape.fill" catalog="system" symbolScale="large"/>
+                            </state>
+                        </button>
                     </subviews>
                     <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <constraints>
                         <constraint firstAttribute="trailing" secondItem="TU8-ei-nsO" secondAttribute="trailing" constant="20" id="22v-0k-rSm"/>
                         <constraint firstItem="1kP-2x-Lvs" firstAttribute="top" secondItem="fUG-ls-mSU" secondAttribute="top" id="BRw-Qf-Iem"/>
-                        <constraint firstItem="TU8-ei-nsO" firstAttribute="leading" secondItem="fUG-ls-mSU" secondAttribute="leading" constant="20" id="EgI-40-Ggf"/>
+                        <constraint firstItem="TU8-ei-nsO" firstAttribute="leading" secondItem="fUG-ls-mSU" secondAttribute="leading" constant="65" id="EgI-40-Ggf"/>
                         <constraint firstAttribute="trailing" secondItem="1kP-2x-Lvs" secondAttribute="trailing" id="Fbf-E3-xAu"/>
                         <constraint firstItem="1kP-2x-Lvs" firstAttribute="leading" secondItem="fUG-ls-mSU" secondAttribute="leading" id="IhT-0L-tSH"/>
                         <constraint firstAttribute="bottom" secondItem="1kP-2x-Lvs" secondAttribute="bottom" id="LnA-sx-iQR"/>
                         <constraint firstItem="1kP-2x-Lvs" firstAttribute="height" secondItem="fUG-ls-mSU" secondAttribute="height" id="Vg8-Eq-NiL"/>
                         <constraint firstItem="1kP-2x-Lvs" firstAttribute="width" secondItem="fUG-ls-mSU" secondAttribute="width" id="bXj-2J-Lqd"/>
                         <constraint firstAttribute="bottom" secondItem="TU8-ei-nsO" secondAttribute="bottom" constant="20" id="cJn-uR-0Xn"/>
+                        <constraint firstItem="m6t-Me-xFW" firstAttribute="leading" secondItem="fUG-ls-mSU" secondAttribute="leading" constant="20" id="qbe-53-bQC"/>
+                        <constraint firstItem="TU8-ei-nsO" firstAttribute="leading" secondItem="m6t-Me-xFW" secondAttribute="trailing" constant="5" id="ujD-Il-wdU"/>
+                        <constraint firstAttribute="bottom" secondItem="m6t-Me-xFW" secondAttribute="bottom" constant="20" id="zqw-Ge-f8S"/>
                     </constraints>
                 </scrollView>
                 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kDJ-TL-vNL" userLabel="Button Cancel">
@@ -65,7 +82,7 @@
                         <constraint firstAttribute="width" constant="40" id="axp-R5-jBp"/>
                         <constraint firstAttribute="height" constant="40" id="xsU-84-Xow"/>
                     </constraints>
-                    <color key="tintColor" red="0.41176470590000003" green="0.27058823529999998" blue="0.64705882349999999" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
+                    <color key="tintColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
                     <state key="normal">
                         <imageReference key="image" image="xmark" catalog="system" symbolScale="large"/>
                     </state>
@@ -97,6 +114,7 @@
     </objects>
     <resources>
         <image name="Send-(White)" width="500" height="500"/>
+        <image name="gearshape.fill" catalog="system" width="128" height="121"/>
         <image name="xmark" catalog="system" width="128" height="113"/>
     </resources>
 </document>