From 509ffc2a0126241b9aa1c654c9da864ed69cad9d Mon Sep 17 00:00:00 2001
From: Mohamed Javid <19339952+smohamedjavid@users.noreply.github.com>
Date: Wed, 16 Nov 2022 22:13:58 +0530
Subject: [PATCH] [Feature] Added messages skeleton (#14072)

* [Feature][#14025] Added Messages Skeleton while loading messages

* [Improvements][#14025] Added animation to message skeleton

* [Improvements][#14025] Changed to Reanimated2 for message skeleton animation

* [Chores][#14025] Removed unused code

* [Improvements][#14025] Added preview screen for messages skeleton

* [Improvements][#14025] Style update for messages skeleton

* [Chore][#14025] Indentation for message skeleton

* [Improvements][#14025] On Layout calculation cleanup

* [Fix][#14025] Added Skeleton on New UI only

* [Chore] Moved Message Skeleton to new UI

* [Feature][#14025] Added Message Skeleton on message gap

* [TEMP][#14025] Added delay of preloading messages for testing

* [Lint][#14025] Lint fixes for Message Gap component

* [Chore][#14025] Reanimated namespace update

* [Chore][#14025] React native namespace update

* [Chore][#14025] Rollback preload messages
---
 src/react_native/reanimated.cljs              | 10 ++-
 .../chat/components/messages_skeleton.cljs    | 77 +++++++++++++++++++
 .../ui/screens/chat/message/gap.cljs          | 10 ++-
 .../ui2/screens/chat/messages/view.cljs       | 12 ++-
 .../ui2/screens/quo2_preview/main.cljs        |  4 +
 .../messages_skeleton.cljs                    | 11 +++
 6 files changed, 116 insertions(+), 8 deletions(-)
 create mode 100644 src/status_im/ui/screens/chat/components/messages_skeleton.cljs
 create mode 100644 src/status_im/ui2/screens/quo2_preview/posts_and_attachments/messages_skeleton.cljs

diff --git a/src/react_native/reanimated.cljs b/src/react_native/reanimated.cljs
index 93c3c79aeb..7e53182e0d 100644
--- a/src/react_native/reanimated.cljs
+++ b/src/react_native/reanimated.cljs
@@ -2,8 +2,9 @@
   (:require ["react-native" :as rn]
             [reagent.core :as reagent]
             [clojure.string :as string]
+            ["react-native-linear-gradient" :default LinearGradient]
             ["react-native-reanimated" :default reanimated
-             :refer (useSharedValue useAnimatedStyle withTiming withDelay withSpring Easing Keyframe)]))
+             :refer (useSharedValue useAnimatedStyle withTiming withDelay withSpring withRepeat Easing Keyframe)]))
 
 ;; Animated Components
 (def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated)))
@@ -12,6 +13,8 @@
 (def image (reagent/adapt-react-class (.-Image reanimated)))
 (def touchable-opacity (create-animated-component (.-TouchableOpacity ^js rn)))
 
+(def linear-gradient (create-animated-component LinearGradient))
+
 ;; Hooks 
 (def use-shared-value useSharedValue)
 (def use-animated-style useAnimatedStyle)
@@ -21,6 +24,7 @@
 (def with-delay withDelay)
 (def with-spring withSpring)
 (def key-frame Keyframe)
+(def with-repeat withRepeat)
 
 ;; Easings
 (def bezier (.-bezier ^js Easing))
@@ -68,3 +72,7 @@
 (defn animate-shared-value-with-delay [anim val duration easing delay]
   (set-shared-value anim (with-delay delay (with-timing val (js-obj "duration" duration
                                                                     "easing"   (get easings easing))))))
+
+(defn animate-shared-value-with-repeat [anim val duration easing number-of-repetitions reverse?]
+  (set-shared-value anim (with-repeat (with-timing val (js-obj "duration" duration
+                                                               "easing"   (get easings easing))) number-of-repetitions reverse?)))
\ No newline at end of file
diff --git a/src/status_im/ui/screens/chat/components/messages_skeleton.cljs b/src/status_im/ui/screens/chat/components/messages_skeleton.cljs
new file mode 100644
index 0000000000..ba7fdc450e
--- /dev/null
+++ b/src/status_im/ui/screens/chat/components/messages_skeleton.cljs
@@ -0,0 +1,77 @@
+(ns status-im.ui.screens.chat.components.messages-skeleton
+  (:require [status-im.ui.components.react :as react]
+            [react-native.core :as rn]
+            [reagent.core :as reagent]
+            [react-native.reanimated :as reanimated]
+            [quo2.foundations.colors :as colors]))
+
+(def message-skeleton-height 54)
+
+(def avatar-skeleton-size 32)
+
+(def message-content-width [{:author  80
+                             :message 249}
+                            {:author  124
+                             :message 156}
+                            {:author  96
+                             :message 212}
+                            {:author  112
+                             :message 144}])
+
+;; Standlone message skeleton
+(defn message-skeleton []
+  [:f>
+   (fn []
+     (let [color                   (colors/theme-colors colors/neutral-5 colors/neutral-70)
+           loading-color           (colors/theme-colors colors/neutral-10 colors/neutral-60)
+           content-width           (rand-nth message-content-width)
+           author-width            (content-width :author)
+           message-width           (content-width :message)
+           {window-width :width}   (rn/use-window-dimensions)
+           translate-x             (reanimated/use-shared-value (- window-width))
+           animated-gradient-style (reanimated/apply-animations-to-style
+                                    {:transform [{:translateX translate-x}]}
+                                    {:width     window-width
+                                     :height    "100%"})]
+       (reanimated/animate-shared-value-with-repeat translate-x window-width 1000 :linear (- 1) false)
+       [react/masked-view
+        {:style {:height message-skeleton-height}
+         :maskElement (reagent/as-element
+                       [rn/view {:style {:height           message-skeleton-height
+                                         :flex-direction   :row
+                                         :padding-vertical 11
+                                         :background-color :transparent
+                                         :padding-left     21}}
+                        [rn/view {:style {:height           avatar-skeleton-size
+                                          :width            avatar-skeleton-size
+                                          :border-radius    (/ avatar-skeleton-size 2)
+                                          :background-color color
+                                          :overflow         :hidden}}]
+                        [rn/view {:style {:padding-left     8
+                                          :background-color :transparent}}
+                         [rn/view {:style {:height           8
+                                           :width            author-width
+                                           :border-radius    6
+                                           :background-color color
+                                           :margin-bottom    8
+                                           :overflow         :hidden}}]
+                         [rn/view {:style {:height           16
+                                           :width            message-width
+                                           :border-radius    6
+                                           :overflow         :hidden
+                                           :background-color color}}]]])}
+        [rn/view {:style {:flex             1
+                          :background-color color}}
+         [reanimated/linear-gradient {:colors   [color color loading-color color color]
+                                      :start    {:x 0 :y 0}
+                                      :end      {:x 1 :y 0}
+                                      :style    animated-gradient-style}]]]))])
+
+(defn messages-skeleton [parent-height]
+  (let [number-of-skeletons (int (Math/floor (/ parent-height message-skeleton-height)))]
+    [rn/view {:style {:background-color (colors/theme-colors
+                                         colors/white
+                                         colors/neutral-90)
+                      :flex              1}}
+     (for [n (range number-of-skeletons)]
+       [message-skeleton {:key n}])]))
\ No newline at end of file
diff --git a/src/status_im/ui/screens/chat/message/gap.cljs b/src/status_im/ui/screens/chat/message/gap.cljs
index 8efaa16a70..50c4b811b6 100644
--- a/src/status_im/ui/screens/chat/message/gap.cljs
+++ b/src/status_im/ui/screens/chat/message/gap.cljs
@@ -4,6 +4,7 @@
             [re-frame.core :as re-frame]
             [status-im.i18n.i18n :as i18n]
             [status-im.utils.datetime :as datetime]
+            [status-im.ui.screens.chat.components.messages-skeleton :as messages-skeleton]
             [status-im.ui.screens.chat.styles.input.gap :as style]))
 
 (defn on-press
@@ -18,16 +19,17 @@
                                 chat-id]
                   connected?   [:mailserver/connected?]
                   use-status-nodes? [:mailserver/use-status-nodes?]
-                  first-gap?   (= gap-ids #{:first-gap})]
+                  first-gap?   (= gap-ids #{:first-gap})
+                  window-height [:dimensions/window-height]]
     (when (or (not first-gap?) public? community?)
-      [react/view {:style (style/gap-container)}
+      [react/view {:style (when-not in-progress? style/gap-container)}
        [react/touchable-highlight
         {:on-press (when (and (not in-progress?) use-status-nodes? connected?)
                      (on-press chat-id gap-ids))
-         :style    style/touchable}
+         :style    {:height (if in-progress? window-height 48)}}
         [react/view {:style style/label-container}
          (if in-progress?
-           [react/activity-indicator]
+           [messages-skeleton/messages-skeleton window-height]
            [react/nested-text
             {:style (style/gap-text (and connected? use-status-nodes?))}
             (i18n/label (if first-gap? :t/load-more-messages :t/fetch-messages))
diff --git a/src/status_im/ui2/screens/chat/messages/view.cljs b/src/status_im/ui2/screens/chat/messages/view.cljs
index c019669c88..4eef9758be 100644
--- a/src/status_im/ui2/screens/chat/messages/view.cljs
+++ b/src/status_im/ui2/screens/chat/messages/view.cljs
@@ -9,6 +9,7 @@
             [status-im.ui.screens.chat.group :as chat.group]
             [status-im.ui.screens.chat.message.datemark :as message-datemark]
             [status-im.ui.screens.chat.message.gap :as gap]
+            [status-im.ui.screens.chat.components.messages-skeleton :as messages-skeleton]
             [status-im.utils.utils :as utils]
             [status-im.utils.platform :as platform]
             [status-im.ui.screens.chat.state :as state]
@@ -19,6 +20,11 @@
 (defonce show-floating-scroll-down-button (reagent/atom false))
 (defonce messages-list-ref (atom nil))
 
+(def messages-view-height (reagent/atom 0))
+
+(defn on-messages-view-layout [^js ev]
+  (reset! messages-view-height (-> ev .-nativeEvent .-layout .-height)))
+
 (def list-key-fn #(or (:message-id %) (:value %)))
 (def list-ref #(reset! messages-list-ref %))
 
@@ -63,8 +69,7 @@
         all-loaded? (<sub [:chats/all-loaded? chat-id])]
     [rn/view {:style (when platform/android? {:scaleY -1})}
      (if (or loading-messages? (not chat-id) (not all-loaded?))
-       [rn/view {:height 324 :align-items :center :justify-content :center}
-        [rn/activity-indicator {:animating true}]]
+       [messages-skeleton/messages-skeleton @messages-view-height]
        [chat-intro-header-container chat no-messages?])]))
 
 (defn list-header [{:keys [chat-id chat-type invitation-admin]}]
@@ -186,6 +191,7 @@
         :on-scroll                    on-scroll
         ;;TODO https://github.com/facebook/react-native/issues/30034
         :inverted                     (when platform/ios? true)
-        :style                        (when platform/android? {:scaleY -1})})]
+        :style                        (when platform/android? {:scaleY -1})
+        :on-layout                    on-messages-view-layout})]
      (when @show-floating-scroll-down-button
        [floating-scroll-down-button show-input?])]))
diff --git a/src/status_im/ui2/screens/quo2_preview/main.cljs b/src/status_im/ui2/screens/quo2_preview/main.cljs
index a4433b019d..d4e738de79 100644
--- a/src/status_im/ui2/screens/quo2_preview/main.cljs
+++ b/src/status_im/ui2/screens/quo2_preview/main.cljs
@@ -31,6 +31,7 @@
             [status-im.ui2.screens.quo2-preview.messages.gap :as messages-gap]
             [status-im.ui2.screens.quo2-preview.messages.system-message :as system-message]
             [status-im.ui2.screens.quo2-preview.notifications.activity-logs :as activity-logs]
+            [status-im.ui2.screens.quo2-preview.posts-and-attachments.messages-skeleton :as messages-skeleton]
             [status-im.ui2.screens.quo2-preview.reactions.react :as react]
             [status-im.ui2.screens.quo2-preview.selectors.disclaimer :as disclaimer]
             [status-im.ui2.screens.quo2-preview.selectors.selectors :as selectors]
@@ -144,6 +145,9 @@
    :notifications [{:name      :activity-logs
                     :insets    {:top false}
                     :component activity-logs/preview-activity-logs}]
+   :posts-and-attachments [{:name      :messages-skeleton
+                            :insets    {:top false}
+                            :component messages-skeleton/preview-messages-skeleton}]
    :reactions [{:name      :react
                 :insets    {:top false}
                 :component react/preview-react}]
diff --git a/src/status_im/ui2/screens/quo2_preview/posts_and_attachments/messages_skeleton.cljs b/src/status_im/ui2/screens/quo2_preview/posts_and_attachments/messages_skeleton.cljs
new file mode 100644
index 0000000000..3e3cef509b
--- /dev/null
+++ b/src/status_im/ui2/screens/quo2_preview/posts_and_attachments/messages_skeleton.cljs
@@ -0,0 +1,11 @@
+(ns status-im.ui2.screens.quo2-preview.posts-and-attachments.messages-skeleton
+  (:require [quo.react-native :as rn]
+            [quo2.foundations.colors :as colors]
+            [status-im.ui.screens.chat.components.messages-skeleton :as messages-skeleton]))
+
+(defn preview-messages-skeleton []
+  [rn/view  {:background-color (colors/theme-colors
+                                colors/white
+                                colors/neutral-90)
+             :flex             1}
+   [messages-skeleton/messages-skeleton]])
\ No newline at end of file