alqindiirsyam 2 năm trước cách đây
mục cha
commit
3f923b2aeb

+ 117 - 104
appbuilder-ios/NexilisLite/NexilisLite/Resource/Palio.storyboard

@@ -2474,6 +2474,58 @@
                         <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
+                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" indicatorStyle="black" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="tSE-6b-2zD">
+                                <rect key="frame" x="0.0" y="0.0" width="414" height="766"/>
+                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            </tableView>
+                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yLm-Hp-3ZU">
+                                <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="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"/>
+                                        </constraints>
+                                        <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
+                                        <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
+                                    </textView>
+                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="04I-YH-6sq">
+                                        <rect key="frame" x="354" 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="SRf-xt-2cJ"/>
+                                            <constraint firstAttribute="height" constant="40" id="tq8-ze-QJr"/>
+                                        </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 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="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>
                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Xda-XC-yEA">
                                 <rect key="frame" x="0.0" y="836" width="414" height="60"/>
                                 <subviews>
@@ -2543,58 +2595,6 @@
                                     <constraint firstAttribute="trailing" secondItem="UMZ-Cg-DEH" secondAttribute="trailing" id="iKD-wY-zod"/>
                                 </constraints>
                             </view>
-                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" indicatorStyle="black" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="tSE-6b-2zD">
-                                <rect key="frame" x="0.0" y="0.0" width="414" height="766"/>
-                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                            </tableView>
-                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yLm-Hp-3ZU">
-                                <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="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"/>
-                                        </constraints>
-                                        <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                        <fontDescription key="fontDescription" type="system" pointSize="12"/>
-                                        <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
-                                    </textView>
-                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="04I-YH-6sq">
-                                        <rect key="frame" x="354" 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="SRf-xt-2cJ"/>
-                                            <constraint firstAttribute="height" constant="40" id="tq8-ze-QJr"/>
-                                        </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 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="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>
                         <viewLayoutGuide key="safeArea" id="4DG-z5-IE7"/>
                         <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
@@ -2644,6 +2644,65 @@
                         <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                         <subviews>
+                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="pVj-YY-BAA">
+                                <rect key="frame" x="0.0" y="0.0" width="414" height="766"/>
+                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                            </tableView>
+                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="-1" estimatedSectionHeaderHeight="-1" sectionFooterHeight="-1" estimatedSectionFooterHeight="-1" translatesAutoresizingMaskIntoConstraints="NO" id="1vz-q2-vEl">
+                                <rect key="frame" x="0.0" y="836" width="414" height="150"/>
+                                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                <constraints>
+                                    <constraint firstAttribute="height" constant="150" id="h0o-Vx-cZy"/>
+                                </constraints>
+                            </tableView>
+                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fnQ-OG-1Lg">
+                                <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="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"/>
+                                        </constraints>
+                                        <color key="textColor" systemColor="labelColor"/>
+                                        <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="wLj-Ys-hxi">
+                                        <rect key="frame" x="354" y="0.0" width="40" height="40"/>
+                                        <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                        <constraints>
+                                            <constraint firstAttribute="height" constant="40" id="34W-h6-eQX"/>
+                                            <constraint firstAttribute="width" constant="40" id="Nbz-G0-ALu"/>
+                                        </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 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="65" id="xBy-8v-aCZ"/>
+                                </constraints>
+                            </view>
                             <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rs3-vX-pDB">
                                 <rect key="frame" x="0.0" y="836" width="414" height="60"/>
                                 <subviews>
@@ -2709,58 +2768,6 @@
                                     <constraint firstAttribute="bottom" secondItem="ZXb-nM-tPm" secondAttribute="bottom" id="gfG-x0-ImF"/>
                                 </constraints>
                             </view>
-                            <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" showsHorizontalScrollIndicator="NO" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="pVj-YY-BAA">
-                                <rect key="frame" x="0.0" y="0.0" width="414" height="766"/>
-                                <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                            </tableView>
-                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fnQ-OG-1Lg">
-                                <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="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"/>
-                                        </constraints>
-                                        <color key="textColor" systemColor="labelColor"/>
-                                        <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="wLj-Ys-hxi">
-                                        <rect key="frame" x="354" y="0.0" width="40" height="40"/>
-                                        <color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
-                                        <constraints>
-                                            <constraint firstAttribute="height" constant="40" id="34W-h6-eQX"/>
-                                            <constraint firstAttribute="width" constant="40" id="Nbz-G0-ALu"/>
-                                        </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 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="65" id="xBy-8v-aCZ"/>
-                                </constraints>
-                            </view>
                         </subviews>
                         <viewLayoutGuide key="safeArea" id="kcu-H8-MOl"/>
                         <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
@@ -2768,9 +2775,12 @@
                             <constraint firstAttribute="bottom" secondItem="rs3-vX-pDB" secondAttribute="bottom" id="7cj-we-UtI"/>
                             <constraint firstItem="pVj-YY-BAA" firstAttribute="leading" secondItem="kcu-H8-MOl" secondAttribute="leading" id="Boh-wY-vBg"/>
                             <constraint firstItem="kcu-H8-MOl" firstAttribute="trailing" secondItem="pVj-YY-BAA" secondAttribute="trailing" id="FBS-Wa-HbT"/>
+                            <constraint firstItem="kcu-H8-MOl" firstAttribute="trailing" secondItem="1vz-q2-vEl" secondAttribute="trailing" id="GKY-2n-FNs"/>
                             <constraint firstItem="pVj-YY-BAA" firstAttribute="top" secondItem="UqO-mX-4jX" secondAttribute="top" id="JcX-k3-BwQ"/>
                             <constraint firstItem="fnQ-OG-1Lg" firstAttribute="top" secondItem="pVj-YY-BAA" secondAttribute="bottom" constant="10" id="LOl-kg-Tex"/>
                             <constraint firstItem="kcu-H8-MOl" firstAttribute="trailing" secondItem="rs3-vX-pDB" secondAttribute="trailing" id="MMd-8d-bIy"/>
+                            <constraint firstItem="1vz-q2-vEl" firstAttribute="leading" secondItem="kcu-H8-MOl" secondAttribute="leading" id="Skg-0N-h9e"/>
+                            <constraint firstAttribute="bottom" secondItem="1vz-q2-vEl" secondAttribute="bottom" constant="-90" id="XGF-zq-0GX"/>
                             <constraint firstItem="rs3-vX-pDB" firstAttribute="top" secondItem="fnQ-OG-1Lg" secondAttribute="bottom" id="bT4-KC-g5c"/>
                             <constraint firstItem="rs3-vX-pDB" firstAttribute="leading" secondItem="kcu-H8-MOl" secondAttribute="leading" id="o4K-LT-Opo"/>
                             <constraint firstItem="fnQ-OG-1Lg" firstAttribute="trailing" secondItem="kcu-H8-MOl" secondAttribute="trailing" id="rRs-Ah-lAB"/>
@@ -2788,8 +2798,11 @@
                         <outlet property="constraintBottomTableViewWithTextfield" destination="LOl-kg-Tex" id="aqm-eZ-phZ"/>
                         <outlet property="constraintTopTextField" destination="rwD-sI-rSc" id="d82-qJ-lee"/>
                         <outlet property="constraintViewTextField" destination="bT4-KC-g5c" id="KVz-UY-FuX"/>
+                        <outlet property="contraintBottomMention" destination="XGF-zq-0GX" id="CV7-nf-rEk"/>
+                        <outlet property="heightTableMention" destination="h0o-Vx-cZy" id="AXI-C9-9Cb"/>
                         <outlet property="heightTextFieldSend" destination="8Zi-nS-Q7O" id="V0u-tl-hcY"/>
                         <outlet property="tableChatView" destination="pVj-YY-BAA" id="0Nc-wF-dmw"/>
+                        <outlet property="tableMention" destination="1vz-q2-vEl" id="rV1-5z-LMT"/>
                         <outlet property="textFieldSend" destination="bW3-fS-6Qv" id="NWj-px-tv0"/>
                         <outlet property="viewAttachment" destination="ZXb-nM-tPm" id="Bbu-bM-wLZ"/>
                         <outlet property="viewButton" destination="rs3-vX-pDB" id="4yF-Hx-qVU"/>

+ 5 - 7
appbuilder-ios/NexilisLite/NexilisLite/Source/Extension.swift

@@ -628,7 +628,7 @@ extension String {
         return length
     }
     
-    public func richText(isEditing: Bool = false, first: Int = 0, last: Int = 0, isSearching: Bool = false, textSearch: String = "") -> NSAttributedString {
+    public func richText(isEditing: Bool = false, isSearching: Bool = false, textSearch: String = "") -> NSAttributedString {
         let font = UIFont.systemFont(ofSize: 12)
         let textUTF8 = String(self.utf8)
         let finalText = NSMutableAttributedString(string: textUTF8, attributes: [NSAttributedString.Key.font: font])
@@ -790,12 +790,10 @@ extension String {
                 let listText = listTextEnter[j].split(separator: " ")
                 for i in 0...listText.count - 1 {
                     if listText[i].lowercased().checkStartWithLink() {
-                        if ((listText[i].lowercased().starts(with: "www.") && listText[i].lowercased().split(separator: ".").count >= 3) || (!listText[i].lowercased().starts(with: "www.") && listText[i].lowercased().split(separator: ".").count >= 2)) && listText[i].lowercased().split(separator: ".").last!.count >= 2 {
-                            if let range: Range<String.Index> = finalText.string.range(of: listText[i]) {
-                                let index: Int = finalText.string.distance(from: finalText.string.startIndex, to: range.lowerBound)
-                                finalText.addAttribute(.foregroundColor, value: UIColor.linkColor, range: NSRange(index...index + listText[i].count - 1))
-                                finalText.addAttribute(.underlineStyle, value: NSUnderlineStyle.thick.rawValue, range: NSRange(index...index + listText[i].count - 1))
-                            }
+                        if let range: Range<String.Index> = finalText.string.range(of: listText[i]) {
+                            let index: Int = finalText.string.distance(from: finalText.string.startIndex, to: range.lowerBound)
+                            finalText.addAttribute(.foregroundColor, value: UIColor.linkColor, range: NSRange(index...index + listText[i].count - 1))
+                            finalText.addAttribute(.underlineStyle, value: NSUnderlineStyle.thick.rawValue, range: NSRange(index...index + listText[i].count - 1))
                         }
                     }
                 }

+ 202 - 139
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorGroup.swift

@@ -32,6 +32,9 @@ public class EditorGroup: UIViewController {
     @IBOutlet weak var buttonAckConfidential: UIButton!
     @IBOutlet weak var constraintBottomTableViewWithTextfield: NSLayoutConstraint!
     @IBOutlet weak var viewAttachment: UIStackView!
+    @IBOutlet weak var tableMention: UITableView!
+    @IBOutlet weak var heightTableMention: NSLayoutConstraint!
+    @IBOutlet weak var contraintBottomMention: NSLayoutConstraint!
     public var dataGroup: [String: Any?] = [:]
     public var dataTopic: [String: Any?] = [:]
     var dataMessages: [[String: Any?]] = []
@@ -75,6 +78,9 @@ public class EditorGroup: UIViewController {
     var lastScrollIdxSearch = 0
     var buttonUp: UIButton!
     var buttonDown: UIButton!
+    var keyboardHeightForMention: CGFloat?
+    var listMentionWithText:[User] = []
+    var showingLink = ""
     
     public override func viewDidDisappear(_ animated: Bool) {
         if self.isMovingFromParent {
@@ -168,6 +174,10 @@ public class EditorGroup: UIViewController {
             }
             dataMessageForward = nil
         }
+        tableMention.register(UITableViewCell.self, forCellReuseIdentifier: "cellMention")
+        tableMention.dataSource = self
+        tableMention.delegate = self
+        tableMention.contentInset = UIEdgeInsets(top: -25, left: 0, bottom: 0, right: 0)
     }
     
     public func afterUnfriend() {
@@ -1109,7 +1119,11 @@ public class EditorGroup: UIViewController {
             let duration: CGFloat = info[UIResponder.keyboardAnimationDurationUserInfoKey] as! NSNumber as! CGFloat
             
             if self.constraintViewTextField.constant != keyboardHeight - 60 {
+                if self.contraintBottomMention.constant > 0 {
+                    self.contraintBottomMention.constant = self.contraintBottomMention.constant + keyboardHeight - 60
+                }
                 self.constraintViewTextField.constant = keyboardHeight - 60
+                self.keyboardHeightForMention = keyboardHeight
                 if isSearching {
                     self.constraintViewTextField.constant = self.constraintViewTextField.constant + 60
                     self.constraintBottomContainerMultpileSelectSession.constant = -keyboardHeight
@@ -1137,6 +1151,10 @@ public class EditorGroup: UIViewController {
             
             self.constraintViewTextField.constant = 0
             self.constraintBottomContainerMultpileSelectSession.constant = 0
+            if self.contraintBottomMention.constant > 0 {
+                self.contraintBottomMention.constant = self.contraintBottomMention.constant - self.keyboardHeightForMention! + 60
+            }
+            keyboardHeightForMention = nil
             UIView.animate(withDuration: TimeInterval(duration), animations: {
                 self.view.layoutIfNeeded()
             })
@@ -1580,23 +1598,44 @@ extension EditorGroup: UIDocumentPickerDelegate, DocumentPickerDelegate, QLPrevi
 
 extension EditorGroup: UITextViewDelegate {
     public func textViewDidChangeSelection(_ textView: UITextView) {
+        let cursorPositionInt = textView.selectedRange.location
+        var isShowMention = false
+        if cursorPositionInt > 0 {
+            if textView.text.contains("@") {
+                let fulltext = textView.text.substring(from: 0, to: cursorPositionInt - 1)
+                let splitBreak = fulltext.components(separatedBy: "\n")
+                let indexLastBreak = splitBreak.lastIndex(where: { $0.contains("@") })
+                if indexLastBreak != nil {
+                    let splitSpace = splitBreak[indexLastBreak!].components(separatedBy: " ")
+                    let indexLastMention = splitSpace.lastIndex(where: { $0.substring(from: 0, to: 0) == "@" })
+                    if indexLastMention != nil {
+                        let fullTextMention = splitSpace[indexLastMention!]
+                        showMention(text: fullTextMention.substring(from: 1, to: fullTextMention.count))
+                        isShowMention = true
+                    }
+                }
+            }
+        }
+        if !isShowMention {
+            hideMention()
+        }
         let cursorPosition = textView.caretRect(for: self.textFieldSend.selectedTextRange!.start).origin
         let currentLine = Int(cursorPosition.y / self.textFieldSend.font!.lineHeight)
         UIView.animate(withDuration: 0.3) {
-            print("MEME \(self.heightTextFieldSend.constant)")
-            if self.heightTextFieldSend.constant < 95.0 && currentLine >= 4 {
+            let numberOfLines = textView.textContainer.lineBreakMode == .byWordWrapping ? Int(textView.contentSize.height / textView.font!.lineHeight) - 1 : 1
+            if currentLine == 0 && numberOfLines == 1 {
+                self.heightTextFieldSend.constant = 40
+            } else if self.heightTextFieldSend.constant < 95.0 && currentLine >= 4 {
                 self.heightTextFieldSend.constant = 95.0
-            }
-            if currentLine < 4 && self.heightTextFieldSend.constant < 95.0 {
-                if (self.textFieldSend.text.count > 0) {
+            } else if currentLine < 4 && numberOfLines < 5 {
+                if (self.textFieldSend.text.count > 0 && self.heightTextFieldSend.constant != self.textFieldSend.contentSize.height) {
                     self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height
                 }
             }
-            let countBreakLine = textView.text.split(separator: "\n").count
-            if countBreakLine > 1 && self.heightTextFieldSend.constant != CGFloat(40 + (countBreakLine * 18)) {
-                self.heightTextFieldSend.constant = CGFloat(40 + (countBreakLine * 18))
-            }
         }
+    }
+    
+    public func textViewDidChange(_ textView: UITextView) {
         if allowTyping {
             allowTyping = false
             if dataTopic["chat_id"] as! String == "" {
@@ -1610,119 +1649,145 @@ extension EditorGroup: UITextViewDelegate {
                 self.allowTyping = true
             })
         }
-        if let selectedRange = textView.selectedTextRange {
-            let cursorPosition = textView.offset(from: textView.beginningOfDocument, to: selectedRange.start)
-            let beforeCursor = textView.text.substring(from: cursorPosition - cursorPosition, to: cursorPosition - 1).split(separator: " ").last
-            let afterCursor = textView.text.substring(from: cursorPosition, to: textView.text.count - 1).split(separator: " ").first
-            var firstCount = 0
-            var lastCount = textView.text.count
-            if beforeCursor != nil {
-                firstCount = cursorPosition - beforeCursor!.count - 1
-                if firstCount == -1 {
-                    firstCount = 0
-                }
-                if beforeCursor!.lowercased().checkStartWithLink() {
-                    if textView.text.split(separator: " ").count > 1 && self.containerLink.isDescendant(of: self.viewTextfield) {
-                        
-                    } else {
-                        if ((beforeCursor!.lowercased().starts(with: "www.") && beforeCursor!.lowercased().split(separator: ".").count >= 3) || (!beforeCursor!.lowercased().starts(with: "www.") && beforeCursor!.lowercased().split(separator: ".").count >= 2)) && beforeCursor!.lowercased().split(separator: ".").last!.count >= 2 {
-                            checkLink(text: beforeCursor!.lowercased())
-                        } else {
-                            deleteLinkPreview()
-                        }
-                    }
-                }
-            }
-            if afterCursor != nil {
-                if beforeCursor == nil {
-                    lastCount = afterCursor!.count + 2
-                } else {
-                    lastCount = beforeCursor!.count + afterCursor!.count + 2
-                }
-                if afterCursor!.lowercased().checkStartWithLink() {
-                    if textView.text.split(separator: " ").count > 1 && self.containerLink.isDescendant(of: self.viewTextfield) {
-                        
-                    } else {
-                        if ((afterCursor!.lowercased().starts(with: "www.") && afterCursor!.lowercased().split(separator: ".").count >= 3) || afterCursor!.lowercased().split(separator: ".").count >= 2) && afterCursor!.lowercased().split(separator: ".").last!.count >= 2 {
-                            checkLink(text: afterCursor!.lowercased())
-                        } else {
-                            deleteLinkPreview()
-                        }
-                    }
-                }
-            }
-            if textView.text.contains("*") || textView.text.contains("_") || textView.text.contains("^") || textView.text.contains("~") {
-                textView.preserveCursorPosition(withChanges: { _ in
-                    textView.attributedText = textView.text.richText(isEditing: true, first: firstCount, last: lastCount)
-                    return .preserveCursor
-                })
-            }
+        checkLink(fullText: textView.text)
+        if textView.text.contains("*") || textView.text.contains("_") || textView.text.contains("^") || textView.text.contains("~") {
+            textView.preserveCursorPosition(withChanges: { _ in
+                textView.attributedText = textView.text.richText(isEditing: true)
+                return .preserveCursor
+            })
         }
     }
     
-    private func checkLink(text: String) {
-        var stringURl = text
-        if stringURl.starts(with: "www.") {
-            stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
+    private func showMention(text: String) {
+        if self.contraintBottomMention.constant < 0 {
+            self.contraintBottomMention.constant = 65 + ((self.keyboardHeightForMention != nil) ? self.keyboardHeightForMention! : 0)
+            UIView.animate(withDuration: 0.5, animations: {
+                self.view.layoutIfNeeded()
+            })
         }
-        var dataURL = ""
-        Database.shared.database?.inTransaction({ (fmdb, rollback) in
-            if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(stringURl)'") {
+        listMentionWithText.removeAll()
+        Database.shared.database?.inTransaction({ fmdb, rollback in
+            let idMe = UserDefaults.standard.string(forKey: "me")!
+            if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "SELECT thumb_id, f_pin, first_name || ' ' || ifnull(last_name, '') name FROM GROUPZ_MEMBER where group_id='\(self.dataGroup["group_id"] as! String)' AND f_pin <> '\(idMe)' AND name LIKE '%\(text)%'") {
                 while cursor.next() {
-                    if let data = cursor.string(forColumnIndex: 0) {
-                        dataURL = data
-                    }
+                    listMentionWithText.append(User(pin: cursor.string(forColumnIndex: 1) ?? "",
+                                                    firstName: cursor.string(forColumnIndex: 2) ?? "",
+                                                    lastName: cursor.string(forColumnIndex: 2) ?? "",
+                                                    thumb: cursor.string(forColumnIndex: 0) ?? ""))
                 }
                 cursor.close()
             }
+            if listMentionWithText.count > 0 {
+                if listMentionWithText.count < 5 {
+                    self.heightTableMention.constant = CGFloat(44 * listMentionWithText.count)
+                } else {
+                    self.heightTableMention.constant = 44 * 4
+                }
+                tableMention.reloadData()
+            } else {
+                self.heightTableMention.constant = 44
+                self.hideMention()
+            }
         })
-        if !dataURL.isEmpty {
-            if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
-                let title = data["title"] as! String
-                let description = data["description"] as! String
-                let imageUrl = data["imageUrl"] as? String
-                self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
+    }
+    
+    private func hideMention() {
+        if self.contraintBottomMention.constant > 0 {
+            self.contraintBottomMention.constant = 0 - self.heightTableMention.constant
+            UIView.animate(withDuration: 0.5, animations: {
+                self.view.layoutIfNeeded()
+            })
+        }
+    }
+    
+    private func checkLink(fullText: String) {
+        var text = ""
+        let listTextSplitBreak = fullText.components(separatedBy: "\n")
+        let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
+        if indexFirstLinkSplitBreak != nil {
+            let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
+            let indexFirstLinkSplitSpace = listTextSplitSpace.firstIndex(where: { ($0.starts(with: "www.") && $0.components(separatedBy: ".").count > 2) || ($0.starts(with: "http://") && $0.components(separatedBy: ".").count > 1) || ($0.starts(with: "https://") && $0.components(separatedBy: ".").count > 1) })
+            if indexFirstLinkSplitSpace != nil {
+                text = listTextSplitSpace[indexFirstLinkSplitSpace!]
             }
-        } else {
-            let urlData = URL(string: stringURl)!
-            Readability.parse(url: urlData, completion: { data in
-                self.deleteLinkPreview()
-                
-                if data != nil {
-                    let title = data!.title
-                    let description = data!.description
-                    let imageUrl = data!.topImage
-                    Database.shared.database?.inTransaction({ (fmdb, rollback) in
-                        do {
-                            var dataJson: [String: Any] = [:]
-                            dataJson["title"] = title
-                            dataJson["description"] = description
-                            dataJson["imageUrl"] = imageUrl
-                            guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
-                                return
-                            }
-                            _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
-                                "id" : "\(Date().currentTimeMillis().toHex())",
-                                "link" : stringURl,
-                                "data_link" : json,
-                                "retry": 0
-                            ], replace: true)
-                        } catch {
-                            rollback.pointee = true
-                            print(error)
+        }
+        if !text.isEmpty {
+            var stringURl = text
+            if stringURl.starts(with: "www.") {
+                stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
+            }
+            var dataURL = ""
+            Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'") {
+                    while cursor.next() {
+                        if let data = cursor.string(forColumnIndex: 0) {
+                            dataURL = data
                         }
-                    })
-                    
-                    self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
+                    }
+                    cursor.close()
                 }
             })
+            if !dataURL.isEmpty {
+                if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
+                    let title = data["title"] as! String
+                    let description = data["description"] as! String
+                    let imageUrl = data["imageUrl"] as? String
+                    let link = data["link"] as! String
+                    if self.showingLink != text {
+                        self.showingLink = text
+                        self.deleteLinkPreview()
+                        self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: link)
+                    }
+                }
+            } else {
+                let urlData = URL(string: stringURl)!
+                Readability.parse(url: urlData, completion: { data in
+                    if data != nil {
+                        let title = data!.title
+                        let description = data!.description
+                        let imageUrl = data!.topImage
+                        Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                            do {
+                                var dataJson: [String: Any] = [:]
+                                dataJson["title"] = title
+                                dataJson["description"] = description
+                                dataJson["imageUrl"] = imageUrl
+                                dataJson["link"] = text
+                                guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
+                                    return
+                                }
+                                _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
+                                    "id" : "\(Date().currentTimeMillis().toHex())",
+                                    "link" : text,
+                                    "data_link" : json,
+                                    "retry": 0
+                                ], replace: true)
+                            } catch {
+                                rollback.pointee = true
+                                print(error)
+                            }
+                        })
+                        if self.showingLink != text {
+                            self.showingLink = text
+                            self.deleteLinkPreview()
+                            self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
+                        } else {
+                            self.deleteLinkPreview()
+                        }
+                    }
+                })
+            }
+        } else {
+            deleteLinkPreview()
         }
     }
     
     private func buildPreviewLink(imageUrl: String?, title: String, description: String?, stringURl: String) {
-        UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
-            self.constraintTopTextField.constant = self.constraintTopTextField.constant + 80
-        }, completion: nil)
+        if !self.viewTextfield.subviews.contains(self.containerLink){
+            UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
+                self.constraintTopTextField.constant = self.constraintTopTextField.constant + 80
+            }, completion: nil)
+        }
         
         self.viewTextfield.addSubview(self.containerLink)
         self.containerLink.translatesAutoresizingMaskIntoConstraints = false
@@ -1843,39 +1908,6 @@ extension EditorGroup: UITextViewDelegate {
     }
     
     public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
-        let cursorPosition = textView.caretRect(for: self.textFieldSend.selectedTextRange!.start).origin
-        let currentLine = Int(cursorPosition.y / self.textFieldSend.font!.lineHeight)
-        UIView.animate(withDuration: 0.3) {
-            if currentLine == 0 && text != "\n"{
-                self.heightTextFieldSend.constant = 40
-            } else if currentLine < 4 {
-                DispatchQueue.main.async {
-                    if (self.currentIndexpath != nil) {
-                        self.tableChatView.scrollToRow(at: IndexPath(row: self.currentIndexpath!.row, section: self.currentIndexpath!.section), at: .none, animated: false)
-                    }
-                }
-                if (text == "\n" && self.textFieldSend.text.count > 0) {
-                    self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height + 18
-                }
-                else if (self.textFieldSend.text.count > 0) {
-                    self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height
-                }
-                
-                let txt = textView.text
-                let txtRange = Range(range, in: txt!)
-                let subString: Substring = txt![txtRange!]
-                if  subString == "\n" {
-                    self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height - 18
-                    if (self.heightTextFieldSend.constant <= 50 && self.heightTextFieldSend.constant >= 40) {
-                        self.heightTextFieldSend.constant = 40
-                    }
-                }
-            }
-            let countBreakLine = textView.text.split(separator: "\n").count
-            if countBreakLine > 1 && self.heightTextFieldSend.constant != CGFloat(40 + (countBreakLine * 18)) {
-                self.heightTextFieldSend.constant = CGFloat(40 + (countBreakLine * 18))
-            }
-        }
         if (self.textFieldSend.text.count == 0) {
             return text != "\n"
         }
@@ -2465,6 +2497,7 @@ extension EditorGroup: UIContextMenuInteractionDelegate {
             UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
                 self.constraintTopTextField.constant = self.constraintTopTextField.constant - 80
             }, completion: nil)
+            self.showingLink = ""
         }
         if self.reffId != nil {
             self.bottomAnchorPreviewReply.isActive = false
@@ -2523,15 +2556,24 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
     }
     
     public func numberOfSections(in tableView: UITableView) -> Int {
-        dataDates.count
+        if tableView == tableMention {
+            return 1
+        }
+        return dataDates.count
     }
     
     public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if tableView == tableMention {
+            return listMentionWithText.count
+        }
         let count = dataMessages.filter({ $0["chat_date"] as! String == dataDates[section] }).count
         return count
     }
     
     public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        if tableView == tableMention {
+            return .none
+        }
         let containerView = UIView()
         containerView.backgroundColor = .clear
         
@@ -2570,6 +2612,9 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
     }
     
     public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+        if tableView == tableMention {
+            return 0
+        }
         if section == 0 {
             return 40
         }
@@ -2577,6 +2622,11 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
     }
     
     public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        if tableView == tableMention {
+            tableView.deselectRow(at: indexPath, animated: true)
+            
+            return
+        }
         let dataMessages = self.dataMessages.filter({ $0["chat_date"] as! String == dataDates[indexPath.section] })
         if copySession || forwardSession || deleteSession {
             if copySession && dataMessages[indexPath.row]["f_pin"] as! String != "-999" {
@@ -2646,6 +2696,19 @@ extension EditorGroup: UITableViewDelegate, UITableViewDataSource {
     
     
     public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        if tableView == tableMention {
+            let cellMention = tableView.dequeueReusableCell(withIdentifier: "cellMention", for: indexPath as IndexPath)
+            var content = cellMention.defaultContentConfiguration()
+            content.textProperties.font = UIFont.systemFont(ofSize: 11)
+            content.imageProperties.tintColor = .black
+            content.imageProperties.maximumSize = CGSize(width: 24, height: 24)
+            getImage(name: listMentionWithText[indexPath.row].thumb, placeholderImage: UIImage(systemName: "person"), isCircle: true, tableView: tableView, indexPath: indexPath, completion: { result, isDownloaded, image in
+                content.image = image
+            })
+            content.text = listMentionWithText[indexPath.row].firstName
+            cellMention.contentConfiguration = content
+            return cellMention
+        }
         let idMe = UserDefaults.standard.string(forKey: "me") as String?
         let dataMessages = dataMessages.filter({$0["chat_date"] as! String == dataDates[indexPath.section]})
         

+ 72 - 115
appbuilder-ios/NexilisLite/NexilisLite/Source/View/Chat/EditorPersonal.swift

@@ -92,6 +92,7 @@ public class EditorPersonal: UIViewController, ImageVideoPickerDelegate, UIGestu
     var multipleOffsetUp = 1
     var lastOffsetDown = 1
     var gettingDataMessage = true
+    var showingLink = ""
     
     public override func viewDidDisappear(_ animated: Bool) {
         if self.isMovingFromParent {
@@ -2850,20 +2851,20 @@ extension EditorPersonal: UITextViewDelegate {
         let cursorPosition = textView.caretRect(for: self.textFieldSend.selectedTextRange!.start).origin
         let currentLine = Int(cursorPosition.y / self.textFieldSend.font!.lineHeight)
         UIView.animate(withDuration: 0.3) {
-            print("MEME \(self.heightTextFieldSend.constant)")
-            if self.heightTextFieldSend.constant < 95.0 && currentLine >= 4 {
+            let numberOfLines = textView.textContainer.lineBreakMode == .byWordWrapping ? Int(textView.contentSize.height / textView.font!.lineHeight) - 1 : 1
+            if currentLine == 0 && numberOfLines == 1 {
+                self.heightTextFieldSend.constant = 40
+            } else if self.heightTextFieldSend.constant < 95.0 && currentLine >= 4 {
                 self.heightTextFieldSend.constant = 95.0
-            }
-            if currentLine < 4 && self.heightTextFieldSend.constant < 95.0 {
-                if (self.textFieldSend.text.count > 0) {
+            } else if currentLine < 4 && numberOfLines < 5 {
+                if (self.textFieldSend.text.count > 0 && self.heightTextFieldSend.constant != self.textFieldSend.contentSize.height) {
                     self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height
                 }
             }
-            let countBreakLine = textView.text.split(separator: "\n").count
-            if countBreakLine > 1 && self.heightTextFieldSend.constant != CGFloat(40 + (countBreakLine * 18)) {
-                self.heightTextFieldSend.constant = CGFloat(40 + (countBreakLine * 18))
-            }
         }
+    }
+    
+    public func textViewDidChange(_ textView: UITextView) {
         if allowTyping {
             allowTyping = false
             if isContactCenter && !fPinContacCenter.isEmpty {
@@ -2875,84 +2876,57 @@ extension EditorPersonal: UITextViewDelegate {
                 self.allowTyping = true
             })
         }
-        if let selectedRange = textView.selectedTextRange {
-            let cursorPosition = textView.offset(from: textView.beginningOfDocument, to: selectedRange.start)
-            let beforeCursor = textView.text.substring(from: cursorPosition - cursorPosition, to: cursorPosition - 1).split(separator: " ").last
-            let afterCursor = textView.text.substring(from: cursorPosition, to: textView.text.count - 1).split(separator: " ").first
-            var firstCount = 0
-            var lastCount = textView.text.count
-            if beforeCursor != nil {
-                firstCount = cursorPosition - beforeCursor!.count - 1
-                if firstCount == -1 {
-                    firstCount = 0
-                }
-                if beforeCursor!.lowercased().checkStartWithLink() {
-                    if textView.text.split(separator: " ").count > 1 && self.containerLink.isDescendant(of: self.viewTextfield) {
-                        
-                    } else {
-                        if ((beforeCursor!.lowercased().starts(with: "www.") && beforeCursor!.lowercased().split(separator: ".").count >= 3) || (!beforeCursor!.lowercased().starts(with: "www.") && beforeCursor!.lowercased().split(separator: ".").count >= 2)) && beforeCursor!.lowercased().split(separator: ".").last!.count >= 2 {
-                            checkLink(text: beforeCursor!.lowercased())
-                        } else {
-                            deleteLinkPreview()
-                        }
-                    }
-                }
-            }
-            if afterCursor != nil {
-                if beforeCursor == nil {
-                    lastCount = afterCursor!.count + 2
-                } else {
-                    lastCount = beforeCursor!.count + afterCursor!.count + 2
-                }
-                if afterCursor!.lowercased().checkStartWithLink() {
-                    if textView.text.split(separator: " ").count > 1 && self.containerLink.isDescendant(of: self.viewTextfield) {
-                        
-                    } else {
-                        if ((afterCursor!.lowercased().starts(with: "www.") && afterCursor!.lowercased().split(separator: ".").count >= 3) || afterCursor!.lowercased().split(separator: ".").count >= 2) && afterCursor!.lowercased().split(separator: ".").last!.count >= 2 {
-                            checkLink(text: afterCursor!.lowercased())
-                        } else {
-                            deleteLinkPreview()
-                        }
-                    }
-                }
-            }
-            if textView.text.contains("*") || textView.text.contains("_") || textView.text.contains("^") || textView.text.contains("~") {
-                textView.preserveCursorPosition(withChanges: { _ in
-                    textView.attributedText = textView.text.richText(isEditing: true, first: firstCount, last: lastCount)
-                    return .preserveCursor
-                })
-            }
+        checkLink(fullText: textView.text)
+        if textView.text.contains("*") || textView.text.contains("_") || textView.text.contains("^") || textView.text.contains("~") {
+            textView.preserveCursorPosition(withChanges: { _ in
+                textView.attributedText = textView.text.richText(isEditing: true)
+                return .preserveCursor
+            })
         }
     }
     
-    private func checkLink(text: String) {
-        var stringURl = text
-        if stringURl.starts(with: "www.") {
-            stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
-        }
-        var dataURL = ""
-        Database.shared.database?.inTransaction({ (fmdb, rollback) in
-            if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(stringURl)'") {
-                while cursor.next() {
-                    if let data = cursor.string(forColumnIndex: 0) {
-                        dataURL = data
+    private func checkLink(fullText: String) {
+        var text = ""
+        let listTextSplitBreak = fullText.components(separatedBy: "\n")
+        let indexFirstLinkSplitBreak = listTextSplitBreak.firstIndex(where: { $0.contains("www.") || $0.contains("http://") || $0.contains("https://") })
+        if indexFirstLinkSplitBreak != nil {
+            let listTextSplitSpace = listTextSplitBreak[indexFirstLinkSplitBreak!].components(separatedBy: " ")
+            let indexFirstLinkSplitSpace = listTextSplitSpace.firstIndex(where: { ($0.starts(with: "www.") && $0.components(separatedBy: ".").count > 2) || ($0.starts(with: "http://") && $0.components(separatedBy: ".").count > 1) || ($0.starts(with: "https://") && $0.components(separatedBy: ".").count > 1) })
+            if indexFirstLinkSplitSpace != nil {
+                text = listTextSplitSpace[indexFirstLinkSplitSpace!]
+            }
+        }
+        if !text.isEmpty {
+            var stringURl = text
+            if stringURl.starts(with: "www.") {
+                stringURl = "https://" + stringURl.replacingOccurrences(of: "www.", with: "")
+            }
+            var dataURL = ""
+            Database.shared.database?.inTransaction({ (fmdb, rollback) in
+                if let cursor = Database.shared.getRecords(fmdb: fmdb, query: "select data_link from LINK_PREVIEW where link='\(text)'") {
+                    while cursor.next() {
+                        if let data = cursor.string(forColumnIndex: 0) {
+                            dataURL = data
+                        }
+                    }
+                    cursor.close()
+                }
+            })
+            if !dataURL.isEmpty {
+                if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
+                    let title = data["title"] as! String
+                    let description = data["description"] as! String
+                    let imageUrl = data["imageUrl"] as? String
+                    let link = data["link"] as! String
+                    if self.showingLink != text {
+                        self.showingLink = text
+                        self.deleteLinkPreview()
+                        self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: link)
                     }
                 }
-                cursor.close()
-            }
-        })
-        if !dataURL.isEmpty {
-            if let data = try! JSONSerialization.jsonObject(with: dataURL.data(using: String.Encoding.utf8)!, options: []) as? [String: Any] {
-                let title = data["title"] as! String
-                let description = data["description"] as! String
-                let imageUrl = data["imageUrl"] as? String
-                self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
-            }
-        } else {
-            if let urlData = URL(string: stringURl) {
+            } else {
+                let urlData = URL(string: stringURl)!
                 Readability.parse(url: urlData, completion: { data in
-                    self.deleteLinkPreview()
-                    
                     if data != nil {
                         let title = data!.title
                         let description = data!.description
@@ -2963,12 +2937,13 @@ extension EditorPersonal: UITextViewDelegate {
                                 dataJson["title"] = title
                                 dataJson["description"] = description
                                 dataJson["imageUrl"] = imageUrl
+                                dataJson["link"] = text
                                 guard let json = String(data: try! JSONSerialization.data(withJSONObject: dataJson, options: []), encoding: String.Encoding.utf8) else {
                                     return
                                 }
                                 _ = try Database.shared.insertRecord(fmdb: fmdb, table: "LINK_PREVIEW", cvalues: [
                                     "id" : "\(Date().currentTimeMillis().toHex())",
-                                    "link" : stringURl,
+                                    "link" : text,
                                     "data_link" : json,
                                     "retry": 0
                                 ], replace: true)
@@ -2977,18 +2952,27 @@ extension EditorPersonal: UITextViewDelegate {
                                 print(error)
                             }
                         })
-                        
-                        self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
+                        if self.showingLink != text {
+                            self.showingLink = text
+                            self.deleteLinkPreview()
+                            self.buildPreviewLink(imageUrl: imageUrl, title: title, description: description, stringURl: text)
+                        }
+                    } else {
+                        self.deleteLinkPreview()
                     }
                 })
             }
+        } else {
+            deleteLinkPreview()
         }
     }
     
     private func buildPreviewLink(imageUrl: String?, title: String, description: String?, stringURl: String) {
-        UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
-            self.constraintTopTextField.constant = self.constraintTopTextField.constant + 80
-        }, completion: nil)
+        if !self.viewTextfield.subviews.contains(self.containerLink){
+            UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
+                self.constraintTopTextField.constant = self.constraintTopTextField.constant + 80
+            }, completion: nil)
+        }
         
         self.viewTextfield.addSubview(self.containerLink)
         self.containerLink.translatesAutoresizingMaskIntoConstraints = false
@@ -3112,34 +3096,6 @@ extension EditorPersonal: UITextViewDelegate {
     }
     
     public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
-        let cursorPosition = textView.caretRect(for: self.textFieldSend.selectedTextRange!.start).origin
-        let currentLine = Int(cursorPosition.y / self.textFieldSend.font!.lineHeight)
-        UIView.animate(withDuration: 0.3) {
-            if currentLine == 0 && text != "\n" {
-                self.heightTextFieldSend.constant = 40
-            } else if currentLine < 4 {
-                if (text == "\n" && self.textFieldSend.text.count > 0) {
-                    self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height + 18
-                }
-                else if (self.textFieldSend.text.count > 0) {
-                    self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height
-                }
-                
-                let txt = textView.text
-                let txtRange = Range(range, in: txt!)
-                let subString: Substring = txt![txtRange!]
-                if  subString == "\n" {
-                    self.heightTextFieldSend.constant = self.textFieldSend.contentSize.height - 18
-                    if (self.heightTextFieldSend.constant <= 50 && self.heightTextFieldSend.constant >= 40) {
-                        self.heightTextFieldSend.constant = 40
-                    }
-                }
-            }
-            let countBreakLine = textView.text.split(separator: "\n").count
-            if countBreakLine > 1 && self.heightTextFieldSend.constant != CGFloat(40 + (countBreakLine * 18)) {
-                self.heightTextFieldSend.constant = CGFloat(40 + (countBreakLine * 18))
-            }
-        }
         if (self.textFieldSend.text.count == 0) {
             return text != "\n"
         }
@@ -3790,6 +3746,7 @@ extension EditorPersonal: UIContextMenuInteractionDelegate {
             UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseInOut, animations: {
                 self.constraintTopTextField.constant = self.constraintTopTextField.constant - 80
             }, completion: nil)
+            self.showingLink = ""
         }
         if self.reffId != nil {
             self.bottomAnchorPreviewReply.isActive = false