diff --git a/ios/Podfile.lock b/ios/Podfile.lock index df45d1dc77..2b53410413 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -330,8 +330,8 @@ PODS: - SDWebImageWebPCoder (~> 0.8.4) - RNFS (2.16.6): - React - - RNGestureHandler (1.8.0): - - React + - RNGestureHandler (2.5.0): + - React-Core - RNHoleView (2.1.1): - React-Core - RNImageCropPicker (0.36.2): @@ -691,7 +691,7 @@ SPEC CHECKSUMS: RNCPushNotificationIOS: c145c6253ea016e5efeff604f2720736b4a596f7 RNFastImage: 1f2cab428712a4baaf78d6169eaec7f622556dd7 RNFS: 2bd9eb49dc82fa9676382f0585b992c424cd59df - RNGestureHandler: 7a5833d0f788dbd107fbb913e09aa0c1ff333c39 + RNGestureHandler: bad495418bcbd3ab47017a38d93d290ebd406f50 RNHoleView: 07572d21c97fad71fdc47f7248a8513e15a38949 RNImageCropPicker: 35a3ceb837446fa11547704709bb22b5fac6d584 RNKeychain: 216f37338fcb9e5c3a2530f1e3295f737a690cb1 diff --git a/package.json b/package.json index db01a01d5a..c4ec0a78b6 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "react-native-fast-image": "^8.5.11", "react-native-fetch-polyfill": "^1.1.2", "react-native-fs": "^2.14.1", - "react-native-gesture-handler": "^1.8.0", + "react-native-gesture-handler": "^2.5.0", "react-native-haptic-feedback": "^1.9.0", "react-native-hole-view": "git+https://github.com/status-im/react-native-hole-view.git#refs/tags/v2.1.1-status", "react-native-image-crop-picker": "git+https://github.com/status-im/react-native-image-crop-picker.git#refs/tags/v0.36.2-status.0", diff --git a/resources/images/icons/arrow-up20@2x.png b/resources/images/icons/arrow-up20@2x.png new file mode 100644 index 0000000000..a0f0acc26f Binary files /dev/null and b/resources/images/icons/arrow-up20@2x.png differ diff --git a/resources/images/icons/arrow-up20@3x.png b/resources/images/icons/arrow-up20@3x.png new file mode 100644 index 0000000000..2d25ca2b18 Binary files /dev/null and b/resources/images/icons/arrow-up20@3x.png differ diff --git a/resources/images/icons/image20@2x.png b/resources/images/icons/image20@2x.png new file mode 100644 index 0000000000..96d4d5ea2d Binary files /dev/null and b/resources/images/icons/image20@2x.png differ diff --git a/resources/images/icons/image20@3x.png b/resources/images/icons/image20@3x.png new file mode 100644 index 0000000000..aec8ca0312 Binary files /dev/null and b/resources/images/icons/image20@3x.png differ diff --git a/resources/images/icons/reaction20@2x.png b/resources/images/icons/reaction20@2x.png new file mode 100644 index 0000000000..59ae42027b Binary files /dev/null and b/resources/images/icons/reaction20@2x.png differ diff --git a/resources/images/icons/reaction20@3x.png b/resources/images/icons/reaction20@3x.png new file mode 100644 index 0000000000..9d968c0518 Binary files /dev/null and b/resources/images/icons/reaction20@3x.png differ diff --git a/src/mocks/js_dependencies.cljs b/src/mocks/js_dependencies.cljs index 9966fb5589..6942584ba5 100644 --- a/src/mocks/js_dependencies.cljs +++ b/src/mocks/js_dependencies.cljs @@ -219,6 +219,8 @@ :FlatList #js {} :ScrollView #js {} :TouchableOpacity #js {} + :GestureDetector #js {} + :Gesture #js {:Pan nil} :createNativeWrapper identity}) (def react-native-redash #js {:clamp nil}) diff --git a/src/quo/gesture_handler.cljs b/src/quo/gesture_handler.cljs index 68e0d40d28..8b177e2e5c 100644 --- a/src/quo/gesture_handler.cljs +++ b/src/quo/gesture_handler.cljs @@ -1,13 +1,11 @@ (ns quo.gesture-handler (:require [oops.core :refer [oget]] - ["react-native-reanimated" :default animated] [reagent.core :as reagent] [quo.design-system.colors :as colors] ["react-native-gesture-handler" :refer (TapGestureHandler PanGestureHandler LongPressGestureHandler - PureNativeButton TouchableWithoutFeedback TouchableOpacity - TouchableHighlight - createNativeWrapper State NativeViewGestureHandler + TouchableWithoutFeedback TouchableOpacity + TouchableHighlight State NativeViewGestureHandler FlatList ScrollView)])) (def flat-list-raw FlatList) @@ -25,8 +23,6 @@ (def long-press-gesture-handler (reagent/adapt-react-class LongPressGestureHandler)) -(def pure-native-button PureNativeButton) - (def touchable-without-feedback-class TouchableWithoutFeedback) (def touchable-without-feedback @@ -42,12 +38,6 @@ (def touchable-opacity (reagent/adapt-react-class TouchableOpacity)) -(def raw-button - (reagent/adapt-react-class - (createNativeWrapper (.createAnimatedComponent animated PureNativeButton) - #js {:shouldActivateOnStart true - :shouldCancelWhenOutside true}))) - (def native-view-gesture-handler (reagent/adapt-react-class NativeViewGestureHandler)) (def states {:began (oget State "BEGAN") diff --git a/src/quo2/gesture.cljs b/src/quo2/gesture.cljs new file mode 100644 index 0000000000..1ccdc712c3 --- /dev/null +++ b/src/quo2/gesture.cljs @@ -0,0 +1,13 @@ +(ns quo2.gesture + (:require ["react-native-gesture-handler" :refer (GestureDetector Gesture)] + [reagent.core :as reagent])) + +(def gesture-detector (reagent/adapt-react-class GestureDetector)) + +(defn gesture-pan [] (.Pan ^js Gesture)) + +(defn on-update [^js pan handler] (.onUpdate pan handler)) + +(defn on-start [^js pan handler] (.onStart pan handler)) + +(defn on-end [^js pan handler] (.onEnd pan handler)) \ No newline at end of file diff --git a/src/quo2/reanimated.cljs b/src/quo2/reanimated.cljs index 1a524b22d9..95e5d3702a 100644 --- a/src/quo2/reanimated.cljs +++ b/src/quo2/reanimated.cljs @@ -3,7 +3,7 @@ [reagent.core :as reagent] [clojure.string :as string] ["react-native-reanimated" :default reanimated - :refer (useSharedValue useAnimatedStyle withTiming withDelay Easing Keyframe)])) + :refer (useSharedValue useAnimatedStyle withTiming withDelay withSpring Easing Keyframe)])) ;; Animated Components (def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated))) @@ -19,6 +19,7 @@ ;; Animations (def with-timing withTiming) (def with-delay withDelay) +(def with-spring withSpring) (def key-frame Keyframe) ;; Easings diff --git a/src/status_im/switcher/constants.cljs b/src/status_im/switcher/constants.cljs index 71059b8d5f..54f3a960ca 100644 --- a/src/status_im/switcher/constants.cljs +++ b/src/status_im/switcher/constants.cljs @@ -27,10 +27,10 @@ (def switcher-bottom-positions {:android {:home-stack 15 - :chat 57} + :chat 140} :ios {:home-stack 40 - :chat 67}}) + :chat 140}}) (defn switcher-bottom-position [view-id] (get-in diff --git a/src/status_im/switcher/switcher.cljs b/src/status_im/switcher/switcher.cljs index d99f8e03ea..bef5ef253f 100644 --- a/src/status_im/switcher/switcher.cljs +++ b/src/status_im/switcher/switcher.cljs @@ -8,7 +8,8 @@ [status-im.switcher.animation :as animation] [status-im.ui.components.icons.icons :as icons] [status-im.react-native.resources :as resources] - [status-im.switcher.switcher-container :as switcher-container])) + [status-im.switcher.switcher-container :as switcher-container] + [quo.react-native :as rn])) (defn switcher-button [view-id toggle-switcher-screen-fn shared-values] [:f> @@ -71,7 +72,9 @@ :switcher-container-scale (reanimated/use-shared-value 0.9) :close-button-opacity (animation/switcher-close-button-opacity switcher-button-opacity) :switcher-container-bottom (animation/switcher-container-bottom-position switcher-screen-bottom)} - toggle-switcher-screen-fn #(animation/switcher-touchable-on-press-out switcher-opened? view-id shared-values)] - [:<> - [switcher-screen toggle-switcher-screen-fn shared-values] - [switcher-button view-id toggle-switcher-screen-fn shared-values]]))]) + toggle-switcher-screen-fn #(animation/switcher-touchable-on-press-out switcher-opened? view-id shared-values) + {:keys [keyboard-shown]} (rn/use-keyboard)] + (when-not keyboard-shown + [:<> + [switcher-screen toggle-switcher-screen-fn shared-values] + [switcher-button view-id toggle-switcher-screen-fn shared-values]])))]) diff --git a/src/status_im/ui/components/react.cljs b/src/status_im/ui/components/react.cljs index 0ee37e9c0e..75fcbf580b 100644 --- a/src/status_im/ui/components/react.cljs +++ b/src/status_im/ui/components/react.cljs @@ -248,6 +248,14 @@ (update props :keyboardVerticalOffset + 44 (:status-bar-height @navigation-const))))] children)) +(defn keyboard-avoiding-view-new [props & children] + (into [keyboard-avoiding-view-class + (merge (when platform/ios? {:behavior :padding}) + (if (:ignore-offset props) + props + (update props :keyboardVerticalOffset + 44)))] + children)) + (defn scroll-view [props & children] (vec (conj children props scroll-view-class))) diff --git a/src/status_im/ui/screens/chat/components/input.cljs b/src/status_im/ui/screens/chat/components/input.cljs index 468ae98d88..2a23f90c90 100644 --- a/src/status_im/ui/screens/chat/components/input.cljs +++ b/src/status_im/ui/screens/chat/components/input.cljs @@ -1,6 +1,7 @@ (ns status-im.ui.screens.chat.components.input (:require [status-im.ui.components.icons.icons :as icons] [quo.react-native :as rn] + [oops.core :refer [oget]] [quo.react :as react] [quo.platform :as platform] [quo.components.text :as text] @@ -19,7 +20,11 @@ [quo.components.list.item :as list-item] [status-im.ui.screens.chat.photos :as photos] [reagent.core :as reagent] - [clojure.string :as string])) + [clojure.string :as string] + [quo2.components.button :as quo2.button] + [quo2.reanimated :as reanimated] + [quo2.gesture :as gesture] + [quo.components.safe-area :as safe-area])) (defn input-focus [text-input-ref] (some-> ^js (react/current-ref text-input-ref) .focus)) @@ -58,8 +63,9 @@ :on-denied #(utils.utils/set-timeout (fn [] - (utils.utils/show-popup (i18n/label :t/audio-recorder-error) - (i18n/label :t/audio-recorder-permissions-error))) + (utils.utils/show-popup + (i18n/label :t/audio-recorder-error) + (i18n/label :t/audio-recorder-permissions-error))) 50)}])) (defn touchable-audio-icon [{:keys [panel active set-active accessibility-label input-focus]}] @@ -82,6 +88,15 @@ :accessibility-label :send-message-button :color (styles/send-icon-color)}])]]) +(defn send-button-old [on-send contact-request] + [rn/touchable-opacity {:on-press-in on-send} + [rn/view {:style (styles/send-message-button)} + (when-not contact-request + [icons/icon :main-icons/arrow-up + {:container-style (styles/send-message-container contact-request) + :accessibility-label :send-message-button + :color (styles/send-icon-color)}])]]) + (defn on-selection-change [timeout-id last-text-change mentionable-users args] (let [selection (.-selection ^js (.-nativeEvent ^js args)) start (.-start selection) @@ -128,14 +143,18 @@ (swap! chat-input-key inc)) (defn show-send [{:keys [actions-ref send-ref sticker-ref]}] - (quo.react/set-native-props actions-ref #js {:width 0 :left -88}) + (when actions-ref + (quo.react/set-native-props actions-ref #js {:width 0 :left -88})) (quo.react/set-native-props send-ref #js {:width nil :right nil}) - (quo.react/set-native-props sticker-ref #js {:width 0 :right -100})) + (when sticker-ref + (quo.react/set-native-props sticker-ref #js {:width 0 :right -100}))) (defn hide-send [{:keys [actions-ref send-ref sticker-ref]}] - (quo.react/set-native-props actions-ref #js {:width nil :left nil}) + (when actions-ref + (quo.react/set-native-props actions-ref #js {:width nil :left nil})) (quo.react/set-native-props send-ref #js {:width 0 :right -100}) - (quo.react/set-native-props sticker-ref #js {:width nil :right nil})) + (when sticker-ref + (quo.react/set-native-props sticker-ref #js {:width nil :right nil}))) (defn reset-input [refs chat-id] (some-> ^js (react/current-ref (:text-input-ref refs)) .clear) @@ -235,7 +254,7 @@ (when platform/android? (re-frame/dispatch [::mentions/calculate-suggestions mentionable-users])))) -(defn text-input [{:keys [set-active-panel refs chat-id sending-image]}] +(defn text-input-old [{:keys [set-active-panel refs chat-id sending-image]}] (let [cooldown-enabled? @(re-frame/subscribe [:chats/cooldown-enabled?]) mentionable-users @(re-frame/subscribe [:chats/mentionable-users]) timeout-id (atom nil) @@ -244,7 +263,7 @@ contact-request @(re-frame/subscribe [:chats/sending-contact-request])] [rn/text-input - {:style (styles/text-input contact-request) + {:style (styles/text-input-old contact-request) :ref (:text-input-ref refs) :max-font-size-multiplier 1 :accessibility-label :chat-message-input @@ -274,6 +293,47 @@ text]) (get @input-texts chat-id))])) +(defn text-input [{:keys [set-active-panel refs chat-id sending-image on-content-size-change]}] + (let [cooldown-enabled? @(re-frame/subscribe [:chats/cooldown-enabled?]) + mentionable-users @(re-frame/subscribe [:chats/mentionable-users]) + timeout-id (atom nil) + last-text-change (atom nil) + mentions-enabled (get @mentions-enabled chat-id)] + + [rn/text-input + {:style (styles/text-input) + :ref (:text-input-ref refs) + :max-font-size-multiplier 1 + :accessibility-label :chat-message-input + :text-align-vertical :center + :multiline true + :editable (not cooldown-enabled?) + :blur-on-submit false + :auto-focus false + :on-focus #(set-active-panel nil) + :max-length chat.constants/max-text-size + :placeholder-text-color (:text-02 @colors/theme) + :placeholder (if cooldown-enabled? + (i18n/label :cooldown/text-input-disabled) + (i18n/label :t/type-a-message)) + :underline-color-android :transparent + :auto-capitalize :sentences + :auto-correct false + :spell-check false + :on-content-size-change on-content-size-change + :on-selection-change (partial on-selection-change timeout-id last-text-change mentionable-users) + :on-change (partial on-change last-text-change timeout-id mentionable-users refs chat-id sending-image) + :on-text-input (partial on-text-input mentionable-users chat-id)} + (if mentions-enabled + (for [[idx [type text]] (map-indexed + (fn [idx item] + [idx item]) + @(re-frame/subscribe [:chat/input-with-mentions]))] + ^{:key (str idx "_" type "_" text)} + [rn/text (when (= type :mention) {:style {:color "#0DA4C9"}}) + text]) + (get @input-texts chat-id))])) + (defn mention-item [[public-key {:keys [alias name nickname] :as user}] _ _ text-input-ref] (let [ens-name? (not= alias name)] @@ -350,12 +410,12 @@ :active active-panel :set-active set-active-panel}])]) -(defn chat-toolbar [{:keys [chat-id]}] - (let [actions-ref (quo.react/create-ref) - send-ref (quo.react/create-ref) - sticker-ref (quo.react/create-ref) +(defn chat-toolbar-old [{:keys [chat-id]}] + (let [actions-ref (quo.react/create-ref) + send-ref (quo.react/create-ref) + sticker-ref (quo.react/create-ref) toolbar-options (re-frame/subscribe [:chats/chat-toolbar]) - show-send (seq (get @input-texts chat-id))] + show-send (seq (get @input-texts chat-id))] (fn [{:keys [active-panel set-active-panel text-input-ref chat-id]}] (let [;we want to control components on native level, so instead of RN state we set native props via reference ;we don't react on input text in this view, @input-texts below is a regular atom @@ -364,24 +424,24 @@ :sticker-ref sticker-ref :text-input-ref text-input-ref} {:keys [send stickers image extensions audio sending-image]} @toolbar-options - show-send (or show-send sending-image) + show-send (or show-send sending-image) contact-request @(re-frame/subscribe [:chats/sending-contact-request])] [rn/view {:style (styles/toolbar) :on-layout on-chat-toolbar-layout} - ;;EXTENSIONS and IMAGE buttons + ;;EXTENSIONS and IMAGE buttons [actions extensions image show-send actions-ref active-panel set-active-panel contact-request] [rn/view {:style (styles/input-container contact-request)} [send-image] [rn/view {:style styles/input-row} - [text-input {:chat-id chat-id - :sending-image sending-image - :refs refs - :set-active-panel set-active-panel}] + [text-input-old {:chat-id chat-id + :sending-image sending-image + :refs refs + :set-active-panel set-active-panel}] ;;SEND button [rn/view {:ref send-ref :style (when-not show-send {:width 0 :right -100})} (when send - [send-button #(do (clear-input chat-id refs) - (re-frame/dispatch [:chat.ui/send-current-message])) + [send-button-old #(do (clear-input chat-id refs) + (re-frame/dispatch [:chat.ui/send-current-message])) contact-request])] ;;STICKERS and AUDIO buttons @@ -400,3 +460,163 @@ :active active-panel :input-focus #(input-focus text-input-ref) :set-active set-active-panel}])])]]])))) + +(defn calculate-y [context keyboard-shown min-y max-y] + (if keyboard-shown + (if (= (:state @context) :max) + max-y + (if (< (:y @context) max-y) + (:y @context) + (do + (swap! context assoc :state :max) + max-y))) + (do + (swap! context assoc :state :min) + min-y))) + +(defn get-bottom-sheet-gesture [context translate-y text-input-ref keyboard-shown min-y max-y shared-height max-height bg-opacity] + (-> (gesture/gesture-pan) + (gesture/on-start + (fn [_] + (if keyboard-shown + (swap! context assoc :pan-y (reanimated/get-shared-value translate-y)) + (input-focus text-input-ref)))) + (gesture/on-update + (fn [evt] + (when keyboard-shown + (swap! context assoc :dy (- (.-translationY evt) (:pdy @context))) + (swap! context assoc :pdy (.-translationY evt)) + (reanimated/set-shared-value + translate-y + (max (min (+ (.-translationY evt) (:pan-y @context)) (- min-y)) (- max-y)))))) + (gesture/on-end + (fn [_] + (when keyboard-shown + (if (< (:dy @context) 0) + (do + (swap! context assoc :state :max) + (input-focus text-input-ref) + (reanimated/set-shared-value translate-y (reanimated/with-timing (- max-y))) + (reanimated/set-shared-value shared-height (reanimated/with-timing max-height)) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1))) + (do + (swap! context assoc :state :min) + (reanimated/set-shared-value translate-y (reanimated/with-timing (- min-y))) + (reanimated/set-shared-value shared-height (reanimated/with-timing min-y)) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0)) + (re-frame/dispatch [:dismiss-keyboard])))))))) + +(defn get-input-content-change [context translate-y shared-height max-height bg-opacity keyboard-shown min-y max-y] + (fn [evt] + (if (:clear @context) + (do + (swap! context dissoc :clear) + (swap! context assoc :state :min) + (swap! context assoc :y min-y) + (reanimated/set-shared-value translate-y (reanimated/with-timing (- min-y))) + (reanimated/set-shared-value shared-height (reanimated/with-timing min-y)) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0))) + (when (not= (:state @context) :max) + (let [new-y (+ min-y (- (max (oget evt "nativeEvent" "contentSize" "height") 22) 22))] + (if (< new-y max-y) + (do + (if (> (- max-y new-y) 120) + (do + (swap! context assoc :state :custom-chat-available) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0))) + (do + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1)) + (swap! context assoc :state :custom-chat-unavailable))) + (swap! context assoc :y new-y) + (when keyboard-shown + (reanimated/set-shared-value + translate-y + (reanimated/with-timing (- new-y))) + (reanimated/set-shared-value + shared-height + (reanimated/with-timing (min new-y max-height))))) + (do + (swap! context assoc :state :max) + (swap! context assoc :y max-y) + (when keyboard-shown + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1)) + (reanimated/set-shared-value + translate-y + (reanimated/with-timing (- max-y))))))))))) + +(defn chat-input-bottom-sheet [chat-id] + [safe-area/consumer + (fn [insets] + (let [min-y 112 + context (atom {:y min-y ;current y value + :min-y min-y ;minimum y value + :dy 0 ;used for gesture + :pdy 0 ;used for gesture + :state :min ;:min, :custom-chat-available, :custom-chat-unavailable, :max + :clear false}) + keyboard-was-shown (atom false)] + (fn [] + [:f> + (fn [] + (let [text-input-ref (quo.react/create-ref) + send-ref (quo.react/create-ref) + refs {:send-ref send-ref + :text-input-ref text-input-ref} + + {window-height :height} (rn/use-window-dimensions) + {:keys [keyboard-shown keyboard-height]} (rn/use-keyboard) + + max-y (- window-height (if (> keyboard-height 0) keyboard-height 360) (:top insets)) ; 360 - defaul height + max-height (- max-y 56 (:bottom insets)) ; 56 - topbar height + y (calculate-y context keyboard-shown min-y max-y) + + translate-y (reanimated/use-shared-value 0) + shared-height (reanimated/use-shared-value min-y) + bg-opacity (reanimated/use-shared-value 0) + + bottom-sheet-gesture (get-bottom-sheet-gesture context translate-y text-input-ref keyboard-shown + min-y max-y shared-height max-height bg-opacity) + input-content-change (get-input-content-change context translate-y shared-height max-height + bg-opacity keyboard-shown min-y max-y)] + (quo.react/effect! #(do + (when (and @keyboard-was-shown (not keyboard-shown)) + (swap! context assoc :state :min)) + (reset! keyboard-was-shown keyboard-shown) + (if (#{:max :custom-chat-unavailable} (:state @context)) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 1)) + (reanimated/set-shared-value bg-opacity (reanimated/with-timing 0))) + (reanimated/set-shared-value translate-y (reanimated/with-timing (- y))) + (reanimated/set-shared-value shared-height (reanimated/with-timing (min y max-height))))) + [reanimated/view {:style (reanimated/apply-animations-to-style + {:height shared-height} + {})} + ;;INPUT MESSAGE bottom sheet + [gesture/gesture-detector {:gesture bottom-sheet-gesture} + [reanimated/view {:style (reanimated/apply-animations-to-style + {:transform [{:translateY translate-y}]} + (styles/new-input-bottom-sheet window-height))} + ;handle + [rn/view {:style (styles/new-bottom-sheet-handle)}] + [rn/view {:style {:height (- max-y 80)}} + [text-input {:chat-id chat-id + :on-content-size-change input-content-change + :sending-image false + :refs refs + :set-active-panel #()}]]]] + ;CONTROLS + [rn/view {:style (styles/new-bottom-sheet-controls insets)} + [quo2.button/button {:icon true :type :outline :size 32} :main-icons2/image] + [rn/view {:width 12}] + [quo2.button/button {:icon true :type :outline :size 32} :main-icons2/reaction] + [rn/view {:flex 1}] + ;;SEND button + [rn/view {:ref send-ref :style (when-not (seq (get @input-texts chat-id)) {:width 0 :right -100})} + [quo2.button/button {:icon true :size 32 :accessibility-label :send-message-button + :on-press #(do (swap! context assoc :clear true) + (clear-input chat-id refs) + (re-frame/dispatch [:chat.ui/send-current-message]))} + :main-icons2/arrow-up]]] + ;black background + [reanimated/view {:style (reanimated/apply-animations-to-style + {:opacity bg-opacity} + (styles/new-bottom-sheet-background window-height))}]]))])))]) \ No newline at end of file diff --git a/src/status_im/ui/screens/chat/components/style.cljs b/src/status_im/ui/screens/chat/components/style.cljs index b4e0b7ee31..35c7192fba 100644 --- a/src/status_im/ui/screens/chat/components/style.cljs +++ b/src/status_im/ui/screens/chat/components/style.cljs @@ -1,7 +1,9 @@ (ns status-im.ui.screens.chat.components.style (:require [quo.platform :as platform] [quo.design-system.colors :as colors] - [quo.design-system.typography :as typography])) + [quo.design-system.typography :as typography] + [quo2.foundations.colors :as quo2.colors] + [quo2.foundations.typography :as quo2.typography])) (defn toolbar [] {:min-height 52 @@ -35,7 +37,7 @@ (when platform/ios? {:padding-top 2}))) -(defn text-input [contact-request] +(defn text-input-old [contact-request] (merge typography/font-regular typography/base {:flex 1 @@ -50,13 +52,27 @@ {:padding-top (if contact-request 10 2) :padding-bottom (if contact-request 5 6)}))) +(defn text-input [] + (merge quo2.typography/font-regular + quo2.typography/paragraph-1 + {:flex 1 + :min-height 34 + :margin 0 + :flex-shrink 1 + :color (:text-01 @colors/theme) + :margin-horizontal 20} + (if platform/android? + {:padding-vertical 8 + :text-align-vertical :top} + {:margin-top 8 + :margin-bottom 8}))) + (defn actions-wrapper [show-send] - (merge - (when show-send - {:width 0 :left -88}) - {:flex-direction :row - :padding-left 4 - :min-height 34})) + (merge (when show-send + {:width 0 :left -88}) + {:flex-direction :row + :padding-left 4 + :min-height 34})) (defn touchable-icon [] {:padding-horizontal 10 @@ -85,7 +101,7 @@ :background-color (:ui-03 @colors/theme)}) (defn reply-container [] - {:flex-direction :row}) + {:flex-direction :row}) (defn reply-content-old [] {:padding-vertical 6 @@ -98,9 +114,9 @@ :flex-direction :row}) (defn contact-request-content [] - {:flex 1 - :flex-direction :row - :justify-content :space-between}) + {:flex 1 + :flex-direction :row + :justify-content :space-between}) (defn close-button [] {:margin-top 3}) @@ -128,3 +144,52 @@ :background-color (colors/get-color :ui-background) :border-top-width 1 :border-top-color (colors/get-color :ui-01)}) + +(defn new-input-bottom-sheet [window-height] + (merge {:border-top-left-radius 20 + :border-top-right-radius 20 + :position :absolute + :left 0 + :right 0 + :bottom (- window-height) + :height window-height + :flex 1 + :background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90) + :z-index 1000} + (if platform/ios? + {:shadow-radius 16 + :shadow-opacity 1 + :shadow-color "rgba(9, 16, 28, 0.04)" + :shadow-offset {:width 0 :height -2}} + {:elevation 2}))) + +(defn new-bottom-sheet-handle [] + {:width 32 + :height 4 + :background-color (quo2.colors/theme-colors quo2.colors/black quo2.colors/white) + :opacity 0.05 + :border-radius 100 + :align-self :center + :margin-top 8}) + +(defn new-bottom-sheet-controls [insets] + {:flex-direction :row + :padding-horizontal 20 + :elevation 2 + :z-index 2000 + :position :absolute + :background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-90) + ;these 3 props play together, we need this magic to hide message text in the safe area + :padding-top 10 + :padding-bottom (+ 12 (:bottom insets)) + :bottom (- 2 (:bottom insets))}) + +(defn new-bottom-sheet-background [window-height] + {:pointerEvents :none + :position :absolute + :left 0 + :right 0 + :bottom 0 + :height window-height + :background-color quo2.colors/neutral-95-opa-70 + :z-index 500}) \ No newline at end of file diff --git a/src/status_im/ui/screens/chat/views.cljs b/src/status_im/ui/screens/chat/views.cljs index 657c7b36c4..6488d430be 100644 --- a/src/status_im/ui/screens/chat/views.cljs +++ b/src/status_im/ui/screens/chat/views.cljs @@ -85,23 +85,23 @@ (let [contact-request @(re-frame/subscribe [:chats/sending-contact-request])] [react/view {:style style/contact-request} [react/image {:source (resources/get-image :hand-wave) - :style {:width 112 - :height 96.71 + :style {:width 112 + :height 96.71 :margin-top 17}}] - [quo/text {:style {:margin-top 14} + [quo/text {:style {:margin-top 14} :weight :bold :size :large} (i18n/label :t/say-hi)] - [quo/text {:style {:margin-top 2 + [quo/text {:style {:margin-top 2 :margin-bottom 14}} (i18n/label :t/send-contact-request-message)] (when-not contact-request [react/view {:style {:padding-horizontal 16 - :padding-bottom 8}} + :padding-bottom 8}} [quo/button - {:style {:width "100%"} + {:style {:width "100%"} :accessibility-label :contact-request--button - :on-press #(re-frame/dispatch [:chat.ui/send-contact-request])} + :on-press #(re-frame/dispatch [:chat.ui/send-contact-request])} (i18n/label :t/contact-request)]])])) (defn chat-intro [{:keys [chat-id @@ -116,7 +116,7 @@ no-messages? contact-request-state emoji]}] - [react/view {:style (style/intro-header-container loading-messages? no-messages?) + [react/view {:style (style/intro-header-container loading-messages? no-messages?) :accessibility-label :history-chat} ;; Icon section [react/view {:style {:margin-top 52 @@ -133,34 +133,25 @@ (if group-chat chat-name contact-name)] ;; Description section (if group-chat - [chat.group/group-chat-description-container {:chat-id chat-id - :invitation-admin invitation-admin + [chat.group/group-chat-description-container {:chat-id chat-id + :invitation-admin invitation-admin :loading-messages? loading-messages? - :chat-name chat-name - :chat-type chat-type - :no-messages? no-messages?}] - [react/text {:style (assoc style/intro-header-description - :margin-bottom 32)} - - (str - (i18n/label :t/empty-chat-description-one-to-one) - contact-name)]) - (when - (and - mutual-contact-requests-enabled? - (= chat-type constants/one-to-one-chat-type) - (or - (= contact-request-state constants/contact-request-state-none) - (= contact-request-state constants/contact-request-state-received) - (= contact-request-state constants/contact-request-state-dismissed))) + :chat-name chat-name + :chat-type chat-type + :no-messages? no-messages?}] + [react/text {:style (assoc style/intro-header-description :margin-bottom 32)} + (str (i18n/label :t/empty-chat-description-one-to-one) contact-name)]) + (when (and mutual-contact-requests-enabled? + (= chat-type constants/one-to-one-chat-type) + (or (= contact-request-state constants/contact-request-state-none) + (= contact-request-state constants/contact-request-state-received) + (= contact-request-state constants/contact-request-state-dismissed))) [contact-request])]) (defn chat-intro-one-to-one [{:keys [chat-id] :as opts}] - (let [contact @(re-frame/subscribe - [:contacts/contact-by-identity chat-id]) + (let [contact @(re-frame/subscribe [:contacts/contact-by-identity chat-id]) mutual-contact-requests-enabled? @(re-frame/subscribe [:mutual-contact-requests/enabled?]) - contact-names @(re-frame/subscribe - [:contacts/contact-two-names-by-identity chat-id])] + contact-names @(re-frame/subscribe [:contacts/contact-two-names-by-identity chat-id])] [chat-intro (assoc opts :mutual-contact-requests-enabled? mutual-contact-requests-enabled? :contact-name (first contact-names) @@ -175,21 +166,21 @@ public? emoji]} no-messages] [react/touchable-without-feedback - {:style {:flex 1 - :align-items :flex-start} + {:style {:flex 1 + :align-items :flex-start} :on-press (fn [_] (react/dismiss-keyboard!))} (let [opts - {:chat-id chat-id - :group-chat group-chat - :invitation-admin invitation-admin - :chat-type chat-type - :chat-name chat-name - :public? public? - :color color + {:chat-id chat-id + :group-chat group-chat + :invitation-admin invitation-admin + :chat-type chat-type + :chat-name chat-name + :public? public? + :color color :loading-messages? (not (pos? synced-to)) - :no-messages? no-messages - :emoji emoji}] + :no-messages? no-messages + :emoji emoji}] (if group-chat [chat-intro opts] [chat-intro-one-to-one opts]))]) @@ -241,15 +232,15 @@ [toolbar/toolbar {:show-border? true :right [quo/button - {:type :secondary + {:type :secondary :accessibility-label :retry-button - :on-press #(re-frame/dispatch [:group-chats.ui/membership-retry])} + :on-press #(re-frame/dispatch [:group-chats.ui/membership-retry])} (i18n/label :t/mailserver-retry)] :left [quo/button - {:type :secondary + {:type :secondary :accessibility-label :remove-group-button - :on-press #(re-frame/dispatch [:group-chats.ui/remove-chat-confirmed chat-id])} + :on-press #(re-frame/dispatch [:group-chats.ui/remove-chat-confirmed chat-id])} (i18n/label :t/remove-group)]}] :else [toolbar/toolbar {:show-border? true @@ -442,7 +433,7 @@ (let [{:keys [group-chat chat-type chat-id public? community-id admins]} chat messages @(re-frame/subscribe [:chats/raw-chat-messages-stream chat-id]) - one-to-one? (= chat-type constants/one-to-one-chat-type) + one-to-one? (= chat-type constants/one-to-one-chat-type) contact-added? (when one-to-one? @(re-frame/subscribe [:contacts/contact-added? chat-id])) should-send-contact-request? (and @@ -473,9 +464,9 @@ :on-viewable-items-changed on-viewable-items-changed :on-end-reached list-on-end-reached :on-scroll-to-index-failed identity ;;don't remove this - :content-container-style {:padding-top (+ bottom-space 16) + :content-container-style {:padding-top (+ bottom-space 16) :padding-bottom 16} - :scroll-indicator-insets {:top bottom-space} ;;ios only + :scroll-indicator-insets {:top bottom-space} ;;ios only :keyboard-dismiss-mode :interactive :keyboard-should-persist-taps :handled :onMomentumScrollBegin state/start-scrolling @@ -525,10 +516,10 @@ (defn topbar-old [] ;;we don't use topbar component, because we want chat view as simple (fast) as possible [react/view {:height 56} - [react/touchable-highlight {:on-press-in navigate-back-handler + [react/touchable-highlight {:on-press-in navigate-back-handler :accessibility-label :back-button - :style {:height 56 :width 40 :align-items :center :justify-content :center - :padding-left 16}} + :style {:height 56 :width 40 :align-items :center :justify-content :center + :padding-left 16}} [icons/icon :main-icons/arrow-left {:color colors/black}]] [react/view {:flex 1 :left 52 :right 52 :top 0 :bottom 0 :position :absolute} [topbar-content-old]] @@ -537,9 +528,9 @@ [sheets/current-chat-actions]) :height 256}]) :accessibility-label :chat-menu-button - :style {:right 0 :top 0 :bottom 0 :position :absolute - :height 56 :width 40 :align-items :center :justify-content :center - :padding-right 16}} + :style {:right 0 :top 0 :bottom 0 :position :absolute + :height 56 :width 40 :align-items :center :justify-content :center + :padding-right 16}} [icons/icon :main-icons/more {:color colors/black}]]]) (defn chat-render-old [] @@ -592,7 +583,7 @@ ;; We set the key so we can force a re-render as ;; it does not rely on ratom but just atoms ^{:key (str @components/chat-input-key "chat-input")} - [components/chat-toolbar + [components/chat-toolbar-old {:chat-id chat-id :active-panel @active-panel :set-active-panel set-active-panel @@ -601,70 +592,33 @@ [bottom-sheet @active-panel]])])))) (defn chat-render [] - (let [bottom-space (reagent/atom 0) - panel-space (reagent/atom 52) - active-panel (reagent/atom nil) - position-y (animated/value 0) - pan-state (animated/value 0) - text-input-ref (quo.react/create-ref) - on-update #(when-not (zero? %) (reset! panel-space %)) - pan-responder (accessory/create-pan-responder position-y pan-state) - space-keeper (get-space-keeper-ios bottom-space panel-space active-panel text-input-ref) - set-active-panel (get-set-active-panel active-panel) - on-close #(set-active-panel nil)] - (fn [] - (let [{:keys [chat-id show-input? group-chat admins invitation-admin] :as chat} - ;;we want to react only on these fields, do not use full chat map here - @(re-frame/subscribe [:chats/current-chat-chat-view]) - mutual-contact-requests-enabled? @(re-frame/subscribe [:mutual-contact-requests/enabled?]) - max-bottom-space (max @bottom-space @panel-space)] - [:<> - ;; It is better to not use topbar component because of performance - [topbar/topbar {:navigation :none - :left-component [react/view {:flex-direction :row :margin-left 16} - [back-button]] - :title-component [topbar-content] - :right-component [react/view {:flex-direction :row :margin-right 16} - [search-button]] - :border-bottom false - :new-ui? true}] - [connectivity/loading-indicator] - (when chat-id - (if group-chat - [invitation-requests chat-id admins] - (when-not mutual-contact-requests-enabled? [add-contact-bar chat-id]))) - ;;MESSAGES LIST - [messages-view {:chat chat - :bottom-space max-bottom-space - :pan-responder pan-responder - :mutual-contact-requests-enabled? mutual-contact-requests-enabled? - :space-keeper space-keeper - :show-input? show-input?}] - (when (and group-chat invitation-admin) - [accessory/view {:y position-y - :on-update-inset on-update} - [invitation-bar chat-id]]) - [components/autocomplete-mentions text-input-ref max-bottom-space] - (when show-input? - ;; NOTE: this only accepts two children - [accessory/view {:y position-y - :pan-state pan-state - :has-panel (boolean @active-panel) - :on-close on-close - :on-update-inset on-update} - [react/view - [edit/edit-message-auto-focus-wrapper text-input-ref] - [reply/reply-message-auto-focus-wrapper text-input-ref] - ;; We set the key so we can force a re-render as - ;; it does not rely on ratom but just atoms - ^{:key (str @components/chat-input-key "chat-input")} - [components/chat-toolbar - {:chat-id chat-id - :active-panel @active-panel - :set-active-panel set-active-panel - :text-input-ref text-input-ref}] - [contact-request/contact-request-message-auto-focus-wrapper text-input-ref]] - [bottom-sheet @active-panel]])])))) + (let [{:keys [chat-id show-input? group-chat admins] :as chat} + ;;we want to react only on these fields, do not use full chat map here + @(re-frame/subscribe [:chats/current-chat-chat-view]) + mutual-contact-requests-enabled? @(re-frame/subscribe [:mutual-contact-requests/enabled?])] + [react/keyboard-avoiding-view-new {:style {:flex 1} + :ignore-offset false} + ;; It is better to not use topbar component because of performance + [topbar/topbar {:navigation :none + :left-component [react/view {:flex-direction :row :margin-left 16} + [back-button]] + :title-component [topbar-content] + :right-component [react/view {:flex-direction :row :margin-right 16} + [search-button]] + :border-bottom false + :new-ui? true}] + [connectivity/loading-indicator] + (when chat-id + (if group-chat + [invitation-requests chat-id admins] + (when-not mutual-contact-requests-enabled? [add-contact-bar chat-id]))) + ;;MESSAGES LIST + [messages-view {:chat chat + :mutual-contact-requests-enabled? mutual-contact-requests-enabled? + :show-input? show-input?}] + ;;INPUT COMPONENT + (when show-input? + [components/chat-input-bottom-sheet chat-id])])) (defn chat-old [] (reagent/create-class @@ -676,8 +630,8 @@ (defn chat [] (reagent/create-class - {:component-did-mount (fn [] - (react/hw-back-remove-listener navigate-back-handler) - (react/hw-back-add-listener navigate-back-handler)) + {:component-did-mount (fn [] + (react/hw-back-remove-listener navigate-back-handler) + (react/hw-back-add-listener navigate-back-handler)) :component-will-unmount (fn [] (react/hw-back-remove-listener navigate-back-handler)) - :reagent-render chat-render})) + :reagent-render chat-render})) diff --git a/yarn.lock b/yarn.lock index 79564d433a..c01ac766af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6133,7 +6133,7 @@ lodash.union@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" integrity sha1-SLtQiECfFvGCFmZkHETdGqrjzYg= -lodash@4.17.x: +lodash@4.17.x, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8026,14 +8026,15 @@ react-native-fs@^2.14.1: base-64 "^0.1.0" utf8 "^3.0.0" -react-native-gesture-handler@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-1.8.0.tgz#18f61f51da50320f938957b0ee79bc58f47449dc" - integrity sha512-E2FZa0qZ5Bi0Z8Jg4n9DaFomHvedSjwbO2DPmUUHYRy1lH2yxXUpSrqJd6yymu+Efzmjg2+JZzsjFYA2Iq8VEQ== +react-native-gesture-handler@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-2.5.0.tgz#61385583570ed0a45a9ed142425e35f8fe8274fb" + integrity sha512-djZdcprFf08PZC332D+AeG5wcGeAPhzfCJtB3otUgOgTlvjVXmg/SLFdPJSpzLBqkRAmrC77tM79QgKbuLxkfw== dependencies: "@egjs/hammerjs" "^2.0.17" hoist-non-react-statics "^3.3.0" invariant "^2.2.4" + lodash "^4.17.21" prop-types "^15.7.2" react-native-haptic-feedback@^1.9.0: