Introduce new ui2 namespace, move chat to new namespace and remove new UI toggle from UI (#14156)

* enable new ui by default, introduce new ui2 namespace, move chat to the new namespace
This commit is contained in:
flexsurfer 2022-10-17 15:44:48 +02:00 committed by GitHub
parent 094a7421c1
commit 00fbb0dee6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 2117 additions and 1922 deletions

View File

@ -1,6 +1,5 @@
(ns quo.components.bottom-sheet.style
(:require [quo.design-system.colors :as colors]
[quo2.foundations.colors :as quo2.colors]
[quo.design-system.spacing :as spacing]))
(def border-radius 16)
@ -25,12 +24,8 @@
:bottom 0})
(defn content-container
[window-height new-ui?]
{:background-color (if new-ui?
(quo2.colors/theme-colors
quo2.colors/white
quo2.colors/neutral-90)
(:ui-background @colors/theme))
[window-height]
{:background-color (:ui-background @colors/theme)
:border-top-left-radius border-radius
:border-top-right-radius border-radius
:height (* window-height 2)})

View File

@ -8,8 +8,7 @@
[quo.components.safe-area :as safe-area]
[quo.components.bottom-sheet.style :as styles]
[quo.gesture-handler :as gesture-handler]
[quo.design-system.colors :as colors]
[status-im.utils.config :as config]))
[quo.design-system.colors :as colors]))
(def opacity-coeff 0.8)
(def close-duration 150)
@ -102,8 +101,7 @@
{:inputRange [(animated/multiply open-snap-point opacity-coeff) 0]
:outputRange [1 0]
:extrapolate (:clamp animated/extrapolate)})))
[])
new-ui? @config/new-ui-enabled?]
[])]
(animated/code!
(fn []
(animated/cond* (animated/and* (animated/eq master-state (:end gesture-handler/states))
@ -187,7 +185,7 @@
(when platform/ios?
{:opacity opacity
:background-color (:backdrop @colors/theme)}))}]]
[animated/view {:style (merge (styles/content-container window-height new-ui?)
[animated/view {:style (merge (styles/content-container window-height)
{:transform [{:translateY (if (= sheet-height max-height)
(animated/add translate-y keyboard-height-android-delta)
translate-y)}

View File

@ -8,25 +8,21 @@
[quo.components.text :as text]
[quo.components.controls.view :as controls]
[quo.components.tooltip :as tooltip]
;; FIXME:
[status-im.ui.components.icons.icons :as icons]
[quo2.foundations.colors :as quo2.colors]
[quo2.components.icon :as quo.icons]
[status-im.utils.config :as config]
[quo.components.animated.pressable :as animated]))
(defn themes [theme]
(case theme
:main {:icon-color (:icon-04 @colors/theme)
:icon-bg-color (:interactive-02 @colors/theme)
:active-background (:interactive-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:text-01 @colors/theme)}
:accent {:icon-color (:icon-04 @colors/theme)
:icon-bg-color (:interactive-02 @colors/theme)
:active-background (:interactive-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:text-04 @colors/theme)}
:main {:icon-color (:icon-04 @colors/theme)
:icon-bg-color (:interactive-02 @colors/theme)
:active-background (:interactive-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:text-01 @colors/theme)}
:accent {:icon-color (:icon-04 @colors/theme)
:icon-bg-color (:interactive-02 @colors/theme)
:active-background (:interactive-02 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:text-04 @colors/theme)}
:negative {:icon-color (:negative-01 @colors/theme)
:icon-bg-color (:negative-02 @colors/theme)
:active-background (:negative-02 @colors/theme)
@ -41,17 +37,7 @@
:icon-bg-color (:ui-01 @colors/theme)
:active-background (:ui-01 @colors/theme)
:passive-background (:ui-background @colors/theme)
:text-color (:text-02 @colors/theme)}
:light {:icon-color quo2.colors/neutral-50
:icon-bg-color quo2.colors/white
:text-color quo2.colors/neutral-100
:active-background quo2.colors/neutral-10
:passive-background quo2.colors/white}
:dark {:icon-color quo2.colors/neutral-40
:icon-bg-color quo2.colors/neutral-90
:text-color quo2.colors/white
:active-background quo2.colors/neutral-70
:passive-background quo2.colors/neutral-90}))
:text-color (:text-02 @colors/theme)}))
(defn size->icon-size [size]
(case size
@ -80,26 +66,19 @@
(defn icon-column
[{:keys [icon icon-bg-color icon-color size icon-container-style]}]
(when icon
(let [icon-size (size->icon-size size)
new-ui? @config/new-ui-enabled?]
(let [icon-size (size->icon-size size)]
[rn/view {:style (or icon-container-style (:tiny spacing/padding-horizontal))}
(cond
(vector? icon)
icon
(keyword? icon)
(if new-ui?
[quo.icons/icon icon {:container-style {:align-items :center
:justify-content :center}
:color icon-color
:size 20
:resize-mode :center}]
[rn/view {:style {:width icon-size
:height icon-size
:align-items :center
:justify-content :center
:border-radius (/ icon-size 2)
:background-color icon-bg-color}}
[icons/icon icon {:color icon-color}]]))])))
[rn/view {:style {:width icon-size
:height icon-size
:align-items :center
:justify-content :center
:border-radius (/ icon-size 2)
:background-color icon-bg-color}}
[icons/icon icon {:color icon-color}]])])))
(defn title-column
[{:keys [title text-color subtitle subtitle-max-lines subtitle-secondary

View File

@ -281,12 +281,13 @@
{:events [:contacts/send-contact-request]}
[{:keys [db] :as cofx} public-key message]
(fx/merge cofx
{:chat.ui/clear-inputs nil
::json-rpc/call [{:method "wakuext_sendContactRequest"
:js-response true
:params [{:id public-key :message message}]
:on-error #(log/warn "failed to send a contact request" %)
:on-success #(re-frame/dispatch [:transport/message-sent %])}]}
{:chat.ui/clear-inputs nil
:chat.ui/clear-inputs-old nil
::json-rpc/call [{:method "wakuext_sendContactRequest"
:js-response true
:params [{:id public-key :message message}]
:on-error #(log/warn "failed to send a contact request" %)
:on-success #(re-frame/dispatch [:transport/message-sent %])}]}
(mentions/clear-mentions)
(mentions/clear-cursor)
(clean-input (:current-chat-id db))

View File

@ -5,7 +5,6 @@
[re-frame.core :as re-frame]
[re-frame.interop :as interop]
[reagent.impl.batching :as batching]
[status-im.async-storage.core :as async-storage]
status-im.events
[status-im.i18n.i18n :as i18n]
[status-im.native-module.core :as status]
@ -40,8 +39,6 @@
(utils.universal-links/initialize)
(async-storage/get-item :new-ui-enabled? #(reset! config/new-ui-enabled? %))
;;DEV
(snoopy/subscribe!)
(when (and js/goog.DEBUG platform/ios? DevSettings)

View File

@ -468,7 +468,7 @@
"Decides which root should be initialised depending on user and app state"
[db]
(if (get db :tos/accepted?)
(re-frame/dispatch [:init-root (if @config/new-ui-enabled? :home-stack :chat-stack)])
(re-frame/dispatch [:init-root (if config/new-ui-enabled? :home-stack :chat-stack)])
(re-frame/dispatch [:init-root :tos])))
(fx/defn login-only-events
@ -515,11 +515,14 @@
(multiaccounts/switch-preview-privacy-mode-flag)
(link-preview/request-link-preview-whitelist)
(logging/set-log-level (:log-level multiaccount))
;; if it's a first account, the ToS will be accepted at welcome carousel
;; if not a first account, the ToS might have been accepted by other account logins
(if (or first-account? tos-accepted?)
(navigation/init-root :onboarding-notification)
(navigation/init-root :tos)))))
(if config/new-ui-enabled?
(navigation/init-root :home-stack)
;; if it's a first account, the ToS will be accepted at welcome carousel
;; if not a first account, the ToS might have been accepted by other account logins
(if (or first-account? tos-accepted?)
(navigation/init-root :onboarding-notification)
(navigation/init-root :tos))))))
(defn- keycard-setup? [cofx]
(boolean (get-in cofx [:db :keycard :flow])))

View File

@ -16,6 +16,7 @@
(fx/merge cofx
{:init-root-fx :progress
:chat.ui/clear-inputs nil
:chat.ui/clear-inputs-old nil
:new-ui/reset-bottom-tabs nil
:hide-popover nil
::logout nil

View File

@ -1,21 +1,10 @@
(ns status-im.navigation2
(:require [status-im.utils.fx :as fx]
[status-im.reloader :as reloader]
[status-im.utils.config :as config]
[status-im.utils.datetime :as datetime]
[status-im.async-storage.core :as async-storage]))
[status-im.utils.datetime :as datetime]))
(def parent-stack (atom :home-stack))
(fx/defn toggle-new-ui
{:events [:toggle-new-ui]}
[_]
(swap! config/new-ui-enabled? not)
(reloader/reload)
{:new-ui/reset-bottom-tabs nil
:dispatch [:init-root (if @config/new-ui-enabled? :home-stack :chat-stack)]
::async-storage/set! {:new-ui-enabled? @config/new-ui-enabled?}})
(fx/defn reload-new-ui
{:events [:reload-new-ui]}
[_]
@ -54,9 +43,8 @@
(fx/defn navigate-to-nav2
{:events [:navigate-to-nav2]}
[{:keys [db] :as cofx} go-to-view-id id screen-params from-switcher?]
[{:keys [db]} go-to-view-id id _ from-switcher?]
(let [view-id (:view-id db)
stacks (:navigation2/navigation2-stacks db)
from-home? (= view-id :chat-stack)]
(if from-switcher?
(navigate-from-switcher go-to-view-id id db from-home?)

View File

@ -1,14 +1,15 @@
(ns status-im.navigation2.screens
(:require [status-im.ui.screens.chat.views :as chat]
(:require [status-im.ui2.screens.chat.view :as chat]
[status-im.switcher.home-stack :as home-stack]
[status-im.navigation2.stack-with-switcher :as stack-with-switcher]))
;; We have to use the home screen name :chat-stack for now, for compatibility with navigation.cljs
(def screens [{:name :chat-stack ;; TODO(parvesh) - rename to home-stack
(def screens [{:name :chat-stack ;; TODO(parvesh) - rename to home-stack
:insets {:top false}
:component home-stack/home}])
;; These screens will overwrite navigation/screens.cljs screens on enabling new UI toggle
(def screen-overwrites
[{:name :chat
:component #(stack-with-switcher/overlap-stack chat/chat :chat)}])
[{:name :chat
:options {:topBar {:visible false}}
:component #(stack-with-switcher/overlap-stack chat/chat :chat)}])

View File

@ -13,7 +13,7 @@
:<- [:home-items-show-number]
(fn [[search-filter filtered-chats communities view-id home-items-show-number]]
(if (or (= view-id :home)
(and @config/new-ui-enabled? (= view-id :chat-stack)))
(and config/new-ui-enabled? (= view-id :chat-stack)))
(let [communities-count (count communities)
chats-count (count filtered-chats)
;; If we have both communities & chats we want to display

View File

@ -72,7 +72,7 @@
(do
(js-delete response-js "activityCenterNotifications")
(fx/merge cofx
(if (and @config/new-ui-enabled? @config/new-activity-center-enabled?)
(if config/new-activity-center-enabled?
(->> activity-notifications
types/js->clj
(map data-store.activities/<-rpc)

View File

@ -39,7 +39,7 @@
:accessibility-label :notifications-button
:on-press #(do
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
(if (and @config/new-ui-enabled? @config/new-activity-center-enabled?)
(if config/new-activity-center-enabled?
(re-frame/dispatch [:navigate-to :activity-center])
(re-frame/dispatch [:navigate-to :notifications-center])))}
:main-icons2/notifications]

View File

@ -14,7 +14,6 @@
wakuv2-flag
current-fleet
webview-debug
new-ui-enabled?
mutual-contact-requests-enabled?]}]
(keep
identity
@ -116,14 +115,7 @@
#(re-frame/dispatch
[:multiaccounts.ui/switch-mutual-contact-requests-enabled (not mutual-contact-requests-enabled?)])
:accessory :switch
:active mutual-contact-requests-enabled?}
{:size :small
:title (i18n/label :t/new-ui)
:accessibility-label :new-ui-toggle
:container-margin-bottom 8
:on-press #(re-frame/dispatch [:toggle-new-ui])
:accessory :switch
:active new-ui-enabled?}]))
:active mutual-contact-requests-enabled?}]))
(defn- flat-list-data [options]
(normal-mode-settings-data options))
@ -154,7 +146,6 @@
:wakuv2-flag wakuv2-flag
:waku-bloom-filter-mode waku-bloom-filter-mode
:webview-debug webview-debug
:new-ui-enabled? @config/new-ui-enabled?
:mutual-contact-requests-enabled? mutual-contact-requests-enabled?})
:key-fn (fn [_ i] (str i))
:render-fn render-item}]))

View File

@ -12,7 +12,7 @@
[react/touchable-highlight
{:on-press (fn []
(re-frame/dispatch [:multiaccounts.ui/appearance-switched theme])
(re-frame/dispatch (if @config/new-ui-enabled? [:reload-new-ui] [:init-root :chat-stack]))
(re-frame/dispatch (if config/new-ui-enabled? [:reload-new-ui] [:init-root :chat-stack]))
(re-frame/dispatch [:navigate-change-tab :profile])
(js/setTimeout #(re-frame/dispatch [:navigate-to :appearance]) 1000))}
[react/view (merge {:align-items :center :padding 8 :border-radius 20}

View File

@ -7,19 +7,16 @@
[quo.components.animated.pressable :as pressable]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.screens.chat.components.style :as styles]
[re-frame.core :as re-frame]
[quo2.foundations.colors :as quo2.colors :refer [theme-colors]]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.markdown.text :as quo2.text]))
[re-frame.core :as re-frame]))
(defn input-focus [text-input-ref]
(some-> ^js (quo.react/current-ref text-input-ref) .focus))
(defn edit-message-old []
(defn edit-message []
[rn/view {:style {:flex-direction :row}}
[rn/view {}
[icons/icon :tiny-icons/tiny-edit {:container-style {:margin-top 5}}]]
[rn/view {:style (styles/reply-content-old)}
[rn/view {:style (styles/reply-content)}
[quo/text {:weight :medium
:number-of-lines 1}
(i18n/label :t/editing-message)]]
@ -29,24 +26,6 @@
[icons/icon :main-icons/close-circle {:container-style (styles/close-button)
:color (:icon-02 @quo.colors/theme)}]]]])
(defn edit-message []
[rn/view {:style {:flex-direction :row :height 24}}
[rn/view {:style (styles/reply-content false)}
[icons/icon :main-icons/edit-connector {:color (theme-colors quo2.colors/neutral-40 quo2.colors/neutral-60)
:container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}]
[rn/view {:style {:position :absolute :left 36 :right 54 :top 3 :flex-direction :row :align-items :center}}
[quo2.text/text {:weight :medium
:size :paragraph-2}
(i18n/label :t/editing-message)]]]
[quo2.button/button {:width 24
:size 24
:type :outline
:accessibility-label :reply-cancel-button
:on-press #(re-frame/dispatch [:chat.ui/cancel-message-edit])}
[icons/icon :main-icons/close {:width 16
:height 16
:color (theme-colors quo2.colors/neutral-100 quo2.colors/neutral-40)}]]])
(defn focus-input-on-edit [edit had-edit text-input-ref]
;;when we show edit we focus input
(when-not (= edit @had-edit)
@ -54,13 +33,6 @@
(when edit
(js/setTimeout #(input-focus text-input-ref) 250))))
(defn edit-message-wrapper-old [edit]
[rn/view {:style {:padding-horizontal 15
:border-top-width 1
:border-top-color (:ui-01 @quo.colors/theme)
:padding-vertical 8}}
[edit-message-old edit]])
(defn edit-message-wrapper [edit]
[rn/view {:style {:padding-horizontal 15
:border-top-width 1
@ -68,18 +40,10 @@
:padding-vertical 8}}
[edit-message edit]])
(defn edit-message-auto-focus-wrapper-old [text-input-ref]
(let [had-edit (atom nil)]
(fn []
(let [edit @(re-frame/subscribe [:chats/edit-message])]
(focus-input-on-edit edit had-edit text-input-ref)
(when edit
[edit-message-wrapper-old])))))
(defn edit-message-auto-focus-wrapper [text-input-ref]
(let [had-edit (atom nil)]
(fn []
(let [edit @(re-frame/subscribe [:chats/edit-message])]
(focus-input-on-edit edit had-edit text-input-ref)
(when edit
[edit-message-wrapper])))))
[edit-message-wrapper])))))

View File

@ -1,15 +1,12 @@
(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 quo.react]
[quo.platform :as platform]
[quo.components.text :as text]
[quo.design-system.colors :as colors]
[quo2.foundations.colors :as quo2.colors]
[status-im.ui.screens.chat.components.style :as styles]
[status-im.utils.fx :as fx]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui.screens.chat.components.reply :as reply]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.chat.constants :as chat.constants]
@ -22,11 +19,7 @@
[quo.components.list.item :as list-item]
[status-im.ui.screens.chat.photos :as photos]
[reagent.core :as reagent]
[clojure.string :as string]
[quo2.components.buttons.button :as quo2.button]
[quo2.reanimated :as reanimated]
[quo2.gesture :as gesture]
[quo.components.safe-area :as safe-area]))
[clojure.string :as string]))
(defn input-focus [text-input-ref]
(some-> ^js (quo.react/current-ref text-input-ref) .focus))
@ -90,15 +83,6 @@
: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)
@ -132,7 +116,7 @@
(defonce chat-input-key (reagent/atom 1))
(re-frame/reg-fx
:chat.ui/clear-inputs
:chat.ui/clear-inputs-old
(fn []
(reset! input-texts {})
(reset! mentions-enabled {})
@ -263,7 +247,7 @@
(when platform/android?
(re-frame/dispatch [::mentions/calculate-suggestions mentionable-users]))))
(defn text-input-old [{:keys [set-active-panel refs chat-id sending-image]}]
(defn text-input [{:keys [set-active-panel refs chat-id sending-image]}]
(let [cooldown-enabled? @(re-frame/subscribe [:chats/current-chat-cooldown-enabled?])
mentionable-users @(re-frame/subscribe [:chats/mentionable-users])
timeout-id (atom nil)
@ -272,7 +256,7 @@
contact-request @(re-frame/subscribe [:chats/sending-contact-request])]
[rn/text-input
{:style (styles/text-input-old contact-request)
{:style (styles/text-input contact-request)
:ref (:text-input-ref refs)
:max-font-size-multiplier 1
:accessibility-label :chat-message-input
@ -302,83 +286,43 @@
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/current-chat-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 quo2.colors/primary-50}}) 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)]
[rn/touchable-opacity
{:on-press #(re-frame/dispatch [:chat.ui/select-mention text-input-ref user])}
[list-item/list-item
(cond-> {:icon [photos/member-photo public-key]
:size :small
:text-size :small
:title
[text/text
{:weight :medium
:ellipsize-mode :tail
:number-of-lines 1
:size :small}
(if nickname
nickname
name)
(when nickname
[text/text
{:weight :regular
:color :secondary
:ellipsize-mode :tail
:size :small}
" "
(when ens-name?
"@")
name])]
:title-text-weight :medium
:on-press
(fn []
(re-frame/dispatch [:chat.ui/select-mention text-input-ref user]))}
[list-item/list-item
(cond-> {:icon [photos/member-photo public-key]
:size :small
:text-size :small
:title
[text/text
{:weight :medium
:ellipsize-mode :tail
:number-of-lines 1
:size :small}
(if nickname
nickname
name)
(when nickname
[text/text
{:weight :regular
:color :secondary
:ellipsize-mode :tail
:size :small}
" "
(when ens-name?
"@")
name])]
:title-text-weight :medium}
ens-name?
(assoc :subtitle alias))]]))
ens-name?
(assoc :subtitle alias))]))
(def chat-toolbar-height (reagent/atom nil))
(defn autocomplete-mentions-old [text-input-ref bottom]
(defn autocomplete-mentions [text-input-ref bottom]
(let [suggestions @(re-frame/subscribe [:chat/mention-suggestions])]
(when (seq suggestions)
(let [height (+ 16 (* 52 (min 4.5 (count suggestions))))]
@ -396,22 +340,6 @@
:render-data text-input-ref
:render-fn mention-item}]]]))))
(defn autocomplete-mentions [suggestions]
[:f>
(fn []
(let [animation (reanimated/use-shared-value 0)]
(quo.react/effect! #(do
(reanimated/set-shared-value animation (reanimated/with-timing (if (seq suggestions) 0 200)))))
[reanimated/view {:style (reanimated/apply-animations-to-style
{:transform [{:translateY animation}]}
{:bottom 0 :position :absolute :z-index 5 :max-height 180})}
[list/flat-list
{:keyboardShouldPersistTaps :always
:data suggestions
:key-fn first
:render-fn mention-item
:content-container-style {:padding-bottom 12}}]]))])
(defn on-chat-toolbar-layout [^js ev]
(reset! chat-toolbar-height (-> ev .-nativeEvent .-layout .-height)))
@ -434,7 +362,7 @@
:active active-panel
:set-active set-active-panel}])])
(defn chat-toolbar-old [{:keys [chat-id]}]
(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)
@ -457,15 +385,15 @@
[rn/view {:style (styles/input-container contact-request)}
[send-image]
[rn/view {:style styles/input-row}
[text-input-old {:chat-id chat-id
:sending-image sending-image
:refs refs
:set-active-panel set-active-panel}]
[text-input {: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-old #(do (clear-input chat-id refs)
(re-frame/dispatch [:chat.ui/send-current-message]))
[send-button #(do (clear-input chat-id refs)
(re-frame/dispatch [:chat.ui/send-current-message]))
contact-request])]
;;STICKERS and AUDIO buttons
@ -483,182 +411,4 @@
:accessibility-label :show-audio-message-icon
: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 added-value]
(if keyboard-shown
(if (= (:state @context) :max)
max-y
(if (< (:y @context) max-y)
(+ (:y @context) added-value)
(do
(swap! context assoc :state :max)
max-y)))
(do
(swap! context assoc :state :min)
min-y)))
(defn calculate-y-with-mentions [y max-y max-height chat-id suggestions reply]
(let [input-text (:input-text (get (<sub [:chat/inputs]) chat-id))
num-lines (count (string/split input-text "\n"))
text-height (* num-lines 22)
mentions-height (min 132 (+ 16 (* 46 (- (count suggestions) 1))))
should-translate (if (< (- max-height text-height) mentions-height) true false)
min-value (if-not reply mentions-height (+ mentions-height 44))
; translate value when mentions list appear while at bottom of expanded input sheet
mentions-translate-value (if should-translate (min min-value (- mentions-height (- max-height text-height))) mentions-height)]
(when (or (< y max-y) should-translate) mentions-translate-value)))
(defn get-y-value [context keyboard-shown min-y max-y added-value max-height chat-id suggestions reply]
(let [y (calculate-y context keyboard-shown min-y max-y added-value)]
y (+ y (when (seq suggestions) (calculate-y-with-mentions y max-y max-height chat-id suggestions reply)))))
(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)
text-input-ref (quo.react/create-ref)
send-ref (quo.react/create-ref)
refs {:send-ref send-ref
:text-input-ref text-input-ref}]
(fn []
[:f>
(fn []
(let [reply (<sub [:chats/reply-message])
suggestions (<sub [:chat/mention-suggestions])
{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 - default height
max-height (- max-y 56 (:bottom insets)) ; 56 - top-bar height
added-value (if (and (not (seq suggestions)) reply) 38 0) ; increased height of input box needed when reply
min-y (+ min-y (when reply 38))
y (get-y-value context keyboard-shown min-y max-y added-value max-height chat-id suggestions reply)
translate-y (reanimated/use-shared-value 0)
shared-height (reanimated/use-shared-value min-y)
bg-opacity (reanimated/use-shared-value 0)
input-content-change (get-input-content-change context translate-y shared-height max-height
bg-opacity keyboard-shown min-y max-y)
bottom-sheet-gesture (get-bottom-sheet-gesture context translate-y (:text-input-ref refs) keyboard-shown
min-y max-y shared-height max-height bg-opacity)]
(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)}]
[reply/reply-message-auto-focus-wrapper (:text-input-ref refs) reply]
[rn/view {:style {:height (- max-y 80 added-value)}}
[text-input {:chat-id chat-id
:on-content-size-change input-content-change
:sending-image false
:refs refs
:set-active-panel #()}]]]]
;CONTROLS
(when-not (seq suggestions)
[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)
(>evt [: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))}]
[autocomplete-mentions suggestions]]))])))])
:set-active set-active-panel}])])]]]))))

View File

@ -9,27 +9,13 @@
[status-im.ethereum.stateofus :as stateofus]
[status-im.ui.screens.chat.components.style :as styles]
[re-frame.core :as re-frame]
[clojure.string :as string]
[quo2.foundations.colors :as quo2.colors :refer [theme-colors]]
[quo2.components.buttons.button :as quo2.button]
[quo2.components.markdown.text :as quo2.text]
[status-im.ui.screens.chat.photos :as photos]
[status-im.constants :as constants]))
[clojure.string :as string]))
(def ^:private reply-symbol "↪ ")
(defn input-focus [text-input-ref]
(some-> ^js (quo.react/current-ref text-input-ref) .focus))
(defn format-author-old [contact-name]
(let [author (if (or (= (aget contact-name 0) "@")
;; in case of replies
(= (aget contact-name 1) "@"))
(or (stateofus/username contact-name)
(subs contact-name 0 81))
contact-name)]
(i18n/label :replying-to {:author author})))
(defn format-author [contact-name]
(let [author (if (or (= (aget contact-name 0) "@")
;; in case of replies
@ -37,17 +23,12 @@
(or (stateofus/username contact-name)
(subs contact-name 0 81))
contact-name)]
author))
(defn format-reply-author-old [from username current-public-key]
(or (and (= from current-public-key)
(str reply-symbol (i18n/label :t/You)))
(str reply-symbol (format-author-old username))))
(i18n/label :replying-to {:author author})))
(defn format-reply-author [from username current-public-key]
(or (and (= from current-public-key)
(i18n/label :t/You))
(format-author username)))
(str reply-symbol (i18n/label :t/You)))
(str reply-symbol (format-author username))))
(defn get-quoted-text-with-mentions [parsed-text]
(string/join
@ -66,67 +47,25 @@
literal))
parsed-text)))
(defn reply-message-old [{:keys [from]}]
(defn reply-message [{:keys [from]}]
(let [contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from])
current-public-key @(re-frame/subscribe [:multiaccount/public-key])]
[rn/view {:style {:flex-direction :row}}
[rn/view {:style (styles/reply-content-old)}
[rn/view {:style (styles/reply-content)}
[quo/text {:weight :medium
:number-of-lines 1
:style {:line-height 18}}
(format-reply-author-old from contact-name current-public-key)]]
(format-reply-author from contact-name current-public-key)]]
[rn/view
[pressable/pressable {:on-press #(re-frame/dispatch [:chat.ui/cancel-message-reply])
:accessibility-label :cancel-message-reply}
[icons/icon :main-icons/close-circle {:container-style (styles/close-button)
:color (:icon-02 @quo.colors/theme)}]]]]))
; This component is also used for quoted pinned message as the UI is very similar
(defn reply-message [{:keys [from identicon content-type contentType parsed-text content]} in-chat-input? pin?]
(let [contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from])
current-public-key @(re-frame/subscribe [:multiaccount/public-key])
content-type (or content-type contentType)]
[rn/view {:style {:flex-direction :row :height (when-not pin? 24)}}
[rn/view {:style (styles/reply-content pin?)}
(when-not pin? [icons/icon :main-icons/connector {:color (theme-colors quo2.colors/neutral-40 quo2.colors/neutral-60)
:container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}])
[rn/view {:style (styles/quoted-message pin?)}
[photos/member-photo from identicon 16]
[quo2.text/text {:weight :semi-bold
:size :paragraph-2
:number-of-lines 1
:style {:margin-left 4}}
(format-reply-author from contact-name current-public-key)]
[quo2.text/text {:number-of-lines 1
:size :label
:weight :regular
:style (merge {:ellipsize-mode :tail
:text-transform :none
:margin-left 4
:margin-top 2}
(when (or (= constants/content-type-image content-type)
(= constants/content-type-sticker content-type)
(= constants/content-type-audio content-type))
{:color (theme-colors quo2.colors/neutral-50 quo2.colors/neutral-40)}))}
(case (or content-type contentType)
constants/content-type-image "Image"
constants/content-type-sticker "Sticker"
constants/content-type-audio "Audio"
(get-quoted-text-with-mentions (or parsed-text (:parsed-text content))))]]]
(when in-chat-input?
[quo2.button/button {:width 24
:size 24
:type :outline
:accessibility-label :reply-cancel-button
:on-press #(re-frame/dispatch [:chat.ui/cancel-message-reply])}
[icons/icon :main-icons/close {:width 16
:height 16
:color (theme-colors quo2.colors/neutral-100 quo2.colors/neutral-40)}]])]))
(defn send-image [images]
[rn/view {:style (styles/reply-container-image)}
[rn/scroll-view {:horizontal true
:style (styles/reply-content false)}
:style (styles/reply-content)}
(for [{:keys [uri]} (vals images)]
^{:key uri}
[rn/image {:source {:uri uri}
@ -148,29 +87,17 @@
;; A setTimeout of 0 is necessary to ensure the statement is enqueued and will get executed ASAP.
(js/setTimeout #(input-focus text-input-ref) 0))))
(defn reply-message-wrapper-old [reply]
(defn reply-message-wrapper [reply]
[rn/view {:style {:padding-horizontal 15
:border-top-width 1
:border-top-color (:ui-01 @quo.colors/theme)
:padding-vertical 8}}
[reply-message-old reply]])
[reply-message reply]])
(defn reply-message-wrapper [reply]
[rn/view {:style {:padding-horizontal 15
:padding-vertical 8}}
[reply-message reply true]])
(defn reply-message-auto-focus-wrapper-old [text-input-ref]
(defn reply-message-auto-focus-wrapper [text-input-ref]
(let [had-reply (atom nil)]
(fn []
(let [reply @(re-frame/subscribe [:chats/reply-message])]
(focus-input-on-reply reply had-reply text-input-ref)
(when reply
[reply-message-wrapper-old reply])))))
(defn reply-message-auto-focus-wrapper [text-input-ref _]
(let [had-reply (atom nil)]
(fn [_ reply]
(focus-input-on-reply reply had-reply text-input-ref)
(when reply
[reply-message-wrapper reply]))))
[reply-message-wrapper reply])))))

View File

@ -1,9 +1,7 @@
(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]
[quo2.foundations.colors :as quo2.colors]
[quo2.foundations.typography :as quo2.typography]))
[quo.design-system.typography :as typography]))
(defn toolbar []
{:min-height 52
@ -37,7 +35,7 @@
(when platform/ios?
{:padding-top 2})))
(defn text-input-old [contact-request]
(defn text-input [contact-request]
(merge typography/font-regular
typography/base
{:flex 1
@ -52,21 +50,6 @@
{: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})
@ -103,16 +86,11 @@
(defn reply-container []
{:flex-direction :row})
(defn reply-content-old []
(defn reply-content []
{:padding-vertical 6
:padding-horizontal 10
:flex 1})
(defn reply-content [pin?]
{:padding-horizontal (when-not pin? 10)
:flex 1
:flex-direction :row})
(defn quoted-message [pin?]
(merge {:flex-direction :row
:align-items :center
@ -152,53 +130,4 @@
:background-color (colors/get-color :ui-background)
:border-top-width 1
:border-top-color (colors/get-color :ui-01)
:z-index 3})
(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 2}
(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/neutral-100 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 2
: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 1})
:z-index 3})

View File

@ -1,40 +1,32 @@
(ns status-im.ui.screens.chat.message.message
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.chat.models.delete-message-for-me]
[status-im.chat.models.images :as images]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.chat.models.reactions :as models.reactions]
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.react-native.resources :as resources]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.fast-image :as fast-image]
[quo.design-system.colors :as colors]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.bottom-sheets.context-drawer :as message-context-drawer]
[status-im.ui.screens.chat.components.reply :as components.reply]
[status-im.ui.screens.chat.image.preview.views :as preview]
[status-im.ui.screens.chat.message.audio :as message.audio]
[status-im.ui.screens.chat.message.audio-old :as message.audio]
[status-im.chat.models.reactions :as models.reactions]
[status-im.ui.screens.chat.message.command :as message.command]
[status-im.ui.screens.chat.message.gap :as message.gap]
[status-im.ui.screens.chat.message.link-preview :as link-preview]
[status-im.ui.screens.chat.message.reactions :as reactions]
[status-im.ui.screens.chat.message.reactions-row :as reaction-row]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.screens.chat.styles.message.message :as style]
[status-im.ui.screens.chat.message.gap :as message.gap]
[status-im.ui.screens.chat.styles.message.message-old :as style]
[status-im.ui.screens.chat.utils :as chat.utils]
[status-im.ui.screens.chat.styles.photos :as photos.style]
[status-im.ui.screens.communities.icon :as communities.icon]
[status-im.utils.handlers :refer [>evt]]
[status-im.utils.config :as config]
[status-im.utils.security :as security]
[quo2.foundations.typography :as typography]
[quo2.foundations.colors :as quo2.colors])
[status-im.ui.screens.chat.message.reactions-old :as reactions]
[status-im.ui.screens.chat.image.preview.views :as preview]
[quo.core :as quo]
[status-im.utils.config :as config]
[reagent.core :as reagent]
[status-im.ui.screens.chat.components.reply :as components.reply]
[status-im.ui.screens.chat.message.link-preview :as link-preview]
[status-im.ui.screens.communities.icon :as communities.icon]
[status-im.ui.components.animation :as animation]
[status-im.chat.models.images :as images]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.ui.components.fast-image :as fast-image])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn message-timestamp-anim
@ -81,13 +73,13 @@
:height 12
:color (if pinned colors/gray colors/white)
:accessibility-label (name outgoing-status)}])
(when edited-at [react/text {:style (style/message-status-text)} edited-at-text])]))
(when edited-at [react/text {:style (style/message-status-text (and outgoing (not pinned)))} edited-at-text])]))
(defn message-timestamp
[{:keys [timestamp-str in-popover?]} show-timestamp?]
[{:keys [timestamp-str in-popover?] :as message} show-timestamp?]
(when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover
(let [anim-opacity (animation/create-value 0)]
[react/animated-view {:style (style/message-timestamp-wrapper) :opacity anim-opacity}
[react/animated-view {:style (style/message-timestamp-wrapper message) :opacity anim-opacity}
(when @show-timestamp? (message-timestamp-anim anim-opacity show-timestamp?))
[react/text
{:style (style/message-timestamp-text)
@ -95,14 +87,29 @@
timestamp-str]])))
(defview quoted-message
[_ reply pin?]
[react/view {:style (when-not pin? (style/quoted-message-container))}
[components.reply/reply-message reply false pin?]])
[_ {:keys [from parsed-text image]} outgoing current-public-key public? pinned]
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
[react/view {:style (style/quoted-message-container (and outgoing (not pinned)))}
[react/view {:style style/quoted-message-author-container}
[chat.utils/format-reply-author
from
contact-name
current-public-key
(partial style/quoted-message-author (and outgoing (not pinned)))
(and outgoing (not pinned))]]
(if (and image
;; Disabling images for public-chats
(not public?))
[fast-image/fast-image {:style {:width 56
:height 56
:background-color :black
:border-radius 4}
:source {:uri image}}]
[react/text {:style (style/quoted-message-text (and outgoing (not pinned)))
:number-of-lines 5}
(components.reply/get-quoted-text-with-mentions parsed-text)])]))
(defn system-text? [content-type]
(= content-type constants/content-type-system-text))
(defn render-inline [message-text content-type acc {:keys [type literal destination]}]
(defn render-inline [message-text outgoing pinned content-type acc {:keys [type literal destination]}]
(case type
""
(conj acc literal)
@ -114,22 +121,22 @@
literal])
"emph"
(conj acc [react/text-class (style/emph-style) literal])
(conj acc [react/text-class (style/emph-style (and outgoing (not pinned))) literal])
"strong"
(conj acc [react/text-class (style/strong-style) literal])
(conj acc [react/text-class (style/strong-style (and outgoing (not pinned))) literal])
"strong-emph"
(conj acc [quo/text (style/strong-emph-style) literal])
(conj acc [quo/text (style/strong-emph-style (and outgoing (not pinned))) literal])
"del"
(conj acc [react/text-class (style/strikethrough-style) literal])
(conj acc [react/text-class (style/strikethrough-style (and outgoing (not pinned))) literal])
"link"
(conj acc
[react/text-class
{:style
{:color colors/blue
{:color (if (and outgoing (not pinned)) colors/white-persist colors/blue)
:text-decoration-line :underline}
:on-press
#(when (and (security/safe-link? destination)
@ -139,17 +146,17 @@
destination])
"mention"
(conj acc
[react/view {:style {:background-color quo2.colors/primary-50-opa-10 :border-radius 6 :padding-horizontal 3}}
[react/text-class
{:style (merge {:color (if (system-text? content-type) colors/black quo2.colors/primary-50)}
(if (system-text? content-type) typography/font-regular typography/font-medium))
:on-press (when-not (system-text? content-type)
#(>evt [:chat.ui/show-profile literal]))}
[mention-element literal]]])
(conj acc [react/text-class
{:style {:color (cond
(= content-type constants/content-type-system-text) colors/black
(and outgoing (not pinned)) colors/mention-outgoing
:else colors/mention-incoming)}
:on-press (when-not (= content-type constants/content-type-system-text)
#(re-frame/dispatch [:chat.ui/show-profile literal]))}
[mention-element literal]])
"status-tag"
(conj acc [react/text-class
{:style {:color colors/blue
{:style {:color (if (and outgoing (not pinned)) colors/white-persist colors/blue)
:text-decoration-line :underline}
:on-press
#(re-frame/dispatch
@ -159,19 +166,19 @@
(conj acc literal)))
(defn render-block [{:keys [content content-type in-popover?]} acc
(defn render-block [{:keys [content outgoing content-type pinned in-popover?]} acc
{:keys [type ^js literal children]}]
(case type
"paragraph"
(conj acc (reduce
(fn [acc e] (render-inline (:text content) content-type acc e))
[react/text-class (style/text-style content-type in-popover?)]
(fn [acc e] (render-inline (:text content) outgoing pinned content-type acc e))
[react/text-class (style/text-style (and outgoing (not pinned)) content-type in-popover?)]
children))
"blockquote"
(conj acc [react/view (style/blockquote-style)
[react/text-class (style/blockquote-text-style)
(conj acc [react/view (style/blockquote-style (and outgoing (not pinned)))
[react/text-class (style/blockquote-text-style (and outgoing (not pinned)))
(.substring literal 0 (.-length literal))]])
"codeblock"
@ -186,10 +193,10 @@
(defn render-parsed-text [message tree]
(reduce (fn [acc e] (render-block message acc e)) [:<>] tree))
(defn render-parsed-text-with-message-status [{:keys [edited-at in-popover?] :as message} tree]
(defn render-parsed-text-with-message-status [{:keys [outgoing edited-at in-popover?] :as message} tree]
(let [elements (render-parsed-text message tree)
message-status [react/text {:style (style/message-status-placeholder)}
(str (if (not in-popover?) " " " ") (when (and (not in-popover?) edited-at) edited-at-text))]
(str (if (and outgoing (not in-popover?)) " " " ") (when (and (not in-popover?) edited-at) edited-at-text))]
last-element (peek elements)]
;; Using `nth` here as slightly faster than `first`, roughly 30%
;; It's worth considering pure js structures for this code path as
@ -201,10 +208,10 @@
(conj elements message-status))))
(defn unknown-content-type
[{:keys [content-type content] :as message}]
[{:keys [outgoing content-type content] :as message}]
[react/view (style/message-view message)
[react/text
{:style {:color colors/white-persist}}
{:style {:color (if outgoing colors/white-persist colors/black)}}
(if (seq (:text content))
(:text content)
(str "Unhandled content-type " content-type))]])
@ -229,22 +236,28 @@
(let [user-contact @(re-frame/subscribe [:multiaccount/contact])
contact-names @(re-frame/subscribe [:contacts/contact-two-names-by-identity pinned-by])]
;; We append empty spaces to the name as a workaround to make one-line and multi-line label components show correctly
(str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names)))))
(str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names)))))
(def pin-icon-width 10)
(def pin-icon-width 9)
(def pin-icon-height 15)
(defn pin-icon []
[icons/icon :main-icons/pin16 {:color (:text-04 @colors/theme)
:height pin-icon-height
:width pin-icon-width}])
(defn pinned-by-indicator [pinned-by]
[react/view {:style (style/pin-indicator)
(defn pinned-by-indicator [outgoing display-photo? pinned-by]
[react/view {:style (style/pin-indicator outgoing display-photo?)
:accessibility-label :pinned-by}
[pin-icon]
[quo/text {:size :small
[react/view {:style (style/pinned-by-text-icon-container)}
[react/view {:style (style/pin-icon-container)}
[icons/icon :main-icons/pin {:color colors/gray
:height pin-icon-height
:width pin-icon-width
:background-color :red}]]
[quo/text {:weight :regular
:size :small
:color :main
:style (style/pinned-by-text)}
(i18n/label :t/pinned-by)]]
[quo/text {:weight :medium
:size :small
:color :main
:style (style/pin-author-text)}
(pin-author-name pinned-by)]])
@ -257,11 +270,11 @@
(defview message-author-name [from opts]
(letsubs [contact-with-names [:contacts/contact-by-identity from]]
(chat.utils/format-author contact-with-names opts)))
(chat.utils/format-author-old contact-with-names opts)))
(defview message-my-name [opts]
(letsubs [contact-with-names [:multiaccount/contact]]
(chat.utils/format-author contact-with-names opts)))
(chat.utils/format-author-old contact-with-names opts)))
(defview community-content [{:keys [community-id] :as message}]
(letsubs [{:keys [name description verified] :as community} [:communities/community community-id]
@ -296,50 +309,34 @@
(defn message-content-wrapper
"Author, userpic and delivery wrapper"
[{:keys [last-in-group?
[{:keys [first-in-group? display-photo? display-username?
identicon
from in-popover? timestamp-str
deleted-for-me? pinned]
from outgoing in-popover?]
:as message} content {:keys [modal close-modal]}]
(let [response-to (:response-to (:content message))]
[react/view {:style (style/message-wrapper message)
:pointer-events :box-none
:accessibility-label :chat-item}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message)])
[react/view {:style (style/message-body)
:pointer-events :box-none}
[react/view (style/message-author-userpic)
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[react/view {:style (style/message-wrapper message)
:pointer-events :box-none
:accessibility-label :chat-item}
[react/view {:style (style/message-body message)
:pointer-events :box-none}
(when display-photo?
[react/view (style/message-author-userpic outgoing)
(when first-in-group?
[react/touchable-highlight {:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[photos/member-photo from identicon]])]
[react/view {:style (style/message-author-wrapper)}
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[react/view {:style {:flex-direction :row :align-items :center}}
[react/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]]
[react/text
{:style (merge
{:padding-left 5
:margin-top 2}
(style/message-timestamp-text))
:accessibility-label :message-timestamp}
timestamp-str]])
;; MESSAGE CONTENT
;; TODO(yqrashawn): wait for system message component to display deleted for me UI
(if deleted-for-me?
[react/view {:style {:border-width 2
:border-color :red}}
content]
content)
[link-preview/link-preview-wrapper (:links (:content message)) false false]]]
; delivery status
[react/view (style/delivery-status)
[message-delivery-status message]]]))
[photos/member-photo from identicon]])])
[react/view {:style (style/message-author-wrapper outgoing display-photo? in-popover?)}
(when display-username?
[react/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]])
;;MESSAGE CONTENT
content
[link-preview/link-preview-wrapper (:links (:content message)) outgoing false]]]
; delivery status
[react/view (style/delivery-status outgoing)
[message-delivery-status message]]])
(def image-max-width 260)
(def image-max-height 192)
@ -356,13 +353,13 @@
(swap! dimensions assoc :loaded true)))))
(defn message-content-image
[{:keys [content]} _]
[{:keys [content outgoing in-popover?] :as message}
{:keys [on-long-press]}]
(let [dimensions (reagent/atom {:width image-max-width :height image-max-height :loaded false})
visible (reagent/atom false)
uri (:image content)]
(fn [{:keys [in-popover?] :as message}
{:keys [on-long-press]}]
(let [style-opts {:outgoing false
(fn []
(let [style-opts {:outgoing outgoing
:opacity (if (:loaded @dimensions) 1 0)
:width (:width @dimensions)
:height (:height @dimensions)}]
@ -374,7 +371,7 @@
[react/touchable-highlight {:on-press (fn []
(reset! visible true)
(react/dismiss-keyboard!))
:on-long-press @on-long-press
:on-long-press on-long-press
:disabled in-popover?}
[react/view {:style (style/image-message style-opts)
:accessibility-label :image-message}
@ -408,6 +405,9 @@
[react/view (style/message-view-content)
[render-parsed-text message (:parsed-text content)]]]]])
(def message-height-px 200)
(def max-message-height-px 150)
(defn pin-message [{:keys [chat-id pinned] :as message}]
(let [pinned-messages @(re-frame/subscribe [:chats/pinned chat-id])]
(if (and (not pinned) (> (count pinned-messages) 2))
@ -422,154 +422,135 @@
(on-long-press
(concat
(when (and outgoing edit-enabled)
[{:type :main
:on-press #(re-frame/dispatch [:chat.ui/edit-message message])
:label (i18n/label :t/edit-message)
:icon :main-icons/edit-context20
[{:on-press #(re-frame/dispatch [:chat.ui/edit-message message])
:label (i18n/label :t/edit)
:id :edit}])
(when show-input?
[{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:label (i18n/label :t/message-reply)
:icon :main-icons/reply-context20
:id :reply}])
[{:type :main
:on-press #(react/copy-to-clipboard
[{:on-press #(react/copy-to-clipboard
(components.reply/get-quoted-text-with-mentions
(get content :parsed-text)))
:label (i18n/label :t/copy-text)
:icon :main-icons/copy-context20
:label (i18n/label :t/sharing-copy-to-clipboard)
:id :copy}]
(when message-pin-enabled
[{:type :main
:on-press #(pin-message message)
:label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat))
:icon :main-icons/pin-context20
[{:on-press #(pin-message message)
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))
:id (if pinned :unpin :pin)}])
[{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :main-icons/delete-context20
:id :delete-for-me}]
(when (and outgoing config/delete-message-enabled?)
[{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete-for-everyone)
:icon :main-icons/delete-context20
:id :delete-for-all}]))))
[{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete)
:id :delete}]))))
(defn collapsible-text-message [_ _]
(defn collapsible-text-message [{:keys [mentioned]} _]
(let [collapsed? (reagent/atom false)
collapsible? (reagent/atom false)
show-timestamp? (reagent/atom false)]
(fn [{:keys [content in-popover?] :as message} on-long-press modal ref]
(let [on-long-press (fn []
(if @collapsed?
(do (reset! collapsed? false)
(js/setTimeout #(on-long-press-fn on-long-press message content) 200))
(on-long-press-fn on-long-press message content)))]
(reset! ref on-long-press)
(fn [{:keys [content outgoing current-public-key public? pinned in-popover?] :as message} on-long-press modal]
(let [max-height (when-not (or outgoing modal)
(if @collapsible?
(if @collapsed? message-height-px nil)
message-height-px))]
[react/touchable-highlight
(when-not modal
{:on-press (fn [_]
(react/dismiss-keyboard!)
(reset! show-timestamp? true))
:delay-long-press 100
:on-long-press on-long-press
:on-long-press (fn []
(if @collapsed?
(do (reset! collapsed? false)
(js/setTimeout #(on-long-press-fn on-long-press message content) 200))
(on-long-press-fn on-long-press message content)))
:disabled in-popover?})
[react/view style/message-view-wrapper
[react/view (style/message-view-wrapper outgoing)
[message-timestamp message show-timestamp?]
[react/view {:style (style/message-view message)}
[react/view {:style (style/message-view-content)}
[react/view
[render-parsed-text-with-message-status message (:parsed-text content)]]]]]]))))
[react/view {:style (style/message-view-content)
:max-height max-height}
(let [response-to (:response-to content)]
[react/view {:on-layout
#(when (and (> (.-nativeEvent.layout.height ^js %) max-message-height-px)
(not @collapsible?)
(not outgoing)
(not modal))
(reset! collapsed? true)
(reset! collapsible? true))}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned])
[render-parsed-text-with-message-status message (:parsed-text content)]])
(when-not @collapsible? [message-status message])
(when (and @collapsible? (not modal))
(if @collapsed?
(let [color (if pinned colors/pin-background (if mentioned colors/mentioned-background colors/blue-light))]
[react/touchable-highlight
{:on-press #(swap! collapsed? not)
:style {:position :absolute :bottom 0 :left 0 :right 0 :height 72}}
[react/linear-gradient {:colors [(str color "00") color]
:start {:x 0 :y 0} :end {:x 0 :y 0.9}}
[react/view {:height 72 :align-self :center :justify-content :flex-end
:padding-bottom 10}
[react/view (style/collapse-button)
[icons/icon :main-icons/dropdown
{:color colors/white}]]]]])
[react/touchable-highlight {:on-press #(swap! collapsed? not)
:style {:align-self :center :margin 5}}
[react/view (style/collapse-button)
[icons/icon :main-icons/dropdown-up
{:color colors/white}]]]))]]]]))))
(defmethod ->message constants/content-type-text
[message {:keys [on-long-press modal ref] :as reaction-picker}]
[message {:keys [on-long-press modal] :as reaction-picker}]
[message-content-wrapper message
[collapsible-text-message message on-long-press modal ref]
[collapsible-text-message message on-long-press modal]
reaction-picker])
(defmethod ->message constants/content-type-pin [{:keys [from in-popover? timestamp-str] :as message} {:keys [modal close-modal]}]
(let [response-to (:response-to (:content message))]
[react/view {:style (merge {:flex-direction :row :margin-vertical 8} (style/message-wrapper message))}
[react/view {:style {:width photos.style/default-size
:height photos.style/default-size
:margin-horizontal 8
:border-radius photos.style/default-size
:justify-content :center
:align-items :center
:background-color quo2.colors/primary-50-opa-10}}
[pin-icon]]
[react/view
[react/view {:style {:flex-direction :row :align-items :center}}
[react/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]]
[react/text {:style {:font-size 13}} (str " " (i18n/label :pinned-a-message))]
[react/text
{:style (merge
{:padding-left 5
:margin-top 2}
(style/message-timestamp-text))
:accessibility-label :message-timestamp}
timestamp-str]]
[quoted-message response-to (:quoted-message message) true]]]))
(defmethod ->message constants/content-type-community
[message]
[community-content message])
(defmethod ->message constants/content-type-status
[{:keys [content content-type] :as message}]
[{:keys [content content-type pinned] :as message}]
[message-content-wrapper message
[react/view style/status-container
[react/text {:style (style/status-text)}
(reduce
(fn [acc e] (render-inline (:text content) content-type acc e))
(fn [acc e] (render-inline (:text content) false pinned content-type acc e))
[react/text-class {:style (style/status-text)}]
(-> content :parsed-text peek :children))]]])
(defmethod ->message constants/content-type-emoji []
(let [show-timestamp? (reagent/atom false)]
(fn [{:keys [content pinned in-popover? message-pin-enabled] :as message}
{:keys [on-long-press modal ref]
(fn [{:keys [content current-public-key outgoing public? pinned in-popover? message-pin-enabled] :as message}
{:keys [on-long-press modal]
:as reaction-picker}]
(let [on-long-press (fn []
(on-long-press
(concat
[{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:icon :main-icons/reply-context20
:label (i18n/label :t/message-reply)}
{:type :main
:on-press #(react/copy-to-clipboard (get content :text))
:id :copy
:icon :main-icons/copy-context20
:label (i18n/label :t/copy-text)}]
(when message-pin-enabled [{:type :main
:on-press #(pin-message message)
:id :pin
:icon :main-icons/pin-context20
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))]
(reset! ref on-long-press)
(let [response-to (:response-to content)]
[message-content-wrapper message
[react/touchable-highlight (when-not modal
{:disabled in-popover?
:on-press (fn []
(react/dismiss-keyboard!)
(reset! show-timestamp? true))
{:disabled in-popover?
:on-press (fn []
(react/dismiss-keyboard!)
(reset! show-timestamp? true))
:delay-long-press 100
:on-long-press on-long-press})
[react/view style/message-view-wrapper
:on-long-press (fn []
(on-long-press
(concat
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:label (i18n/label :t/message-reply)}
{:on-press #(react/copy-to-clipboard (get content :text))
:id :copy
:label (i18n/label :t/sharing-copy-to-clipboard)}]
(when message-pin-enabled [{:on-press #(pin-message message)
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))})
[react/view (style/message-view-wrapper outgoing)
[message-timestamp message show-timestamp?]
[react/view (style/message-view message)
[react/view {:style (style/message-view-content)}
[react/view {:style (style/style-message-text)}
[react/view {:style (style/style-message-text outgoing)}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned])
[react/text {:style (style/emoji-message message)}
(:text content)]]
[message-status message]]]]]
@ -578,18 +559,9 @@
(defmethod ->message constants/content-type-sticker
[{:keys [content from outgoing in-popover?]
:as message}
{:keys [on-long-press modal ref]
{:keys [on-long-press modal]
:as reaction-picker}]
(let [pack (get-in content [:sticker :pack])
on-long-press (fn []
(on-long-press
(when-not outgoing
[{:type :main
:icon :main-icons/stickers-context20
:on-press #(when pack
(re-frame/dispatch [:chat.ui/show-profile from]))
:label (i18n/label :t/see-sticker-set)}])))]
(reset! ref on-long-press)
(let [pack (get-in content [:sticker :pack])]
[message-content-wrapper message
[react/touchable-highlight (when-not modal
{:disabled in-popover?
@ -598,97 +570,66 @@
(when pack
(re-frame/dispatch [:stickers/open-sticker-pack (str pack)]))
(react/dismiss-keyboard!))
:delay-long-press 100
:on-long-press on-long-press})
:delay-long-press 100
:on-long-press (fn []
(on-long-press
(when-not outgoing
[{:on-press #(when pack
(re-frame/dispatch [:chat.ui/show-profile from]))
:label (i18n/label :t/view-details)}])))})
[fast-image/fast-image {:style {:margin 10 :width 140 :height 140}
:source {:uri (str (-> content :sticker :url) "&download=true")}}]]
reaction-picker]))
(defmethod ->message constants/content-type-image
[{:keys [content in-popover? outgoing] :as message}
{:keys [on-long-press modal ref]
{:keys [on-long-press modal]
:as reaction-picker}]
(let [on-long-press (fn []
(on-long-press
(concat [{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:icon :main-icons/reply-context20
:label (i18n/label :t/message-reply)}
{:type :main
:on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)])
:id :save
:icon :main-icons/save-context20
:label (i18n/label :t/save-image-library)}
{:type :main
:on-press #(images/download-image-http
(get-in message [:content :image]) preview/share)
:id :share
:icon :main-icons/share-context20
:label (i18n/label :t/share-image)}]
[{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :main-icons/delete-context20
:id :delete-for-me}]
(when (and outgoing config/delete-message-enabled?)
[{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete-for-everyone)
:icon :main-icons/delete-context20
:id :delete}]))))]
(reset! ref on-long-press)
[message-content-wrapper message
[message-content-image message
{:modal modal
:disabled in-popover?
:delay-long-press 100
:on-long-press ref}]
reaction-picker]))
[message-content-wrapper message
[message-content-image message
{:modal modal
:disabled in-popover?
:delay-long-press 100
:on-long-press (fn []
(on-long-press
(concat [{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:label (i18n/label :t/message-reply)}
{:on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)])
:id :save
:label (i18n/label :t/save)}
{:on-press #(images/download-image-http
(get-in message [:content :image]) preview/share)
:id :share
:label (i18n/label :t/share)}]
(when (and outgoing config/delete-message-enabled?)
[{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete)
:id :delete}]))))}]
reaction-picker])
(defmethod ->message constants/content-type-audio []
(let [show-timestamp? (reagent/atom false)]
(fn [{:keys [outgoing pinned] :as message}
{:keys [on-long-press modal ref]
(fn [{:keys [outgoing] :as message}
{:keys [on-long-press modal]
:as reaction-picker}]
(let [on-long-press (fn [] (on-long-press [{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:label (i18n/label :t/message-reply)
:icon :main-icons/reply-context20
:id :reply}
{:type :main
:on-press #(pin-message message)
:label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat))
:icon :main-icons/pin-context20
:id (if pinned :unpin :pin)}
{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :main-icons/delete-context20
:id :delete-for-me}
(when (and outgoing config/delete-message-enabled?)
{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete-for-everyone)
:icon :main-icons/delete-context20
:id :delete})]))]
(reset! ref on-long-press)
[message-content-wrapper message
[react/touchable-highlight
(when-not modal
{:on-long-press on-long-press
:on-press (fn []
(reset! show-timestamp? true))})
[react/view style/message-view-wrapper
[message-timestamp message show-timestamp?]
[react/view {:style (style/message-view message) :accessibility-label :audio-message}
[react/view {:style (style/message-view-content)}
[message.audio/message-content message] [message-status message]]]]]
reaction-picker]))))
[message-content-wrapper message
[react/touchable-highlight
(when-not modal
{:on-long-press
(fn [] (on-long-press (if (and outgoing config/delete-message-enabled?)
[{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete)
:id :delete}]
[])))
:on-press (fn []
(reset! show-timestamp? true))})
[react/view (style/message-view-wrapper (:outgoing message))
[message-timestamp message show-timestamp?]
[react/view {:style (style/message-view message) :accessibility-label :audio-message}
[react/view {:style (style/message-view-content)}
[message.audio/message-content message] [message-status message]]]]]
reaction-picker])))
(defn contact-request-status-pending []
[react/view {:style {:flex-direction :row}}
@ -718,8 +659,8 @@
constants/contact-request-message-state-declined [contact-request-status-declined])])
(defmethod ->message constants/content-type-contact-request
[message _]
[react/view {:style (style/content-type-contact-request)}
[{:keys [outgoing] :as message} _]
[react/view {:style (style/content-type-contact-request outgoing)}
[react/image {:source (resources/get-image :hand-wave)
:style {:width 112
:height 97}}]
@ -737,39 +678,25 @@
[message-content-wrapper message
[unknown-content-type message]])
(defn chat-message [{:keys [pinned pinned-by mentioned] :as message}]
(let [reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)])
own-reactions (reduce (fn [acc {:keys [emoji-id own]}]
(if own (conj acc emoji-id) acc))
[] reactions)
send-emoji (fn [{:keys [emoji-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction
{:message-id (:message-id message)
:emoji-id emoji-id}]))
retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction-retraction
{:message-id (:message-id message)
:emoji-id emoji-id
:emoji-reaction-id emoji-reaction-id}]))
on-emoji-press (fn [emoji-id]
(let [active ((set own-reactions) emoji-id)]
(if active
(retract-emoji {:emoji-id emoji-id
:emoji-reaction-id (reactions/extract-id reactions emoji-id)})
(send-emoji {:emoji-id emoji-id}))))
on-open-drawer (fn [actions]
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (message-context-drawer/message-options
actions
(into #{} (js->clj own-reactions))
#(on-emoji-press %))}]))
on-long-press (atom nil)]
[react/view {:style (merge (when (or mentioned pinned) {:background-color quo2.colors/primary-50-opa-5 :border-radius 16 :margin-bottom 4}) {:margin-horizontal 8})}
(when pinned
[react/view {:style (style/pin-indicator-container)}
[pinned-by-indicator pinned-by]])
[->message message {:ref on-long-press
:modal false
:on-long-press on-open-drawer}]
[reaction-row/message-reactions message reactions nil on-emoji-press on-long-press] ;; TODO: pass on-open-drawer function
]))
(defn chat-message [{:keys [outgoing display-photo? pinned pinned-by] :as message} space-keeper]
[:<>
[reactions/with-reaction-picker
{:message message
:reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)])
:picker-on-open (fn []
(space-keeper true))
:picker-on-close (fn []
(space-keeper false))
:send-emoji (fn [{:keys [emoji-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction
{:message-id (:message-id message)
:emoji-id emoji-id}]))
:retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction-retraction
{:message-id (:message-id message)
:emoji-id emoji-id
:emoji-reaction-id emoji-reaction-id}]))
:render ->message}]
(when pinned
[react/view {:style (style/pin-indicator-container outgoing)}
[pinned-by-indicator outgoing display-photo? pinned-by]])])

View File

@ -1,702 +0,0 @@
(ns status-im.ui.screens.chat.message.message-old
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.react-native.resources :as resources]
[quo.design-system.colors :as colors]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.message.audio-old :as message.audio]
[status-im.chat.models.reactions :as models.reactions]
[status-im.ui.screens.chat.message.command :as message.command]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.screens.chat.message.gap :as message.gap]
[status-im.ui.screens.chat.styles.message.message-old :as style]
[status-im.ui.screens.chat.utils :as chat.utils]
[status-im.utils.security :as security]
[status-im.ui.screens.chat.message.reactions-old :as reactions]
[status-im.ui.screens.chat.image.preview.views :as preview]
[quo.core :as quo]
[status-im.utils.config :as config]
[reagent.core :as reagent]
[status-im.ui.screens.chat.components.reply :as components.reply]
[status-im.ui.screens.chat.message.link-preview :as link-preview]
[status-im.ui.screens.communities.icon :as communities.icon]
[status-im.ui.components.animation :as animation]
[status-im.chat.models.images :as images]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.ui.components.fast-image :as fast-image])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn message-timestamp-anim
[anim-opacity show-timestamp?]
(animation/start
(animation/anim-sequence
[(animation/timing
anim-opacity
{:toValue 1
:duration 100
:easing (.-ease ^js animation/easing)
:useNativeDriver true})
(animation/timing
anim-opacity
{:toValue 0
:delay 2000
:duration 100
:easing (.-ease ^js animation/easing)
:useNativeDriver true})]) #(reset! show-timestamp? false)))
(defview mention-element [from]
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
contact-name))
(def edited-at-text (str " ⌫ " (i18n/label :t/edited)))
(defn message-status [{:keys [outgoing content outgoing-status pinned edited-at in-popover?]}]
(when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover
[react/view
{:align-self :flex-end
:position :absolute
:bottom 9 ; 6 Bubble bottom, 3 message baseline
(if (:rtl? content) :left :right) 0
:flex-direction :row
:align-items :flex-end}
(when outgoing
[icons/icon (case outgoing-status
:sending :tiny-icons/tiny-pending
:sent :tiny-icons/tiny-sent
:not-sent :tiny-icons/tiny-warning
:delivered :tiny-icons/tiny-delivered
:tiny-icons/tiny-pending)
{:width 16
:height 12
:color (if pinned colors/gray colors/white)
:accessibility-label (name outgoing-status)}])
(when edited-at [react/text {:style (style/message-status-text (and outgoing (not pinned)))} edited-at-text])]))
(defn message-timestamp
[{:keys [timestamp-str in-popover?] :as message} show-timestamp?]
(when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover
(let [anim-opacity (animation/create-value 0)]
[react/animated-view {:style (style/message-timestamp-wrapper message) :opacity anim-opacity}
(when @show-timestamp? (message-timestamp-anim anim-opacity show-timestamp?))
[react/text
{:style (style/message-timestamp-text)
:accessibility-label :message-timestamp}
timestamp-str]])))
(defview quoted-message
[_ {:keys [from parsed-text image]} outgoing current-public-key public? pinned]
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
[react/view {:style (style/quoted-message-container (and outgoing (not pinned)))}
[react/view {:style style/quoted-message-author-container}
[chat.utils/format-reply-author
from
contact-name
current-public-key
(partial style/quoted-message-author (and outgoing (not pinned)))
(and outgoing (not pinned))]]
(if (and image
;; Disabling images for public-chats
(not public?))
[fast-image/fast-image {:style {:width 56
:height 56
:background-color :black
:border-radius 4}
:source {:uri image}}]
[react/text {:style (style/quoted-message-text (and outgoing (not pinned)))
:number-of-lines 5}
(components.reply/get-quoted-text-with-mentions parsed-text)])]))
(defn render-inline [message-text outgoing pinned content-type acc {:keys [type literal destination]}]
(case type
""
(conj acc literal)
"code"
(conj acc [quo/text {:max-font-size-multiplier react/max-font-size-multiplier
:style (style/inline-code-style)
:monospace true}
literal])
"emph"
(conj acc [react/text-class (style/emph-style (and outgoing (not pinned))) literal])
"strong"
(conj acc [react/text-class (style/strong-style (and outgoing (not pinned))) literal])
"strong-emph"
(conj acc [quo/text (style/strong-emph-style (and outgoing (not pinned))) literal])
"del"
(conj acc [react/text-class (style/strikethrough-style (and outgoing (not pinned))) literal])
"link"
(conj acc
[react/text-class
{:style
{:color (if (and outgoing (not pinned)) colors/white-persist colors/blue)
:text-decoration-line :underline}
:on-press
#(when (and (security/safe-link? destination)
(security/safe-link-text? message-text))
(re-frame/dispatch
[:browser.ui/message-link-pressed destination]))}
destination])
"mention"
(conj acc [react/text-class
{:style {:color (cond
(= content-type constants/content-type-system-text) colors/black
(and outgoing (not pinned)) colors/mention-outgoing
:else colors/mention-incoming)}
:on-press (when-not (= content-type constants/content-type-system-text)
#(re-frame/dispatch [:chat.ui/show-profile literal]))}
[mention-element literal]])
"status-tag"
(conj acc [react/text-class
{:style {:color (if (and outgoing (not pinned)) colors/white-persist colors/blue)
:text-decoration-line :underline}
:on-press
#(re-frame/dispatch
[:chat.ui/start-public-chat literal])}
"#"
literal])
(conj acc literal)))
(defn render-block [{:keys [content outgoing content-type pinned in-popover?]} acc
{:keys [type ^js literal children]}]
(case type
"paragraph"
(conj acc (reduce
(fn [acc e] (render-inline (:text content) outgoing pinned content-type acc e))
[react/text-class (style/text-style (and outgoing (not pinned)) content-type in-popover?)]
children))
"blockquote"
(conj acc [react/view (style/blockquote-style (and outgoing (not pinned)))
[react/text-class (style/blockquote-text-style (and outgoing (not pinned)))
(.substring literal 0 (.-length literal))]])
"codeblock"
(conj acc [react/view {:style style/codeblock-style}
[quo/text {:max-font-size-multiplier react/max-font-size-multiplier
:style style/codeblock-text-style
:monospace true}
(.substring literal 0 (dec (.-length literal)))]])
acc))
(defn render-parsed-text [message tree]
(reduce (fn [acc e] (render-block message acc e)) [:<>] tree))
(defn render-parsed-text-with-message-status [{:keys [outgoing edited-at in-popover?] :as message} tree]
(let [elements (render-parsed-text message tree)
message-status [react/text {:style (style/message-status-placeholder)}
(str (if (and outgoing (not in-popover?)) " " " ") (when (and (not in-popover?) edited-at) edited-at-text))]
last-element (peek elements)]
;; Using `nth` here as slightly faster than `first`, roughly 30%
;; It's worth considering pure js structures for this code path as
;; it's perfomance critical
(if (= react/text-class (nth last-element 0))
;; Append message status to last text
(conj (pop elements) (conj last-element message-status))
;; Append message status to new block
(conj elements message-status))))
(defn unknown-content-type
[{:keys [outgoing content-type content] :as message}]
[react/view (style/message-view message)
[react/text
{:style {:color (if outgoing colors/white-persist colors/black)}}
(if (seq (:text content))
(:text content)
(str "Unhandled content-type " content-type))]])
(defn message-not-sent-text
[chat-id message-id]
[react/touchable-highlight
{:on-press
(fn []
(re-frame/dispatch
[:bottom-sheet/show-sheet
{:content (sheets/options chat-id message-id)
:content-height 200}])
(react/dismiss-keyboard!))}
[react/view style/not-sent-view
[react/text {:style style/not-sent-text}
(i18n/label :t/status-not-sent-tap)]
[react/view style/not-sent-icon
[icons/icon :main-icons/warning {:color colors/red}]]]])
(defn pin-author-name [pinned-by]
(let [user-contact @(re-frame/subscribe [:multiaccount/contact])
contact-names @(re-frame/subscribe [:contacts/contact-two-names-by-identity pinned-by])]
;; We append empty spaces to the name as a workaround to make one-line and multi-line label components show correctly
(str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names)))))
(def pin-icon-width 9)
(def pin-icon-height 15)
(defn pinned-by-indicator [outgoing display-photo? pinned-by]
[react/view {:style (style/pin-indicator outgoing display-photo?)
:accessibility-label :pinned-by}
[react/view {:style (style/pinned-by-text-icon-container)}
[react/view {:style (style/pin-icon-container)}
[icons/icon :main-icons/pin {:color colors/gray
:height pin-icon-height
:width pin-icon-width
:background-color :red}]]
[quo/text {:weight :regular
:size :small
:color :main
:style (style/pinned-by-text)}
(i18n/label :t/pinned-by)]]
[quo/text {:weight :medium
:size :small
:color :main
:style (style/pin-author-text)}
(pin-author-name pinned-by)]])
(defn message-delivery-status
[{:keys [chat-id message-id outgoing-status message-type]}]
(when (and (not= constants/message-type-private-group-system-message message-type)
(= outgoing-status :not-sent))
[message-not-sent-text chat-id message-id]))
(defview message-author-name [from opts]
(letsubs [contact-with-names [:contacts/contact-by-identity from]]
(chat.utils/format-author-old contact-with-names opts)))
(defview message-my-name [opts]
(letsubs [contact-with-names [:multiaccount/contact]]
(chat.utils/format-author-old contact-with-names opts)))
(defview community-content [{:keys [community-id] :as message}]
(letsubs [{:keys [name description verified] :as community} [:communities/community community-id]
communities-enabled? [:communities/enabled?]]
(when (and communities-enabled? community)
[react/view {:style (assoc (style/message-wrapper message)
:margin-vertical 10
:margin-left 8
:width 271)}
(when verified
[react/view (style/community-verified)
[react/text {:style {:font-size 13
:color colors/blue}} (i18n/label :t/communities-verified)]])
[react/view (style/community-message verified)
[react/view {:width 62
:padding-left 14}
(if (= community-id constants/status-community-id)
[react/image {:source (resources/get-image :status-logo)
:style {:width 40
:height 40}}]
[communities.icon/community-icon community])]
[react/view {:padding-right 14 :flex 1}
[react/text {:style {:font-weight "700" :font-size 17}}
name]
[react/text description]]]
[react/view (style/community-view-button)
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to
:community
{:community-id (:id community)}])}
[react/text {:style {:text-align :center
:color colors/blue}} (i18n/label :t/view)]]]])))
(defn message-content-wrapper
"Author, userpic and delivery wrapper"
[{:keys [first-in-group? display-photo? display-username?
identicon
from outgoing in-popover?]
:as message} content {:keys [modal close-modal]}]
[react/view {:style (style/message-wrapper message)
:pointer-events :box-none
:accessibility-label :chat-item}
[react/view {:style (style/message-body message)
:pointer-events :box-none}
(when display-photo?
[react/view (style/message-author-userpic outgoing)
(when first-in-group?
[react/touchable-highlight {:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[photos/member-photo from identicon]])])
[react/view {:style (style/message-author-wrapper outgoing display-photo? in-popover?)}
(when display-username?
[react/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]])
;;MESSAGE CONTENT
content
[link-preview/link-preview-wrapper (:links (:content message)) outgoing false]]]
; delivery status
[react/view (style/delivery-status outgoing)
[message-delivery-status message]]])
(def image-max-width 260)
(def image-max-height 192)
(defn image-set-size [dimensions]
(fn [evt]
(let [width (.-width (.-nativeEvent evt))
height (.-height (.-nativeEvent evt))]
(if (< width height)
;; if width less than the height we reduce width proportionally to height
(let [k (/ height image-max-height)]
(when (not= (/ width k) (first @dimensions))
(reset! dimensions {:width (/ width k) :height image-max-height :loaded true})))
(swap! dimensions assoc :loaded true)))))
(defn message-content-image
[{:keys [content outgoing in-popover?] :as message}
{:keys [on-long-press]}]
(let [dimensions (reagent/atom {:width image-max-width :height image-max-height :loaded false})
visible (reagent/atom false)
uri (:image content)]
(fn []
(let [style-opts {:outgoing outgoing
:opacity (if (:loaded @dimensions) 1 0)
:width (:width @dimensions)
:height (:height @dimensions)}]
[:<>
[preview/preview-image {:message message
:visible @visible
:on-close #(do (reset! visible false)
(reagent/flush))}]
[react/touchable-highlight {:on-press (fn []
(reset! visible true)
(react/dismiss-keyboard!))
:on-long-press on-long-press
:disabled in-popover?}
[react/view {:style (style/image-message style-opts)
:accessibility-label :image-message}
(when (or (:error @dimensions) (not (:loaded @dimensions)))
[react/view
(merge (dissoc style-opts :opacity)
{:flex 1 :align-items :center :justify-content :center :position :absolute})
(if (:error @dimensions)
[icons/icon :main-icons/cancel]
[react/activity-indicator {:animating true}])])
[fast-image/fast-image {:style (dissoc style-opts :outgoing)
:on-load (image-set-size dimensions)
:on-error #(swap! dimensions assoc :error true)
:source {:uri uri}}]
[react/view {:style (style/image-message-border style-opts)}]]]]))))
(defmulti ->message :content-type)
(defmethod ->message constants/content-type-command
[message]
[message.command/command-content message-content-wrapper message])
(defmethod ->message constants/content-type-gap
[message]
[message.gap/gap message])
(defmethod ->message constants/content-type-system-text [{:keys [content] :as message}]
[react/view {:accessibility-label :chat-item}
[react/view (style/system-message-body message)
[react/view (style/message-view message)
[react/view (style/message-view-content)
[render-parsed-text message (:parsed-text content)]]]]])
(def message-height-px 200)
(def max-message-height-px 150)
(defn pin-message [{:keys [chat-id pinned] :as message}]
(let [pinned-messages @(re-frame/subscribe [:chats/pinned chat-id])]
(if (and (not pinned) (> (count pinned-messages) 2))
(do
(js/setTimeout (fn [] (re-frame/dispatch [:dismiss-keyboard])) 500)
(re-frame/dispatch [:show-popover {:view :pin-limit
:message message
:prevent-closing? true}]))
(re-frame/dispatch [::models.pin-message/send-pin-message (assoc message :pinned (not pinned))]))))
(defn on-long-press-fn [on-long-press {:keys [pinned message-pin-enabled outgoing edit-enabled show-input?] :as message} content]
(on-long-press
(concat
(when (and outgoing edit-enabled)
[{:on-press #(re-frame/dispatch [:chat.ui/edit-message message])
:label (i18n/label :t/edit)
:id :edit}])
(when show-input?
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:label (i18n/label :t/message-reply)
:id :reply}])
[{:on-press #(react/copy-to-clipboard
(components.reply/get-quoted-text-with-mentions
(get content :parsed-text)))
:label (i18n/label :t/sharing-copy-to-clipboard)
:id :copy}]
(when message-pin-enabled
[{:on-press #(pin-message message)
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))
:id (if pinned :unpin :pin)}])
(when (and outgoing config/delete-message-enabled?)
[{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete)
:id :delete}]))))
(defn collapsible-text-message [{:keys [mentioned]} _]
(let [collapsed? (reagent/atom false)
collapsible? (reagent/atom false)
show-timestamp? (reagent/atom false)]
(fn [{:keys [content outgoing current-public-key public? pinned in-popover?] :as message} on-long-press modal]
(let [max-height (when-not (or outgoing modal)
(if @collapsible?
(if @collapsed? message-height-px nil)
message-height-px))]
[react/touchable-highlight
(when-not modal
{:on-press (fn [_]
(react/dismiss-keyboard!)
(reset! show-timestamp? true))
:delay-long-press 100
:on-long-press (fn []
(if @collapsed?
(do (reset! collapsed? false)
(js/setTimeout #(on-long-press-fn on-long-press message content) 200))
(on-long-press-fn on-long-press message content)))
:disabled in-popover?})
[react/view (style/message-view-wrapper outgoing)
[message-timestamp message show-timestamp?]
[react/view {:style (style/message-view message)}
[react/view {:style (style/message-view-content)
:max-height max-height}
(let [response-to (:response-to content)]
[react/view {:on-layout
#(when (and (> (.-nativeEvent.layout.height ^js %) max-message-height-px)
(not @collapsible?)
(not outgoing)
(not modal))
(reset! collapsed? true)
(reset! collapsible? true))}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned])
[render-parsed-text-with-message-status message (:parsed-text content)]])
(when-not @collapsible? [message-status message])
(when (and @collapsible? (not modal))
(if @collapsed?
(let [color (if pinned colors/pin-background (if mentioned colors/mentioned-background colors/blue-light))]
[react/touchable-highlight
{:on-press #(swap! collapsed? not)
:style {:position :absolute :bottom 0 :left 0 :right 0 :height 72}}
[react/linear-gradient {:colors [(str color "00") color]
:start {:x 0 :y 0} :end {:x 0 :y 0.9}}
[react/view {:height 72 :align-self :center :justify-content :flex-end
:padding-bottom 10}
[react/view (style/collapse-button)
[icons/icon :main-icons/dropdown
{:color colors/white}]]]]])
[react/touchable-highlight {:on-press #(swap! collapsed? not)
:style {:align-self :center :margin 5}}
[react/view (style/collapse-button)
[icons/icon :main-icons/dropdown-up
{:color colors/white}]]]))]]]]))))
(defmethod ->message constants/content-type-text
[message {:keys [on-long-press modal] :as reaction-picker}]
[message-content-wrapper message
[collapsible-text-message message on-long-press modal]
reaction-picker])
(defmethod ->message constants/content-type-community
[message]
[community-content message])
(defmethod ->message constants/content-type-status
[{:keys [content content-type pinned] :as message}]
[message-content-wrapper message
[react/view style/status-container
[react/text {:style (style/status-text)}
(reduce
(fn [acc e] (render-inline (:text content) false pinned content-type acc e))
[react/text-class {:style (style/status-text)}]
(-> content :parsed-text peek :children))]]])
(defmethod ->message constants/content-type-emoji []
(let [show-timestamp? (reagent/atom false)]
(fn [{:keys [content current-public-key outgoing public? pinned in-popover? message-pin-enabled] :as message}
{:keys [on-long-press modal]
:as reaction-picker}]
(let [response-to (:response-to content)]
[message-content-wrapper message
[react/touchable-highlight (when-not modal
{:disabled in-popover?
:on-press (fn []
(react/dismiss-keyboard!)
(reset! show-timestamp? true))
:delay-long-press 100
:on-long-press (fn []
(on-long-press
(concat
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:label (i18n/label :t/message-reply)}
{:on-press #(react/copy-to-clipboard (get content :text))
:id :copy
:label (i18n/label :t/sharing-copy-to-clipboard)}]
(when message-pin-enabled [{:on-press #(pin-message message)
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))})
[react/view (style/message-view-wrapper outgoing)
[message-timestamp message show-timestamp?]
[react/view (style/message-view message)
[react/view {:style (style/message-view-content)}
[react/view {:style (style/style-message-text outgoing)}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message) outgoing current-public-key public? pinned])
[react/text {:style (style/emoji-message message)}
(:text content)]]
[message-status message]]]]]
reaction-picker]))))
(defmethod ->message constants/content-type-sticker
[{:keys [content from outgoing in-popover?]
:as message}
{:keys [on-long-press modal]
:as reaction-picker}]
(let [pack (get-in content [:sticker :pack])]
[message-content-wrapper message
[react/touchable-highlight (when-not modal
{:disabled in-popover?
:accessibility-label :sticker-message
:on-press (fn [_]
(when pack
(re-frame/dispatch [:stickers/open-sticker-pack (str pack)]))
(react/dismiss-keyboard!))
:delay-long-press 100
:on-long-press (fn []
(on-long-press
(when-not outgoing
[{:on-press #(when pack
(re-frame/dispatch [:chat.ui/show-profile from]))
:label (i18n/label :t/view-details)}])))})
[fast-image/fast-image {:style {:margin 10 :width 140 :height 140}
:source {:uri (str (-> content :sticker :url) "&download=true")}}]]
reaction-picker]))
(defmethod ->message constants/content-type-image
[{:keys [content in-popover? outgoing] :as message}
{:keys [on-long-press modal]
:as reaction-picker}]
[message-content-wrapper message
[message-content-image message
{:modal modal
:disabled in-popover?
:delay-long-press 100
:on-long-press (fn []
(on-long-press
(concat [{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:label (i18n/label :t/message-reply)}
{:on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)])
:id :save
:label (i18n/label :t/save)}
{:on-press #(images/download-image-http
(get-in message [:content :image]) preview/share)
:id :share
:label (i18n/label :t/share)}]
(when (and outgoing config/delete-message-enabled?)
[{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete)
:id :delete}]))))}]
reaction-picker])
(defmethod ->message constants/content-type-audio []
(let [show-timestamp? (reagent/atom false)]
(fn [{:keys [outgoing] :as message}
{:keys [on-long-press modal]
:as reaction-picker}]
[message-content-wrapper message
[react/touchable-highlight
(when-not modal
{:on-long-press
(fn [] (on-long-press (if (and outgoing config/delete-message-enabled?)
[{:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete)
:id :delete}]
[])))
:on-press (fn []
(reset! show-timestamp? true))})
[react/view (style/message-view-wrapper (:outgoing message))
[message-timestamp message show-timestamp?]
[react/view {:style (style/message-view message) :accessibility-label :audio-message}
[react/view {:style (style/message-view-content)}
[message.audio/message-content message] [message-status message]]]]]
reaction-picker])))
(defn contact-request-status-pending []
[react/view {:style {:flex-direction :row}}
[quo/text {:style {:margin-right 5.27}
:weight :medium
:color :secondary}
(i18n/label :t/contact-request-pending)]
[react/activity-indicator {:animating true
:size :small
:color colors/gray}]])
(defn contact-request-status-accepted []
[quo/text {:style {:color colors/green}
:weight :medium}
(i18n/label :t/contact-request-accepted)])
(defn contact-request-status-declined []
[quo/text {:style {:color colors/red}
:weight :medium}
(i18n/label :t/contact-request-declined)])
(defn contact-request-status-label [state]
[react/view {:style (style/contact-request-status-label state)}
(case state
constants/contact-request-message-state-pending [contact-request-status-pending]
constants/contact-request-message-state-accepted [contact-request-status-accepted]
constants/contact-request-message-state-declined [contact-request-status-declined])])
(defmethod ->message constants/content-type-contact-request
[{:keys [outgoing] :as message} _]
[react/view {:style (style/content-type-contact-request outgoing)}
[react/image {:source (resources/get-image :hand-wave)
:style {:width 112
:height 97}}]
[quo/text {:style {:margin-top 6}
:weight :bold
:size :large}
(i18n/label :t/contact-request)]
[react/view {:style {:padding-horizontal 16}}
[quo/text {:style {:margin-top 2
:margin-bottom 14}}
(get-in message [:content :text])]]
[contact-request-status-label (:contact-request-state message)]])
(defmethod ->message :default [message]
[message-content-wrapper message
[unknown-content-type message]])
(defn chat-message [{:keys [outgoing display-photo? pinned pinned-by] :as message} space-keeper]
[:<>
[reactions/with-reaction-picker
{:message message
:reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)])
:picker-on-open (fn []
(space-keeper true))
:picker-on-close (fn []
(space-keeper false))
:send-emoji (fn [{:keys [emoji-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction
{:message-id (:message-id message)
:emoji-id emoji-id}]))
:retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction-retraction
{:message-id (:message-id message)
:emoji-id emoji-id
:emoji-reaction-id emoji-reaction-id}]))
:render ->message}]
(when pinned
[react/view {:style (style/pin-indicator-container outgoing)}
[pinned-by-indicator outgoing display-photo? pinned-by]])])

View File

@ -8,7 +8,7 @@
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.ui.components.list.views :as list]
[status-im.utils.handlers :refer [<sub]]
[status-im.ui.screens.chat.message.message-old :as message]))
[status-im.ui.screens.chat.message.message :as message]))
(def selected-unpin (reagent/atom nil))

View File

@ -12,7 +12,7 @@
[status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.chat.views :as chat]
[status-im.ui.components.list.views :as list]
[status-im.ui.screens.chat.message.message-old :as message]
[status-im.ui.screens.chat.message.message :as message]
[status-im.utils.datetime :as time]))
(defn pins-topbar [chat]

View File

@ -16,7 +16,6 @@
[quo.react-native :as rn]
[status-im.ui.screens.chat.audio-message.views :as audio-message]
[quo.react :as quo.react]
[status-im.ui.screens.chat.message.message-old :as message-old]
[status-im.ui.screens.chat.message.message :as message]
[status-im.ui.screens.chat.stickers.views :as stickers]
[status-im.ui.screens.chat.styles.main :as style]
@ -28,7 +27,6 @@
[status-im.ui.screens.chat.message.gap :as gap]
[status-im.ui.screens.chat.components.accessory :as accessory]
[status-im.ui.screens.chat.components.input :as components]
[status-im.ui.screens.chat.message.datemark-old :as message-datemark-old]
[status-im.ui.screens.chat.message.datemark :as message-datemark]
[status-im.ui.components.toolbar :as toolbar]
[quo.core :as quo]
@ -39,10 +37,7 @@
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.utils.debounce :as debounce]
[status-im.navigation.state :as navigation.state]
[status-im.react-native.resources :as resources]
[status-im.ui.components.topbar :as topbar]
[quo2.foundations.colors :as quo2.colors]
[quo2.components.buttons.button :as quo2.button]))
[status-im.react-native.resources :as resources]))
(defn invitation-requests [chat-id admins]
(let [current-pk @(re-frame/subscribe [:multiaccount/public-key])
@ -58,18 +53,6 @@
(i18n/label :t/group-membership-request)]]])))))
(defn add-contact-bar [public-key]
(when-not (or @(re-frame/subscribe [:contacts/contact-added? public-key])
@(re-frame/subscribe [:contacts/contact-blocked? public-key]))
[react/touchable-highlight
{:on-press
#(re-frame/dispatch [:contact.ui/add-to-contact-pressed public-key])
:accessibility-label :add-to-contacts-button}
[react/view {:style (merge (style/add-contact) {:background-color (quo2.colors/theme-colors quo2.colors/white quo2.colors/divider-dark)})}
[icons/icon :main-icons/add
{:color colors/blue}]
[react/i18n-text {:style style/add-contact-text :key :add-to-contacts}]]]))
(defn add-contact-bar-old [public-key]
(when-not (or @(re-frame/subscribe [:contacts/contact-added? public-key])
@(re-frame/subscribe [:contacts/contact-blocked? public-key]))
[react/touchable-highlight
@ -294,33 +277,10 @@
[react/view {:style (when platform/android? {:scaleY -1})}
[chat.group/group-chat-footer chat-id invitation-admin]]))
(defn render-fn-old [{:keys [outgoing type] :as message}
idx
_
{:keys [group-chat public? community? current-public-key space-keeper
chat-id show-input? message-pin-enabled edit-enabled in-pinned-view?]}]
[react/view {:style (when (and platform/android? (not in-pinned-view?)) {:scaleY -1})}
(if (= type :datemark)
[message-datemark-old/chat-datemark (:value message)]
(if (= type :gap)
[gap/gap message idx messages-list-ref false chat-id]
; message content
[message-old/chat-message
(assoc message
:incoming-group (and group-chat (not outgoing))
:group-chat group-chat
:public? public?
:community? community?
:current-public-key current-public-key
:show-input? show-input?
:message-pin-enabled message-pin-enabled
:edit-enabled edit-enabled)
space-keeper]))])
(defn render-fn [{:keys [outgoing type] :as message}
idx
_
{:keys [group-chat public? community? current-public-key
{:keys [group-chat public? community? current-public-key space-keeper
chat-id show-input? message-pin-enabled edit-enabled in-pinned-view?]}]
[react/view {:style (when (and platform/android? (not in-pinned-view?)) {:scaleY -1})}
(if (= type :datemark)
@ -337,7 +297,8 @@
:current-public-key current-public-key
:show-input? show-input?
:message-pin-enabled message-pin-enabled
:edit-enabled edit-enabled)]))])
:edit-enabled edit-enabled)
space-keeper]))])
(def list-key-fn #(or (:message-id %) (:value %)))
(def list-ref #(reset! messages-list-ref %))
@ -372,12 +333,12 @@
:edit-enabled edit-enabled
:in-pinned-view? in-pinned-view?}))
(defn messages-view-old [{:keys [chat
bottom-space
pan-responder
mutual-contact-requests-enabled?
space-keeper
show-input?]}]
(defn messages-view [{:keys [chat
bottom-space
pan-responder
mutual-contact-requests-enabled?
space-keeper
show-input?]}]
(let [{:keys [group-chat chat-type chat-id public? community-id admins]} chat
messages @(re-frame/subscribe [:chats/raw-chat-messages-stream chat-id])
@ -408,7 +369,7 @@
:show-input? show-input?
:edit-enabled true
:in-pinned-view? false})
:render-fn render-fn-old
:render-fn render-fn
: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
@ -419,126 +380,17 @@
:keyboard-should-persist-taps :handled
:onMomentumScrollBegin state/start-scrolling
:onMomentumScrollEnd state/stop-scrolling
;;TODO https://github.com/facebook/react-native/issues/30034
;;TODO https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]))
(def show-floating-scroll-down-button (reagent/atom false))
(def threshold-percentage-to-show-floating-scroll-down-button 75)
(defn scroll-to-bottom []
(some-> ^js @messages-list-ref (.scrollToOffset #js {:y 0 :animated true})))
(defn floating-scroll-down-button [show-input?]
[rn/touchable-without-feedback
{:on-press scroll-to-bottom}
[rn/view {:style {:position :absolute
:bottom (if show-input? 126 12)
:right 12
:height 24
:width 24
:align-items :center
:justify-content :center
:border-radius (/ 24 2)
:background-color (quo2.colors/theme-colors quo2.colors/neutral-80-opa-70 quo2.colors/white-opa-70)}}
[icons/icon
:main-icons/arrow-down {:color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-100)
:width 12
:height 12}]]])
(defn on-scroll [^js ev]
(let [y (-> ev .-nativeEvent .-contentOffset .-y)
layout-height (-> ev .-nativeEvent .-layoutMeasurement .-height)
threshold-height (* (/ layout-height 100) threshold-percentage-to-show-floating-scroll-down-button)
reached-threshold? (> y threshold-height)]
(when (not= reached-threshold? @show-floating-scroll-down-button)
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets))
(reset! show-floating-scroll-down-button reached-threshold?))))
(defn messages-view [{:keys [chat
bottom-space
pan-responder
mutual-contact-requests-enabled?
show-input?]}]
(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)
contact-added? (when one-to-one? @(re-frame/subscribe [:contacts/contact-added? chat-id]))
should-send-contact-request?
(and
mutual-contact-requests-enabled?
one-to-one?
(not contact-added?))]
[:<>
;;do not use anonymous functions for handlers
[list/flat-list
(merge
pan-responder
{:key-fn list-key-fn
:ref list-ref
:header [list-header chat]
:footer [list-footer chat]
:data (when-not should-send-contact-request?
messages)
:render-data (get-render-data {:group-chat group-chat
:chat-id chat-id
:public? public?
:community-id community-id
:admins admins
:show-input? show-input?
:edit-enabled true
:in-pinned-view? false})
:render-fn render-fn
: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)
:padding-bottom 16}
:scroll-indicator-insets {:top bottom-space} ;;ios only
:keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :handled
:onMomentumScrollBegin state/start-scrolling
:onMomentumScrollEnd state/stop-scrolling
:scrollEventThrottle 16
:on-scroll on-scroll
;;TODO https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]
(when @show-floating-scroll-down-button
[floating-scroll-down-button show-input?])]))
(defn back-button []
[quo2.button/button {:type :grey
:size 32
:width 32
:accessibility-label "back-button"
:on-press #(re-frame/dispatch [:navigate-back])}
[icons/icon :main-icons/arrow-left {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]])
(defn search-button []
[quo2.button/button {:type :grey
:size 32
:width 32
:accessibility-label "search-button"}
[icons/icon :main-icons/search {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]])
(defn topbar-content []
(let [window-width @(re-frame/subscribe [:dimensions/window-width])
{:keys [group-chat chat-id] :as chat-info} @(re-frame/subscribe [:chats/current-chat])]
[react/view {:flex-direction :row :align-items :center :height 56}
[react/touchable-highlight {:on-press #(when-not group-chat
(debounce/dispatch-and-chill [:chat.ui/show-profile chat-id] 1000))
:style {:flex 1 :margin-left 12 :width (- window-width 120)}}
[toolbar-content/toolbar-content-view-inner chat-info]]]))
(defn navigate-back-handler []
(when (and (not @navigation.state/curr-modal) (= (get @re-frame.db/app-db :view-id) :chat))
(react/hw-back-remove-listener navigate-back-handler)
(re-frame/dispatch [:close-chat])
(re-frame/dispatch [:navigate-back])))
(defn topbar-content-old []
(defn topbar-content []
(let [window-width @(re-frame/subscribe [:dimensions/window-width])
{:keys [group-chat chat-id] :as chat-info} @(re-frame/subscribe [:chats/current-chat])]
[react/touchable-highlight {:on-press #(when-not group-chat
@ -546,7 +398,7 @@
:style {:flex 1 :width (- window-width 120)}}
[toolbar-content/toolbar-content-view-inner chat-info]]))
(defn topbar-old []
(defn topbar []
;;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
@ -555,10 +407,9 @@
: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]]
[topbar-content]]
[react/touchable-highlight {:on-press-in #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[sheets/current-chat-actions])
{:content (fn [] [sheets/current-chat-actions])
:height 256}])
:accessibility-label :chat-menu-button
:style {:right 0 :top 0 :bottom 0 :position :absolute
@ -566,7 +417,7 @@
:padding-right 16}}
[icons/icon :main-icons/more {:color colors/black}]]])
(defn chat-render-old []
(defn chat-render []
(let [bottom-space (reagent/atom 0)
panel-space (reagent/atom 52)
active-panel (reagent/atom nil)
@ -585,24 +436,24 @@
mutual-contact-requests-enabled? @(re-frame/subscribe [:mutual-contact-requests/enabled?])
max-bottom-space (max @bottom-space @panel-space)]
[:<>
[topbar-old]
[topbar]
[connectivity/loading-indicator]
(when chat-id
(if group-chat
[invitation-requests chat-id admins]
(when-not mutual-contact-requests-enabled? [add-contact-bar-old chat-id])))
(when-not mutual-contact-requests-enabled? [add-contact-bar chat-id])))
;;MESSAGES LIST
[messages-view-old {: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?}]
[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-old text-input-ref max-bottom-space]
[components/autocomplete-mentions text-input-ref max-bottom-space]
(when show-input?
;; NOTE: this only accepts two children
[accessory/view {:y position-y
@ -611,12 +462,12 @@
:on-close on-close
:on-update-inset on-update}
[react/view
[edit/edit-message-auto-focus-wrapper-old text-input-ref]
[reply/reply-message-auto-focus-wrapper-old text-input-ref]
[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-old
[components/chat-toolbar
{:chat-id chat-id
:active-panel @active-panel
:set-active-panel set-active-panel
@ -624,47 +475,10 @@
[contact-request/contact-request-message-auto-focus-wrapper text-input-ref]]
[bottom-sheet @active-panel]])]))))
(defn chat-render []
(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 []
(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-will-unmount (fn [] (react/hw-back-remove-listener navigate-back-handler))
:reagent-render chat-render-old}))
(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-will-unmount (fn [] (react/hw-back-remove-listener navigate-back-handler))
:reagent-render chat-render}))
:reagent-render chat-render}))

View File

@ -17,7 +17,7 @@
[status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.chat.utils :as chat.utils]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.screens.chat.message.message-old :as message]
[status-im.ui.screens.chat.message.message :as message]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.profile.components.views :as profile.components]
[status-im.utils.debounce :as debounce]

View File

@ -129,7 +129,7 @@
home-item
{:on-press (fn []
(re-frame/dispatch [:dismiss-keyboard])
(if (and @config/new-ui-enabled? platform/android?)
(if (and config/new-ui-enabled? platform/android?)
(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id]))
(re-frame/dispatch [:search/home-filter-changed nil])
@ -146,7 +146,7 @@
home-item
{:on-press (fn []
(re-frame/dispatch [:dismiss-keyboard])
(if (and @config/new-ui-enabled? platform/android?)
(if (and config/new-ui-enabled? platform/android?)
(re-frame/dispatch [:chat.ui/navigate-to-chat-nav2 chat-id])
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id]))
(re-frame/dispatch [:search/home-filter-changed nil])
@ -256,7 +256,7 @@
:accessibility-label :notifications-button
:on-press #(do
(re-frame/dispatch [:mark-all-activity-center-notifications-as-read])
(if (and @config/new-ui-enabled? @config/new-activity-center-enabled?)
(if config/new-activity-center-enabled?
(re-frame/dispatch [:navigate-to :activity-center])
(re-frame/dispatch [:navigate-to :notifications-center])))}
[icons/icon :main-icons/notification2 {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]]

View File

@ -13,7 +13,7 @@
[status-im.ui.screens.profile.components.styles :as profile.components.styles]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.components.common.common :as components.common]
[status-im.ui.screens.chat.message.message-old :as message]
[status-im.ui.screens.chat.message.message :as message]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.utils :as chat.utils]
[status-im.utils.debounce :as debounce])

View File

@ -5,7 +5,6 @@
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.ui.screens.profile.visibility-status.styles :as styles]
[status-im.utils.config :as config]
[status-im.utils.datetime :as datetime]
[status-im.utils.handlers :refer [<sub]]))
@ -75,9 +74,7 @@
status-type)))
(defn icon-dot-color [{:keys [status-type] :or {status-type constants/visibility-status-inactive}}]
(if @config/new-ui-enabled?
(:color (get visibility-status-type-data status-type))
(:color (get visibility-status-type-data-old status-type))))
(:color (get visibility-status-type-data status-type)))
(defn my-icon? [public-key]
(or (string/blank? public-key)
@ -91,27 +88,17 @@
(defn icon-dot-accessibility-label
[dot-color]
(if @config/new-ui-enabled?
(if (= dot-color quo2.colors/success-50)
:online-profile-photo-dot
:offline-profile-photo-dot)
(if (= dot-color colors/color-online)
:online-profile-photo-dot
:offline-profile-photo-dot)))
(if (= dot-color quo2.colors/success-50)
:online-profile-photo-dot
:offline-profile-photo-dot))
(defn icon-dot-margin
[size identicon?]
(if @config/new-ui-enabled?
-2
(if identicon?
(/ size 6)
(/ size 7))))
[_ _]
-2)
(defn icon-dot-size
[container-size]
(if @config/new-ui-enabled?
(/ container-size 2.4)
(/ container-size 4)))
(/ container-size 2.4))
(defn icon-visibility-status-dot
[public-key container-size identicon?]
@ -120,7 +107,7 @@
size (icon-dot-size container-size)
margin (icon-dot-margin size identicon?)
dot-color (icon-dot-color visibility-status-update)
new-ui? @config/new-ui-enabled?]
new-ui? true]
(merge (styles/visibility-status-dot {:color dot-color
:size size
:new-ui? new-ui?})

View File

@ -11,7 +11,6 @@
[status-im.ui.components.react :as react]
[status-im.ui.screens.profile.visibility-status.styles :as styles]
[status-im.ui.screens.profile.visibility-status.utils :as utils]
[status-im.utils.config :as config]
[status-im.utils.handlers :refer [<sub]]
[status-im.utils.platform :as platform]))
@ -34,7 +33,7 @@
@button-ref
(fn [_ _ _ _ _ py]
(dispatch-popover
(if (and platform/android? @config/new-ui-enabled?)
(if (and platform/android? true)
(- py (:status-bar-height @rn/navigation-const))
py)))))
@ -42,7 +41,7 @@
(let [automatic? (= status-type
constants/visibility-status-automatic)
[border-width margin-left size] (if automatic? [1 -10 12] [0 6 10])
new-ui? @config/new-ui-enabled?]
new-ui? true]
[:<>
(when automatic?
[rn/view {:style (styles/visibility-status-profile-dot

View File

@ -226,7 +226,7 @@
:hardwareBackButton {:dismissModalOnPress false
:popStackOnPress false}
:topBar {:visible false}}
:component chat/chat-old}
:component chat/chat}
;Pinned messages
{:name :chat-pinned-messages
@ -905,9 +905,9 @@
quo.preview/screens)
(when config/quo-preview-enabled?
quo.preview/main-screens)
(when (or config/quo-preview-enabled? @config/new-ui-enabled?)
(when config/quo-preview-enabled?
quo2.preview/screens)
(when @config/new-ui-enabled?
(when config/new-ui-enabled?
navigation2.screens/screen-overwrites)
(when config/quo-preview-enabled?
quo2.preview/main-screens)))

View File

@ -1,5 +1,5 @@
(ns status-im.ui.screens.status.views
(:require [status-im.ui.screens.chat.message.message-old :as message]
(:require [status-im.ui.screens.chat.message.message :as message]
[status-im.ui.components.react :as react]
[quo.design-system.colors :as colors]
[status-im.utils.datetime :as datetime]

View File

@ -59,7 +59,7 @@
(reagent.core/reactify-component
(fn []
(let [{:keys [component insets]} (get
(if (or js/goog.DEBUG @config/new-ui-enabled?)
(if js/goog.DEBUG
(get-screens)
screens)
(keyword key))]

View File

@ -0,0 +1,89 @@
(ns status-im.ui2.screens.chat.components.reply
(:require [quo2.foundations.colors :as quo2.colors]
[status-im.ui.components.icons.icons :as icons]
[quo.react-native :as rn]
[status-im.constants :as constants]
[status-im.utils.handlers :refer [<sub >evt]]
[quo2.components.markdown.text :as quo2.text]
[status-im.ui.screens.chat.photos :as photos]
[quo2.components.buttons.button :as quo2.button]
[clojure.string :as string]
[status-im.ethereum.stateofus :as stateofus]
[status-im.i18n.i18n :as i18n]
[status-im.ui2.screens.chat.composer.style :as styles]))
(defn get-quoted-text-with-mentions [parsed-text]
(string/join
(mapv (fn [{:keys [type literal children]}]
(cond
(= type "paragraph")
(get-quoted-text-with-mentions children)
(= type "mention")
(<sub [:contacts/contact-name-by-identity literal])
(seq children)
(get-quoted-text-with-mentions children)
:else
literal))
parsed-text)))
(defn format-author [contact-name]
(let [author (if (or (= (aget contact-name 0) "@")
;; in case of replies
(= (aget contact-name 1) "@"))
(or (stateofus/username contact-name)
(subs contact-name 0 81))
contact-name)]
author))
(defn format-reply-author [from username current-public-key]
(or (and (= from current-public-key)
(i18n/label :t/You))
(format-author username)))
; This component is also used for quoted pinned message as the UI is very similar
(defn reply-message [{:keys [from identicon content-type contentType parsed-text content]} in-chat-input? pin?]
(let [contact-name (<sub [:contacts/contact-name-by-identity from])
current-public-key (<sub [:multiaccount/public-key])
content-type (or content-type contentType)]
[rn/view {:style {:flex-direction :row :height (when-not pin? 24)}}
[rn/view {:style (styles/reply-content pin?)}
(when-not pin?
;;TODO quo2 icon should be used
[icons/icon :main-icons/connector {:color (quo2.colors/theme-colors quo2.colors/neutral-40 quo2.colors/neutral-60)
:container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}])
[rn/view {:style (styles/quoted-message pin?)}
[photos/member-photo from identicon 16]
[quo2.text/text {:weight :semi-bold
:size :paragraph-2
:number-of-lines 1
:style {:margin-left 4}}
(format-reply-author from contact-name current-public-key)]
[quo2.text/text {:number-of-lines 1
:size :label
:weight :regular
:style (merge {:ellipsize-mode :tail
:text-transform :none
:margin-left 4
:margin-top 2}
(when (or (= constants/content-type-image content-type)
(= constants/content-type-sticker content-type)
(= constants/content-type-audio content-type))
{:color (quo2.colors/theme-colors quo2.colors/neutral-50 quo2.colors/neutral-40)}))}
(case (or content-type contentType)
constants/content-type-image "Image"
constants/content-type-sticker "Sticker"
constants/content-type-audio "Audio"
(get-quoted-text-with-mentions (or parsed-text (:parsed-text content))))]]]
(when in-chat-input?
[quo2.button/button {:width 24
:size 24
:type :outline
:accessibility-label :reply-cancel-button
:on-press #(>evt [:chat.ui/cancel-message-reply])}
;;TODO quo2 icon should be used
[icons/icon :main-icons/close {:width 16
:height 16
:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/neutral-40)}]])]))

View File

@ -0,0 +1,178 @@
(ns status-im.ui2.screens.chat.composer.input
(:require [quo.react-native :as rn]
[status-im.i18n.i18n :as i18n]
[status-im.utils.handlers :refer [<sub >evt]]
[quo.design-system.colors :as colors]
[status-im.utils.utils :as utils.utils]
[status-im.utils.platform :as platform]
[clojure.string :as string]
[reagent.core :as reagent]
[status-im.chat.constants :as chat.constants]
[status-im.ui2.screens.chat.composer.style :as style]
[re-frame.core :as re-frame]
[status-im.chat.models.mentions :as mentions]
[quo.react]))
(defonce input-texts (atom {}))
(defonce mentions-enabled (reagent/atom {}))
(defonce chat-input-key (reagent/atom 1))
(re-frame/reg-fx
:chat.ui/clear-inputs
(fn []
(reset! input-texts {})
(reset! mentions-enabled {})
(reset! chat-input-key 1)))
(defn input-focus [text-input-ref]
(some-> ^js (quo.react/current-ref text-input-ref) .focus))
(defn show-send [{:keys [actions-ref send-ref sticker-ref]}]
(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})
(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]}]
(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})
(when sticker-ref
(quo.react/set-native-props sticker-ref #js {:width nil :right nil})))
(defn reset-input [refs chat-id]
(some-> ^js (quo.react/current-ref (:text-input-ref refs)) .clear)
(swap! mentions-enabled update :render not)
(swap! input-texts dissoc chat-id))
(defn clear-input [chat-id refs]
(hide-send refs)
(if (get @mentions-enabled chat-id)
(do
(swap! mentions-enabled dissoc chat-id)
;;we need this timeout, because if we clear text input and first index was a mention object with blue color,
;;after clearing text will be typed with this blue color, so we render white text first and then clear it
(js/setTimeout #(reset-input refs chat-id) 50))
(reset-input refs chat-id)))
(defn on-text-change [val chat-id]
(swap! input-texts assoc chat-id val)
;;we still store it in app-db for mentions, we don't have reactions in views
(>evt [:chat.ui/set-chat-input-text val]))
(defn on-selection-change [timeout-id last-text-change mentionable-users args]
(let [selection (.-selection ^js (.-nativeEvent ^js args))
start (.-start selection)
end (.-end selection)]
;; NOTE(rasom): on iOS we do not dispatch this event immediately
;; because it is needed only in case if selection is changed without
;; typing. Timeout might be canceled on `on-change`.
(when platform/ios?
(reset!
timeout-id
(utils.utils/set-timeout
#(>evt [::mentions/on-selection-change
{:start start
:end end}
mentionable-users])
50)))
;; NOTE(rasom): on Android we dispatch event only in case if there
;; was no text changes during last 50ms. `on-selection-change` is
;; dispatched after `on-change`, that's why there is no another way
;; to know whether selection was changed without typing.
(when (and platform/android?
(or (not @last-text-change)
(< 50 (- (js/Date.now) @last-text-change))))
(>evt [::mentions/on-selection-change
{:start start
:end end}
mentionable-users]))))
(defn on-change [last-text-change timeout-id mentionable-users refs chat-id sending-image args]
(let [text (.-text ^js (.-nativeEvent ^js args))
prev-text (get @input-texts chat-id)]
(when (and (seq prev-text) (empty? text) (not sending-image))
(hide-send refs))
(when (and (empty? prev-text) (seq text))
(show-send refs))
(when (and (not (get @mentions-enabled chat-id)) (string/index-of text "@"))
(swap! mentions-enabled assoc chat-id true))
;; NOTE(rasom): on iOS `on-selection-change` is canceled in case if it
;; happens during typing because it is not needed for mention
;; suggestions calculation
(when (and platform/ios? @timeout-id)
(utils.utils/clear-timeout @timeout-id))
(when platform/android?
(reset! last-text-change (js/Date.now)))
(on-text-change text chat-id)
;; NOTE(rasom): on iOS `on-change` is dispatched after `on-text-input`,
;; that's why mention suggestions are calculated on `on-change`
(when platform/ios?
(>evt [::mentions/calculate-suggestions mentionable-users]))))
(defn on-text-input [mentionable-users chat-id args]
(let [native-event (.-nativeEvent ^js args)
text (.-text ^js native-event)
previous-text (.-previousText ^js native-event)
range (.-range ^js native-event)
start (.-start ^js range)
end (.-end ^js range)]
(when (and (not (get @mentions-enabled chat-id)) (string/index-of text "@"))
(swap! mentions-enabled assoc chat-id true))
(>evt
[::mentions/on-text-input
{:new-text text
:previous-text previous-text
:start start
:end end}])
;; NOTE(rasom): on Android `on-text-input` is dispatched after
;; `on-change`, that's why mention suggestions are calculated
;; on `on-change`
(when platform/android?
(>evt [::mentions/calculate-suggestions mentionable-users]))))
(defn text-input [{:keys [set-active-panel refs chat-id sending-image on-content-size-change]}]
(let [cooldown-enabled? (<sub [:chats/current-chat-cooldown-enabled?])
mentionable-users (<sub [:chats/mentionable-users])
timeout-id (atom nil)
last-text-change (atom nil)
mentions-enabled (get @mentions-enabled chat-id)]
[rn/text-input
{:style (style/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])
(<sub [:chat/input-with-mentions]))]
^{:key (str idx "_" type "_" text)}
[rn/text (when (= type :mention) {:style {:color "#0DA4C9"}})
text])
(get @input-texts chat-id))]))

View File

@ -0,0 +1,56 @@
(ns status-im.ui2.screens.chat.composer.mentions
(:require [quo2.reanimated :as reanimated]
[status-im.ui.components.list.views :as list]
[quo.components.list.item :as list-item]
[status-im.ui.screens.chat.photos :as photos]
[quo.components.text :as text]
[quo.react-native :as rn]
[quo.react]
[status-im.utils.handlers :refer [>evt]]))
(defn mention-item
[[public-key {:keys [alias name nickname] :as user}] _ _ text-input-ref]
(let [ens-name? (not= alias name)]
[rn/touchable-opacity {:on-press #(>evt [:chat.ui/select-mention text-input-ref user]) :style {:border-width 1 :border-color :red}}
;;TODO quo2 item should be used
[list-item/list-item
(cond-> {:icon [photos/member-photo public-key]
:size :small
:text-size :small
:title
[text/text
{:weight :medium
:ellipsize-mode :tail
:number-of-lines 1
:size :small}
(if nickname
nickname
name)
(when nickname
[text/text
{:weight :regular
:color :secondary
:ellipsize-mode :tail
:size :small}
" "
(when ens-name?
"@")
name])]
:title-text-weight :medium}
ens-name?
(assoc :subtitle alias))]]))
(defn autocomplete-mentions [suggestions]
[:f>
(fn []
(let [animation (reanimated/use-shared-value 0)]
(quo.react/effect! #(reanimated/set-shared-value animation (reanimated/with-timing (if (seq suggestions) 0 200))))
[reanimated/view {:style (reanimated/apply-animations-to-style
{:transform [{:translateY animation}]}
{:bottom 0 :position :absolute :z-index 5 :max-height 180})}
[list/flat-list
{:keyboardShouldPersistTaps :always
:data suggestions
:key-fn first
:render-fn mention-item
:content-container-style {:padding-bottom 12}}]]))])

View File

@ -0,0 +1,21 @@
(ns status-im.ui2.screens.chat.composer.reply
(:require [quo.react-native :as rn]
[status-im.ui2.screens.chat.composer.input :as input]
[status-im.ui2.screens.chat.components.reply :as reply]))
(defn focus-input-on-reply [reply had-reply text-input-ref]
;;when we show reply we focus input
(when-not (= reply @had-reply)
(reset! had-reply reply)
(when reply
;; A setTimeout of 0 is necessary to ensure the statement is enqueued and will get executed ASAP.
(js/setTimeout #(input/input-focus text-input-ref) 0))))
(defn reply-message-auto-focus-wrapper [text-input-ref _]
(let [had-reply (atom nil)]
(fn [_ reply]
(focus-input-on-reply reply had-reply text-input-ref)
(when reply
[rn/view {:style {:padding-horizontal 15
:padding-vertical 8}}
[reply/reply-message reply true]]))))

View File

@ -0,0 +1,82 @@
(ns status-im.ui2.screens.chat.composer.style
(:require [quo2.foundations.typography :as quo2.typography]
[quo.design-system.colors :as colors]
[status-im.utils.platform :as platform]
[quo2.foundations.colors :as quo2.colors]))
(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 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 2}
(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 bottom-sheet-handle []
{:width 32
:height 4
:background-color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)
:opacity 0.05
:border-radius 100
:align-self :center
:margin-top 8})
(defn bottom-sheet-controls [insets]
{:flex-direction :row
:padding-horizontal 20
:elevation 2
:z-index 3
: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 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 1})
(defn reply-content [pin?]
{:padding-horizontal (when-not pin? 10)
:flex 1
:flex-direction :row})
(defn quoted-message [pin?]
(merge {:flex-direction :row
:align-items :center
:width "45%"}
(when-not pin? {:position :absolute
:left 34
:top 3})))

View File

@ -0,0 +1,194 @@
(ns status-im.ui2.screens.chat.composer.view
(:require [quo2.gesture :as gesture]
[quo2.reanimated :as reanimated]
[re-frame.core :as re-frame]
[quo.components.safe-area :as safe-area]
[quo.react-native :as rn]
[status-im.ui2.screens.chat.composer.style :as styles]
[status-im.ui2.screens.chat.composer.reply :as reply]
[quo2.components.buttons.button :as quo2.button]
[status-im.utils.handlers :refer [<sub]]
[status-im.ui2.screens.chat.composer.input :as input]
[oops.core :refer [oget]]
[quo.react]
[clojure.string :as string]
[status-im.ui2.screens.chat.composer.mentions :as mentions]))
(defn calculate-y [context keyboard-shown min-y max-y added-value]
(if keyboard-shown
(if (= (:state @context) :max)
max-y
(if (< (:y @context) max-y)
(+ (:y @context) added-value)
(do
(swap! context assoc :state :max)
max-y)))
(do
(swap! context assoc :state :min)
min-y)))
(defn calculate-y-with-mentions [y max-y max-height chat-id suggestions reply]
(let [input-text (:input-text (get (<sub [:chat/inputs]) chat-id))
num-lines (count (string/split input-text "\n"))
text-height (* num-lines 22)
mentions-height (min 132 (+ 16 (* 46 (- (count suggestions) 1))))
should-translate (if (< (- max-height text-height) mentions-height) true false)
min-value (if-not reply mentions-height (+ mentions-height 44))
; translate value when mentions list appear while at bottom of expanded input sheet
mentions-translate-value (if should-translate (min min-value (- mentions-height (- max-height text-height))) mentions-height)]
(when (or (< y max-y) should-translate) mentions-translate-value)))
(defn get-y-value [context keyboard-shown min-y max-y added-value max-height chat-id suggestions reply]
(let [y (calculate-y context keyboard-shown min-y max-y added-value)]
y (+ y (when (seq suggestions) (calculate-y-with-mentions y max-y max-height chat-id suggestions reply)))))
(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/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/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 composer [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)
text-input-ref (quo.react/create-ref)
send-ref (quo.react/create-ref)
refs {:send-ref send-ref
:text-input-ref text-input-ref}]
(fn []
[:f>
(fn []
(let [reply (<sub [:chats/reply-message])
suggestions (<sub [:chat/mention-suggestions])
{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 - default height
max-height (- max-y 56 (:bottom insets)) ; 56 - top-bar height
added-value (if (and (not (seq suggestions)) reply) 38 0) ; increased height of input box needed when reply
min-y (+ min-y (when reply 38))
y (get-y-value context keyboard-shown min-y max-y added-value max-height chat-id suggestions reply)
translate-y (reanimated/use-shared-value 0)
shared-height (reanimated/use-shared-value min-y)
bg-opacity (reanimated/use-shared-value 0)
input-content-change (get-input-content-change context translate-y shared-height max-height
bg-opacity keyboard-shown min-y max-y)
bottom-sheet-gesture (get-bottom-sheet-gesture context translate-y (:text-input-ref refs) keyboard-shown
min-y max-y shared-height max-height bg-opacity)]
(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/input-bottom-sheet window-height))}
;handle
[rn/view {:style (styles/bottom-sheet-handle)}]
[reply/reply-message-auto-focus-wrapper (:text-input-ref refs) reply]
[rn/view {:style {:height (- max-y 80 added-value)}}
[input/text-input {:chat-id chat-id
:on-content-size-change input-content-change
:sending-image false
:refs refs
:set-active-panel #()}]]]]
;CONTROLS
(when-not (seq suggestions)
[rn/view {:style (styles/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/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)
(input/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/bottom-sheet-background window-height))}]
[mentions/autocomplete-mentions suggestions]]))])))])

View File

@ -0,0 +1,773 @@
(ns status-im.ui2.screens.chat.messages.message
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.chat.models.delete-message-for-me]
[status-im.chat.models.images :as images]
[status-im.chat.models.pin-message :as models.pin-message]
[status-im.chat.models.reactions :as models.reactions]
[status-im.constants :as constants]
[status-im.i18n.i18n :as i18n]
[status-im.react-native.resources :as resources]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.fast-image :as fast-image]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.bottom-sheets.context-drawer :as message-context-drawer]
[status-im.ui.screens.chat.image.preview.views :as preview]
[status-im.ui.screens.chat.message.audio :as message.audio]
[status-im.ui.screens.chat.message.command :as message.command]
[status-im.ui.screens.chat.message.gap :as message.gap]
[status-im.ui.screens.chat.message.link-preview :as link-preview]
[status-im.ui.screens.chat.message.reactions :as reactions]
[status-im.ui.screens.chat.message.reactions-row :as reaction-row]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.screens.chat.styles.message.message :as style]
[status-im.ui.screens.chat.utils :as chat.utils]
[status-im.ui.screens.chat.styles.photos :as photos.style]
[status-im.ui.screens.communities.icon :as communities.icon]
[status-im.utils.handlers :refer [>evt]]
[status-im.utils.config :as config]
[status-im.utils.security :as security]
[quo2.foundations.typography :as typography]
[quo2.foundations.colors :as quo2.colors]
[status-im.ui2.screens.chat.components.reply :as reply])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn message-timestamp-anim
[anim-opacity show-timestamp?]
(animation/start
(animation/anim-sequence
[(animation/timing
anim-opacity
{:toValue 1
:duration 100
:easing (.-ease ^js animation/easing)
:useNativeDriver true})
(animation/timing
anim-opacity
{:toValue 0
:delay 2000
:duration 100
:easing (.-ease ^js animation/easing)
:useNativeDriver true})]) #(reset! show-timestamp? false)))
(defview mention-element [from]
(letsubs [contact-name [:contacts/contact-name-by-identity from]]
contact-name))
(def edited-at-text (str " ⌫ " (i18n/label :t/edited)))
(defn message-status [{:keys [outgoing content outgoing-status pinned edited-at in-popover?]}]
(when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover
[react/view
{:align-self :flex-end
:position :absolute
:bottom 9 ; 6 Bubble bottom, 3 message baseline
(if (:rtl? content) :left :right) 0
:flex-direction :row
:align-items :flex-end}
(when outgoing
[icons/icon (case outgoing-status
:sending :tiny-icons/tiny-pending
:sent :tiny-icons/tiny-sent
:not-sent :tiny-icons/tiny-warning
:delivered :tiny-icons/tiny-delivered
:tiny-icons/tiny-pending)
{:width 16
:height 12
:color (if pinned colors/gray colors/white)
:accessibility-label (name outgoing-status)}])
(when edited-at [react/text {:style (style/message-status-text)} edited-at-text])]))
(defn message-timestamp
[{:keys [timestamp-str in-popover?]} show-timestamp?]
(when-not in-popover? ;; We keep track if showing this message in a list in pin-limit-popover
(let [anim-opacity (animation/create-value 0)]
[react/animated-view {:style (style/message-timestamp-wrapper) :opacity anim-opacity}
(when @show-timestamp? (message-timestamp-anim anim-opacity show-timestamp?))
[react/text
{:style (style/message-timestamp-text)
:accessibility-label :message-timestamp}
timestamp-str]])))
(defview quoted-message
[_ reply pin?]
[react/view {:style (when-not pin? (style/quoted-message-container))}
[reply/reply-message reply false pin?]])
(defn system-text? [content-type]
(= content-type constants/content-type-system-text))
(defn render-inline [message-text content-type acc {:keys [type literal destination]}]
(case type
""
(conj acc literal)
"code"
(conj acc [quo/text {:max-font-size-multiplier react/max-font-size-multiplier
:style (style/inline-code-style)
:monospace true}
literal])
"emph"
(conj acc [react/text-class (style/emph-style) literal])
"strong"
(conj acc [react/text-class (style/strong-style) literal])
"strong-emph"
(conj acc [quo/text (style/strong-emph-style) literal])
"del"
(conj acc [react/text-class (style/strikethrough-style) literal])
"link"
(conj acc
[react/text-class
{:style
{:color colors/blue
:text-decoration-line :underline}
:on-press
#(when (and (security/safe-link? destination)
(security/safe-link-text? message-text))
(re-frame/dispatch
[:browser.ui/message-link-pressed destination]))}
destination])
"mention"
(conj acc
[react/view {:style {:background-color quo2.colors/primary-50-opa-10 :border-radius 6 :padding-horizontal 3}}
[react/text-class
{:style (merge {:color (if (system-text? content-type) colors/black quo2.colors/primary-50)}
(if (system-text? content-type) typography/font-regular typography/font-medium))
:on-press (when-not (system-text? content-type)
#(>evt [:chat.ui/show-profile literal]))}
[mention-element literal]]])
"status-tag"
(conj acc [react/text-class
{:style {:color colors/blue
:text-decoration-line :underline}
:on-press
#(re-frame/dispatch
[:chat.ui/start-public-chat literal])}
"#"
literal])
(conj acc literal)))
(defn render-block [{:keys [content content-type in-popover?]} acc
{:keys [type ^js literal children]}]
(case type
"paragraph"
(conj acc (reduce
(fn [acc e] (render-inline (:text content) content-type acc e))
[react/text-class (style/text-style content-type in-popover?)]
children))
"blockquote"
(conj acc [react/view (style/blockquote-style)
[react/text-class (style/blockquote-text-style)
(.substring literal 0 (.-length literal))]])
"codeblock"
(conj acc [react/view {:style style/codeblock-style}
[quo/text {:max-font-size-multiplier react/max-font-size-multiplier
:style style/codeblock-text-style
:monospace true}
(.substring literal 0 (dec (.-length literal)))]])
acc))
(defn render-parsed-text [message tree]
(reduce (fn [acc e] (render-block message acc e)) [:<>] tree))
(defn render-parsed-text-with-message-status [{:keys [edited-at in-popover?] :as message} tree]
(let [elements (render-parsed-text message tree)
message-status [react/text {:style (style/message-status-placeholder)}
(str (if (not in-popover?) " " " ") (when (and (not in-popover?) edited-at) edited-at-text))]
last-element (peek elements)]
;; Using `nth` here as slightly faster than `first`, roughly 30%
;; It's worth considering pure js structures for this code path as
;; it's perfomance critical
(if (= react/text-class (nth last-element 0))
;; Append message status to last text
(conj (pop elements) (conj last-element message-status))
;; Append message status to new block
(conj elements message-status))))
(defn unknown-content-type
[{:keys [content-type content] :as message}]
[react/view (style/message-view message)
[react/text
{:style {:color colors/white-persist}}
(if (seq (:text content))
(:text content)
(str "Unhandled content-type " content-type))]])
(defn message-not-sent-text
[chat-id message-id]
[react/touchable-highlight
{:on-press
(fn []
(re-frame/dispatch
[:bottom-sheet/show-sheet
{:content (sheets/options chat-id message-id)
:content-height 200}])
(react/dismiss-keyboard!))}
[react/view style/not-sent-view
[react/text {:style style/not-sent-text}
(i18n/label :t/status-not-sent-tap)]
[react/view style/not-sent-icon
[icons/icon :main-icons/warning {:color colors/red}]]]])
(defn pin-author-name [pinned-by]
(let [user-contact @(re-frame/subscribe [:multiaccount/contact])
contact-names @(re-frame/subscribe [:contacts/contact-two-names-by-identity pinned-by])]
;; We append empty spaces to the name as a workaround to make one-line and multi-line label components show correctly
(str " " (if (= pinned-by (user-contact :public-key)) (i18n/label :t/You) (first contact-names)))))
(def pin-icon-width 10)
(def pin-icon-height 15)
(defn pin-icon []
[icons/icon :main-icons/pin16 {:color (:text-04 @colors/theme)
:height pin-icon-height
:width pin-icon-width}])
(defn pinned-by-indicator [pinned-by]
[react/view {:style (style/pin-indicator)
:accessibility-label :pinned-by}
[pin-icon]
[quo/text {:size :small
:color :main
:style (style/pin-author-text)}
(pin-author-name pinned-by)]])
(defn message-delivery-status
[{:keys [chat-id message-id outgoing-status message-type]}]
(when (and (not= constants/message-type-private-group-system-message message-type)
(= outgoing-status :not-sent))
[message-not-sent-text chat-id message-id]))
(defview message-author-name [from opts]
(letsubs [contact-with-names [:contacts/contact-by-identity from]]
(chat.utils/format-author contact-with-names opts)))
(defview message-my-name [opts]
(letsubs [contact-with-names [:multiaccount/contact]]
(chat.utils/format-author contact-with-names opts)))
(defview community-content [{:keys [community-id] :as message}]
(letsubs [{:keys [name description verified] :as community} [:communities/community community-id]
communities-enabled? [:communities/enabled?]]
(when (and communities-enabled? community)
[react/view {:style (assoc (style/message-wrapper message)
:margin-vertical 10
:margin-left 8
:width 271)}
(when verified
[react/view (style/community-verified)
[react/text {:style {:font-size 13
:color colors/blue}} (i18n/label :t/communities-verified)]])
[react/view (style/community-message verified)
[react/view {:width 62
:padding-left 14}
(if (= community-id constants/status-community-id)
[react/image {:source (resources/get-image :status-logo)
:style {:width 40
:height 40}}]
[communities.icon/community-icon community])]
[react/view {:padding-right 14 :flex 1}
[react/text {:style {:font-weight "700" :font-size 17}}
name]
[react/text description]]]
[react/view (style/community-view-button)
[react/touchable-highlight {:on-press #(re-frame/dispatch [:navigate-to
:community
{:community-id (:id community)}])}
[react/text {:style {:text-align :center
:color colors/blue}} (i18n/label :t/view)]]]])))
(defn message-content-wrapper
"Author, userpic and delivery wrapper"
[{:keys [last-in-group?
identicon
from in-popover? timestamp-str
deleted-for-me? pinned]
:as message} content {:keys [modal close-modal]}]
(let [response-to (:response-to (:content message))]
[react/view {:style (style/message-wrapper message)
:pointer-events :box-none
:accessibility-label :chat-item}
(when (and (seq response-to) (:quoted-message message))
[quoted-message response-to (:quoted-message message)])
[react/view {:style (style/message-body)
:pointer-events :box-none}
[react/view (style/message-author-userpic)
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[react/touchable-highlight {:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[photos/member-photo from identicon]])]
[react/view {:style (style/message-author-wrapper)}
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[react/view {:style {:flex-direction :row :align-items :center}}
[react/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]]
[react/text
{:style (merge
{:padding-left 5
:margin-top 2}
(style/message-timestamp-text))
:accessibility-label :message-timestamp}
timestamp-str]])
;; MESSAGE CONTENT
;; TODO(yqrashawn): wait for system message component to display deleted for me UI
(if deleted-for-me?
[react/view {:style {:border-width 2
:border-color :red}}
content]
content)
[link-preview/link-preview-wrapper (:links (:content message)) false false]]]
; delivery status
[react/view (style/delivery-status)
[message-delivery-status message]]]))
(def image-max-width 260)
(def image-max-height 192)
(defn image-set-size [dimensions]
(fn [evt]
(let [width (.-width (.-nativeEvent evt))
height (.-height (.-nativeEvent evt))]
(if (< width height)
;; if width less than the height we reduce width proportionally to height
(let [k (/ height image-max-height)]
(when (not= (/ width k) (first @dimensions))
(reset! dimensions {:width (/ width k) :height image-max-height :loaded true})))
(swap! dimensions assoc :loaded true)))))
(defn message-content-image
[{:keys [content]} _]
(let [dimensions (reagent/atom {:width image-max-width :height image-max-height :loaded false})
visible (reagent/atom false)
uri (:image content)]
(fn [{:keys [in-popover?] :as message}
{:keys [on-long-press]}]
(let [style-opts {:outgoing false
:opacity (if (:loaded @dimensions) 1 0)
:width (:width @dimensions)
:height (:height @dimensions)}]
[:<>
[preview/preview-image {:message message
:visible @visible
:on-close #(do (reset! visible false)
(reagent/flush))}]
[react/touchable-highlight {:on-press (fn []
(reset! visible true)
(react/dismiss-keyboard!))
:on-long-press @on-long-press
:disabled in-popover?}
[react/view {:style (style/image-message style-opts)
:accessibility-label :image-message}
(when (or (:error @dimensions) (not (:loaded @dimensions)))
[react/view
(merge (dissoc style-opts :opacity)
{:flex 1 :align-items :center :justify-content :center :position :absolute})
(if (:error @dimensions)
[icons/icon :main-icons/cancel]
[react/activity-indicator {:animating true}])])
[fast-image/fast-image {:style (dissoc style-opts :outgoing)
:on-load (image-set-size dimensions)
:on-error #(swap! dimensions assoc :error true)
:source {:uri uri}}]
[react/view {:style (style/image-message-border style-opts)}]]]]))))
(defmulti ->message :content-type)
(defmethod ->message constants/content-type-command
[message]
[message.command/command-content message-content-wrapper message])
(defmethod ->message constants/content-type-gap
[message]
[message.gap/gap message])
(defmethod ->message constants/content-type-system-text [{:keys [content] :as message}]
[react/view {:accessibility-label :chat-item}
[react/view (style/system-message-body message)
[react/view (style/message-view message)
[react/view (style/message-view-content)
[render-parsed-text message (:parsed-text content)]]]]])
(defn pin-message [{:keys [chat-id pinned] :as message}]
(let [pinned-messages @(re-frame/subscribe [:chats/pinned chat-id])]
(if (and (not pinned) (> (count pinned-messages) 2))
(do
(js/setTimeout (fn [] (re-frame/dispatch [:dismiss-keyboard])) 500)
(re-frame/dispatch [:show-popover {:view :pin-limit
:message message
:prevent-closing? true}]))
(re-frame/dispatch [::models.pin-message/send-pin-message (assoc message :pinned (not pinned))]))))
(defn on-long-press-fn [on-long-press {:keys [pinned message-pin-enabled outgoing edit-enabled show-input?] :as message} content]
(on-long-press
(concat
(when (and outgoing edit-enabled)
[{:type :main
:on-press #(re-frame/dispatch [:chat.ui/edit-message message])
:label (i18n/label :t/edit-message)
:icon :main-icons/edit-context20
:id :edit}])
(when show-input?
[{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:label (i18n/label :t/message-reply)
:icon :main-icons/reply-context20
:id :reply}])
[{:type :main
:on-press #(react/copy-to-clipboard
(reply/get-quoted-text-with-mentions
(get content :parsed-text)))
:label (i18n/label :t/copy-text)
:icon :main-icons/copy-context20
:id :copy}]
(when message-pin-enabled
[{:type :main
:on-press #(pin-message message)
:label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat))
:icon :main-icons/pin-context20
:id (if pinned :unpin :pin)}])
[{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :main-icons/delete-context20
:id :delete-for-me}]
(when (and outgoing config/delete-message-enabled?)
[{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete-for-everyone)
:icon :main-icons/delete-context20
:id :delete-for-all}]))))
(defn collapsible-text-message [_ _]
(let [collapsed? (reagent/atom false)
show-timestamp? (reagent/atom false)]
(fn [{:keys [content in-popover?] :as message} on-long-press modal ref]
(let [on-long-press (fn []
(if @collapsed?
(do (reset! collapsed? false)
(js/setTimeout #(on-long-press-fn on-long-press message content) 200))
(on-long-press-fn on-long-press message content)))]
(reset! ref on-long-press)
[react/touchable-highlight
(when-not modal
{:on-press (fn [_]
(react/dismiss-keyboard!)
(reset! show-timestamp? true))
:delay-long-press 100
:on-long-press on-long-press
:disabled in-popover?})
[react/view style/message-view-wrapper
[message-timestamp message show-timestamp?]
[react/view {:style (style/message-view message)}
[react/view {:style (style/message-view-content)}
[react/view
[render-parsed-text-with-message-status message (:parsed-text content)]]]]]]))))
(defmethod ->message constants/content-type-text
[message {:keys [on-long-press modal ref] :as reaction-picker}]
[message-content-wrapper message
[collapsible-text-message message on-long-press modal ref]
reaction-picker])
(defmethod ->message constants/content-type-pin [{:keys [from in-popover? timestamp-str] :as message} {:keys [modal close-modal]}]
(let [response-to (:response-to (:content message))]
[react/view {:style (merge {:flex-direction :row :margin-vertical 8} (style/message-wrapper message))}
[react/view {:style {:width photos.style/default-size
:height photos.style/default-size
:margin-horizontal 8
:border-radius photos.style/default-size
:justify-content :center
:align-items :center
:background-color quo2.colors/primary-50-opa-10}}
[pin-icon]]
[react/view
[react/view {:style {:flex-direction :row :align-items :center}}
[react/touchable-opacity {:style style/message-author-touchable
:disabled in-popover?
:on-press #(do (when modal (close-modal))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[message-author-name from {:modal modal}]]
[react/text {:style {:font-size 13}} (str " " (i18n/label :pinned-a-message))]
[react/text
{:style (merge
{:padding-left 5
:margin-top 2}
(style/message-timestamp-text))
:accessibility-label :message-timestamp}
timestamp-str]]
[quoted-message response-to (:quoted-message message) true]]]))
(defmethod ->message constants/content-type-community
[message]
[community-content message])
(defmethod ->message constants/content-type-status
[{:keys [content content-type] :as message}]
[message-content-wrapper message
[react/view style/status-container
[react/text {:style (style/status-text)}
(reduce
(fn [acc e] (render-inline (:text content) content-type acc e))
[react/text-class {:style (style/status-text)}]
(-> content :parsed-text peek :children))]]])
(defmethod ->message constants/content-type-emoji []
(let [show-timestamp? (reagent/atom false)]
(fn [{:keys [content pinned in-popover? message-pin-enabled] :as message}
{:keys [on-long-press modal ref]
:as reaction-picker}]
(let [on-long-press (fn []
(on-long-press
(concat
[{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:icon :main-icons/reply-context20
:label (i18n/label :t/message-reply)}
{:type :main
:on-press #(react/copy-to-clipboard (get content :text))
:id :copy
:icon :main-icons/copy-context20
:label (i18n/label :t/copy-text)}]
(when message-pin-enabled [{:type :main
:on-press #(pin-message message)
:id :pin
:icon :main-icons/pin-context20
:label (if pinned (i18n/label :t/unpin) (i18n/label :t/pin))}]))))]
(reset! ref on-long-press)
[message-content-wrapper message
[react/touchable-highlight (when-not modal
{:disabled in-popover?
:on-press (fn []
(react/dismiss-keyboard!)
(reset! show-timestamp? true))
:delay-long-press 100
:on-long-press on-long-press})
[react/view style/message-view-wrapper
[message-timestamp message show-timestamp?]
[react/view (style/message-view message)
[react/view {:style (style/message-view-content)}
[react/view {:style (style/style-message-text)}
[react/text {:style (style/emoji-message message)}
(:text content)]]
[message-status message]]]]]
reaction-picker]))))
(defmethod ->message constants/content-type-sticker
[{:keys [content from outgoing in-popover?]
:as message}
{:keys [on-long-press modal ref]
:as reaction-picker}]
(let [pack (get-in content [:sticker :pack])
on-long-press (fn []
(on-long-press
(when-not outgoing
[{:type :main
:icon :main-icons/stickers-context20
:on-press #(when pack
(re-frame/dispatch [:chat.ui/show-profile from]))
:label (i18n/label :t/see-sticker-set)}])))]
(reset! ref on-long-press)
[message-content-wrapper message
[react/touchable-highlight (when-not modal
{:disabled in-popover?
:accessibility-label :sticker-message
:on-press (fn [_]
(when pack
(re-frame/dispatch [:stickers/open-sticker-pack (str pack)]))
(react/dismiss-keyboard!))
:delay-long-press 100
:on-long-press on-long-press})
[fast-image/fast-image {:style {:margin 10 :width 140 :height 140}
:source {:uri (str (-> content :sticker :url) "&download=true")}}]]
reaction-picker]))
(defmethod ->message constants/content-type-image
[{:keys [content in-popover? outgoing] :as message}
{:keys [on-long-press modal ref]
:as reaction-picker}]
(let [on-long-press (fn []
(on-long-press
(concat [{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:icon :main-icons/reply-context20
:label (i18n/label :t/message-reply)}
{:type :main
:on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)])
:id :save
:icon :main-icons/save-context20
:label (i18n/label :t/save-image-library)}
{:type :main
:on-press #(images/download-image-http
(get-in message [:content :image]) preview/share)
:id :share
:icon :main-icons/share-context20
:label (i18n/label :t/share-image)}]
[{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :main-icons/delete-context20
:id :delete-for-me}]
(when (and outgoing config/delete-message-enabled?)
[{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete-for-everyone)
:icon :main-icons/delete-context20
:id :delete}]))))]
(reset! ref on-long-press)
[message-content-wrapper message
[message-content-image message
{:modal modal
:disabled in-popover?
:delay-long-press 100
:on-long-press ref}]
reaction-picker]))
(defmethod ->message constants/content-type-audio []
(let [show-timestamp? (reagent/atom false)]
(fn [{:keys [outgoing pinned] :as message}
{:keys [on-long-press modal ref]
:as reaction-picker}]
(let [on-long-press (fn [] (on-long-press [{:type :main
:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:label (i18n/label :t/message-reply)
:icon :main-icons/reply-context20
:id :reply}
{:type :main
:on-press #(pin-message message)
:label (i18n/label (if pinned :t/unpin-from-chat :t/pin-to-chat))
:icon :main-icons/pin-context20
:id (if pinned :unpin :pin)}
{:type :danger
:on-press #(re-frame/dispatch
[:chat.ui/delete-message-for-me message
config/delete-message-for-me-undo-time-limit-ms])
:label (i18n/label :t/delete-for-me)
:icon :main-icons/delete-context20
:id :delete-for-me}
(when (and outgoing config/delete-message-enabled?)
{:type :danger
:on-press #(re-frame/dispatch [:chat.ui/soft-delete-message message])
:label (i18n/label :t/delete-for-everyone)
:icon :main-icons/delete-context20
:id :delete})]))]
(reset! ref on-long-press)
[message-content-wrapper message
[react/touchable-highlight
(when-not modal
{:on-long-press on-long-press
:on-press (fn []
(reset! show-timestamp? true))})
[react/view style/message-view-wrapper
[message-timestamp message show-timestamp?]
[react/view {:style (style/message-view message) :accessibility-label :audio-message}
[react/view {:style (style/message-view-content)}
[message.audio/message-content message] [message-status message]]]]]
reaction-picker]))))
(defn contact-request-status-pending []
[react/view {:style {:flex-direction :row}}
[quo/text {:style {:margin-right 5.27}
:weight :medium
:color :secondary}
(i18n/label :t/contact-request-pending)]
[react/activity-indicator {:animating true
:size :small
:color colors/gray}]])
(defn contact-request-status-accepted []
[quo/text {:style {:color colors/green}
:weight :medium}
(i18n/label :t/contact-request-accepted)])
(defn contact-request-status-declined []
[quo/text {:style {:color colors/red}
:weight :medium}
(i18n/label :t/contact-request-declined)])
(defn contact-request-status-label [state]
[react/view {:style (style/contact-request-status-label state)}
(case state
constants/contact-request-message-state-pending [contact-request-status-pending]
constants/contact-request-message-state-accepted [contact-request-status-accepted]
constants/contact-request-message-state-declined [contact-request-status-declined])])
(defmethod ->message constants/content-type-contact-request
[message _]
[react/view {:style (style/content-type-contact-request)}
[react/image {:source (resources/get-image :hand-wave)
:style {:width 112
:height 97}}]
[quo/text {:style {:margin-top 6}
:weight :bold
:size :large}
(i18n/label :t/contact-request)]
[react/view {:style {:padding-horizontal 16}}
[quo/text {:style {:margin-top 2
:margin-bottom 14}}
(get-in message [:content :text])]]
[contact-request-status-label (:contact-request-state message)]])
(defmethod ->message :default [message]
[message-content-wrapper message
[unknown-content-type message]])
(defn chat-message [{:keys [pinned pinned-by mentioned] :as message}]
(let [reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message) (:chat-id message)])
own-reactions (reduce (fn [acc {:keys [emoji-id own]}]
(if own (conj acc emoji-id) acc))
[] reactions)
send-emoji (fn [{:keys [emoji-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction
{:message-id (:message-id message)
:emoji-id emoji-id}]))
retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}]
(re-frame/dispatch [::models.reactions/send-emoji-reaction-retraction
{:message-id (:message-id message)
:emoji-id emoji-id
:emoji-reaction-id emoji-reaction-id}]))
on-emoji-press (fn [emoji-id]
(let [active ((set own-reactions) emoji-id)]
(if active
(retract-emoji {:emoji-id emoji-id
:emoji-reaction-id (reactions/extract-id reactions emoji-id)})
(send-emoji {:emoji-id emoji-id}))))
on-open-drawer (fn [actions]
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (message-context-drawer/message-options
actions
(into #{} (js->clj own-reactions))
#(on-emoji-press %))}]))
on-long-press (atom nil)]
[react/view {:style (merge (when (or mentioned pinned) {:background-color quo2.colors/primary-50-opa-5 :border-radius 16 :margin-bottom 4}) {:margin-horizontal 8})}
(when pinned
[react/view {:style (style/pin-indicator-container)}
[pinned-by-indicator pinned-by]])
[->message message {:ref on-long-press
:modal false
:on-long-press on-open-drawer}]
[reaction-row/message-reactions message reactions nil on-emoji-press on-long-press]])) ;; TODO: pass on-open-drawer function

View File

@ -0,0 +1,191 @@
(ns status-im.ui2.screens.chat.messages.view
(:require [reagent.core :as reagent]
[quo.react-native :as rn]
[quo2.foundations.colors :as quo2.colors]
[status-im.constants :as constants]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui.components.list.views :as list]
[status-im.ui2.screens.chat.messages.message :as message]
[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.utils.utils :as utils]
[status-im.utils.platform :as platform]
[status-im.ui.screens.chat.state :as state]
[status-im.ui.components.icons.icons :as icons]))
(defonce ^:const threshold-percentage-to-show-floating-scroll-down-button 75)
(defonce show-floating-scroll-down-button (reagent/atom false))
(defonce messages-list-ref (atom nil))
(def list-key-fn #(or (:message-id %) (:value %)))
(def list-ref #(reset! messages-list-ref %))
(defn scroll-to-bottom []
(some-> ^js @messages-list-ref (.scrollToOffset #js {:y 0 :animated true})))
(defn floating-scroll-down-button [show-input?]
[rn/touchable-without-feedback
{:on-press scroll-to-bottom}
[rn/view {:style {:position :absolute
:bottom (if show-input? 126 12)
:right 12
:height 24
:width 24
:align-items :center
:justify-content :center
:border-radius (/ 24 2)
:background-color (quo2.colors/theme-colors quo2.colors/neutral-80-opa-70 quo2.colors/white-opa-70)}}
;;TODO icon from quo2 should be used instead!
[icons/icon
:main-icons/arrow-down {:color (quo2.colors/theme-colors quo2.colors/white quo2.colors/neutral-100)
:width 12
:height 12}]]])
(defn on-scroll [^js ev]
(let [y (-> ev .-nativeEvent .-contentOffset .-y)
layout-height (-> ev .-nativeEvent .-layoutMeasurement .-height)
threshold-height (* (/ layout-height 100) threshold-percentage-to-show-floating-scroll-down-button)
reached-threshold? (> y threshold-height)]
(when (not= reached-threshold? @show-floating-scroll-down-button)
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets))
(reset! show-floating-scroll-down-button reached-threshold?))))
(defn chat-intro-header-container
[]
;;not implemented
[rn/view])
(defn list-footer [{:keys [chat-id] :as chat}]
(let [loading-messages? (<sub [:chats/loading-messages? chat-id])
no-messages? (<sub [:chats/chat-no-messages? chat-id])
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}]]
[chat-intro-header-container chat no-messages?])]))
(defn list-header [{:keys [chat-id chat-type invitation-admin]}]
(when (= chat-type constants/private-group-chat-type)
[rn/view {:style (when platform/android? {:scaleY -1})}
[chat.group/group-chat-footer chat-id invitation-admin]]))
(defn render-fn [{:keys [outgoing type] :as message}
idx
_
{:keys [group-chat public? community? current-public-key
chat-id show-input? message-pin-enabled edit-enabled in-pinned-view?]}]
[rn/view {:style (when (and platform/android? (not in-pinned-view?)) {:scaleY -1})}
(if (= type :datemark)
[message-datemark/chat-datemark (:value message)]
(if (= type :gap)
[gap/gap message idx messages-list-ref false chat-id]
; message content
[message/chat-message
(assoc message
:incoming-group (and group-chat (not outgoing))
:group-chat group-chat
:public? public?
:community? community?
:current-public-key current-public-key
:show-input? show-input?
:message-pin-enabled message-pin-enabled
:edit-enabled edit-enabled)]))])
(defn on-viewable-items-changed [^js e]
(when @messages-list-ref
(reset! state/first-not-visible-item
(when-let [^js last-visible-element (aget (.-viewableItems e) (dec (.-length ^js (.-viewableItems e))))]
(let [index (.-index last-visible-element)
;; Get first not visible element, if it's a datemark/gap
;; we might unnecessarely add messages on receiving as
;; they do not have a clock value, but most of the times
;; it will be a message
first-not-visible (aget (.-data ^js (.-props ^js @messages-list-ref)) (inc index))]
(when (and first-not-visible
(= :message (:type first-not-visible)))
first-not-visible))))))
;;TODO this is not really working in pair with inserting new messages because we stop inserting new messages
;;if they outside the viewarea, but we load more here because end is reached,so its slowdown UI because we
;;load and render 20 messages more, but we can't prevent this , because otherwise :on-end-reached will work wrong
(defn list-on-end-reached []
(if @state/scrolling
(>evt [:chat.ui/load-more-messages-for-current-chat])
(utils/set-timeout #(>evt [:chat.ui/load-more-messages-for-current-chat])
(if platform/low-device? 700 200))))
(defn get-render-data [{:keys [group-chat chat-id public? community-id admins space-keeper show-input? edit-enabled in-pinned-view?]}]
(let [current-public-key (<sub [:multiaccount/public-key])
community (<sub [:communities/community community-id])
group-admin? (get admins current-public-key)
community-admin? (when community (community :admin))
message-pin-enabled (and (not public?)
(or (not group-chat)
(and group-chat
(or group-admin?
community-admin?))))]
{:group-chat group-chat
:public? public?
:community? (not (nil? community-id))
:current-public-key current-public-key
:space-keeper space-keeper
:chat-id chat-id
:show-input? show-input?
:message-pin-enabled message-pin-enabled
:edit-enabled edit-enabled
:in-pinned-view? in-pinned-view?}))
(defn messages-view [{:keys [chat
bottom-space
pan-responder
mutual-contact-requests-enabled?
show-input?]}]
(let [{:keys [group-chat chat-type chat-id public? community-id admins]} chat
messages (<sub [:chats/raw-chat-messages-stream chat-id])
one-to-one? (= chat-type constants/one-to-one-chat-type)
contact-added? (when one-to-one? (<sub [:contacts/contact-added? chat-id]))
should-send-contact-request?
(and
mutual-contact-requests-enabled?
one-to-one?
(not contact-added?))]
[:<>
;;do not use anonymous functions for handlers
[list/flat-list
(merge
pan-responder
{:key-fn list-key-fn
:ref list-ref
:header [list-header chat]
:footer [list-footer chat]
:data (when-not should-send-contact-request?
messages)
:render-data (get-render-data {:group-chat group-chat
:chat-id chat-id
:public? public?
:community-id community-id
:admins admins
:show-input? show-input?
:edit-enabled true
:in-pinned-view? false})
:render-fn render-fn
: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)
:padding-bottom 16}
:scroll-indicator-insets {:top bottom-space} ;;ios only
:keyboard-dismiss-mode :interactive
:keyboard-should-persist-taps :handled
:onMomentumScrollBegin state/start-scrolling
:onMomentumScrollEnd state/stop-scrolling
:scrollEventThrottle 16
:on-scroll on-scroll
;;TODO https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]
(when @show-floating-scroll-down-button
[floating-scroll-down-button show-input?])]))

View File

@ -0,0 +1,85 @@
(ns status-im.ui2.screens.chat.view
(:require [reagent.core :as reagent]
[status-im.ui.components.connectivity.view :as connectivity]
[status-im.ui.components.topbar :as topbar]
[status-im.ui2.screens.chat.composer.view :as composer]
[status-im.utils.debounce :as debounce]
[quo.react-native :as rn]
[quo2.components.buttons.button :as quo2.button]
[quo2.foundations.colors :as quo2.colors]
[status-im.ui.components.react :as react]
[status-im.navigation.state :as navigation.state]
[status-im.ui2.screens.chat.messages.view :as messages]
[status-im.utils.handlers :refer [<sub >evt]]
[status-im.ui.components.icons.icons :as icons]
[re-frame.db]))
(defn topbar-content []
(let [window-width (<sub [:dimensions/window-width])
{:keys [group-chat chat-id chat-name]} (<sub [:chats/current-chat])]
[rn/view {:flex-direction :row :align-items :center :height 56}
[rn/touchable-highlight {:on-press #(when-not group-chat
(debounce/dispatch-and-chill [:chat.ui/show-profile chat-id] 1000))
:style {:flex 1 :margin-left 12 :width (- window-width 120)}}
[rn/view
[rn/text chat-name]]]]))
(defn back-button []
[quo2.button/button {:type :grey
:size 32
:width 32
:accessibility-label "back-button"
:on-press #(>evt [:navigate-back])}
[icons/icon :main-icons/arrow-left {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]])
(defn search-button []
[quo2.button/button {:type :grey
:size 32
:width 32
:accessibility-label "search-button"}
[icons/icon :main-icons/search {:color (quo2.colors/theme-colors quo2.colors/neutral-100 quo2.colors/white)}]])
(defn navigate-back-handler []
(when (and (not @navigation.state/curr-modal) (= (get @re-frame.db/app-db :view-id) :chat))
(react/hw-back-remove-listener navigate-back-handler)
(>evt [:close-chat])
(>evt [:navigate-back])))
(defn chat-render []
(let [{:keys [chat-id show-input?] :as chat}
;;we want to react only on these fields, do not use full chat map here
(<sub [:chats/current-chat-chat-view])
mutual-contact-requests-enabled? (<sub [:mutual-contact-requests/enabled?])]
[react/keyboard-avoiding-view-new {:style {:flex 1}
:ignore-offset false}
;;TODO It is better to not use topbar component because of performance
[topbar/topbar {:navigation :none
:left-component [rn/view {:flex-direction :row :margin-left 16}
[back-button]]
:title-component [topbar-content]
:right-component [rn/view {:flex-direction :row :margin-right 16}
[search-button]]
:border-bottom false
:new-ui? true}]
[connectivity/loading-indicator]
;;TODO not implemented
#_(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/messages-view
{:chat chat
:mutual-contact-requests-enabled? mutual-contact-requests-enabled?
:show-input? show-input?}]
;;INPUT COMPOSER
(when show-input?
[composer/composer chat-id])]))
(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-will-unmount (fn [] (react/hw-back-remove-listener navigate-back-handler))
:reagent-render chat-render}))

View File

@ -13,7 +13,6 @@
([k] (get (config) k))
([k not-found] (get (config) k not-found)))
;; TODO(oskarth): Extend this to deal with true/false for Jenkins parameter builds
(defn enabled? [v] (= "1" v))
;; NOTE(oskarth): Feature flag deprecation lifecycles. We want to make sure
@ -27,13 +26,11 @@
(def goerli-rpc-url (str "https://goerli.infura.io/v3/" INFURA_TOKEN))
(def opensea-api-key OPENSEA_API_KEY)
(def bootnodes-settings-enabled? (enabled? (get-config :BOOTNODES_SETTINGS_ENABLED "1")))
(def rpc-networks-only? (enabled? (get-config :RPC_NETWORKS_ONLY "1")))
(def mailserver-confirmations-enabled? (enabled? (get-config :MAILSERVER_CONFIRMATIONS_ENABLED)))
(def pairing-popup-disabled? (enabled? (get-config :PAIRING_POPUP_DISABLED "0")))
(def cached-webviews-enabled? (enabled? (get-config :CACHED_WEBVIEWS_ENABLED 0)))
(def snoopy-enabled? (enabled? (get-config :SNOOPY 0)))
(def dev-build? (enabled? (get-config :DEV_BUILD 0)))
(def tr-to-talk-enabled? (enabled? (get-config :TRIBUTE_TO_TALK 0)))
(def max-message-delivery-attempts (js/parseInt (get-config :MAX_MESSAGE_DELIVERY_ATTEMPTS "6")))
(def max-images-batch (js/parseInt (get-config :MAX_IMAGES_BATCH "1")))
;; NOTE: only disabled in releases
@ -61,8 +58,6 @@
(def fleet (get-config :FLEET "eth.staging"))
(def apn-topic (get-config :APN_TOPIC "im.status.ethereum"))
(def default-network (get-config :DEFAULT_NETWORK "goerli_rpc"))
(def pow-target (js/parseFloat (get-config :POW_TARGET "0.0001")))
(def pow-time (js/parseInt (get-config :POW_TIME "1")))
(def max-installations 2)
; currently not supported in status-go
(def enable-remove-profile-picture? false)
@ -158,17 +153,6 @@
[id network])
default-networks)))
(def link-preview-enabled-site?
#{"youtube.com"
"youtu.be"
"our.status.im"
"github.com"
"giphy.com"
"gph.is"
"media.giphy.com"})
(def default-relay-provider "https://relay.walletconnect.com")
(def default-wallet-connect-metadata {:name "Status Wallet"
:description "Status is a secure messaging app, crypto wallet, and Web3 browser built with state of the art technology."
:url "#"
@ -176,11 +160,11 @@
(def wallet-connect-project-id "87815d72a81d739d2a7ce15c2cfdefb3")
(def new-ui-enabled? (atom false))
;;TODO for development only should be removed in status 2.0
(def new-ui-enabled? true)
;; TODO: Remove this (highly) temporary flag once the new Activity Center is
;; usable enough to replace the old one **in the new UI**.
(def new-activity-center-enabled?
(atom false))
(def new-activity-center-enabled? false)
(def delete-message-for-me-undo-time-limit-ms 4000)

View File

@ -1,6 +1,5 @@
(ns status-im.utils.image-server
(:require [quo.design-system.colors :as colors]
[status-im.utils.config :as config]))
(:require [quo.design-system.colors :as colors]))
(def ^:const image-server-uri-prefix "https://localhost:")
(def ^:const identicons-action "/messages/identicons")
@ -16,16 +15,10 @@
(.getTime (js/Date.)))
(defn get-identicons-uri [port public-key]
(let [base (str image-server-uri-prefix port identicons-action "?publicKey=" public-key "&theme=" (current-theme) "&clock=" (timestamp))]
(cond-> base
@config/new-ui-enabled? (str "&addRing=1"))))
(str image-server-uri-prefix port identicons-action "?publicKey=" public-key "&theme=" (current-theme) "&clock=" (timestamp) "&addRing=1"))
(defn get-account-image-uri [port public-key image-name key-uid]
(let [base (str image-server-uri-prefix port account-images-action "?publicKey=" public-key "&keyUid=" key-uid "&imageName=" image-name "&theme=" (current-theme) "&clock=" (timestamp))]
(cond-> base
@config/new-ui-enabled? (str "&addRing=1"))))
(str image-server-uri-prefix port account-images-action "?publicKey=" public-key "&keyUid=" key-uid "&imageName=" image-name "&theme=" (current-theme) "&clock=" (timestamp) "&addRing=1"))
(defn get-contact-image-uri [port public-key image-name clock]
(let [base (str image-server-uri-prefix port contact-images-action "?publicKey=" public-key "&imageName=" image-name "&theme=" (current-theme) "&clock=" clock)]
(cond-> base
@config/new-ui-enabled? (str "&addRing=1"))))
(str image-server-uri-prefix port contact-images-action "?publicKey=" public-key "&imageName=" image-name "&theme=" (current-theme) "&clock=" clock "&addRing=1"))