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:
parent
094a7421c1
commit
00fbb0dee6
|
@ -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)})
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -8,11 +8,7 @@
|
|||
[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]
|
||||
|
@ -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}]]))])))
|
||||
[icons/icon icon {:color icon-color}]])])))
|
||||
|
||||
(defn title-column
|
||||
[{:keys [title text-color subtitle subtitle-max-lines subtitle-secondary
|
||||
|
|
|
@ -282,6 +282,7 @@
|
|||
[{:keys [db] :as cofx} public-key message]
|
||||
(fx/merge cofx
|
||||
{: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}]
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 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)))))
|
||||
(navigation/init-root :tos))))))
|
||||
|
||||
(defn- keycard-setup? [cofx]
|
||||
(boolean (get-in cofx [:db :keycard :flow])))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
(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]))
|
||||
|
||||
|
@ -11,4 +11,5 @@
|
|||
;; These screens will overwrite navigation/screens.cljs screens on enabling new UI toggle
|
||||
(def screen-overwrites
|
||||
[{:name :chat
|
||||
:options {:topBar {:visible false}}
|
||||
:component #(stack-with-switcher/overlap-stack chat/chat :chat)}])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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}]))
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,14 +40,6 @@
|
|||
: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 []
|
||||
|
|
|
@ -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,52 +286,9 @@
|
|||
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
|
||||
|
@ -371,14 +312,17 @@
|
|||
(when ens-name?
|
||||
"@")
|
||||
name])]
|
||||
:title-text-weight :medium}
|
||||
:title-text-weight :medium
|
||||
:on-press
|
||||
(fn []
|
||||
(re-frame/dispatch [:chat.ui/select-mention text-input-ref user]))}
|
||||
|
||||
ens-name?
|
||||
(assoc :subtitle alias))]]))
|
||||
(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,14 +385,14 @@
|
|||
[rn/view {:style (styles/input-container contact-request)}
|
||||
[send-image]
|
||||
[rn/view {:style styles/input-row}
|
||||
[text-input-old {:chat-id chat-id
|
||||
[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)
|
||||
[send-button #(do (clear-input chat-id refs)
|
||||
(re-frame/dispatch [:chat.ui/send-current-message]))
|
||||
contact-request])]
|
||||
|
||||
|
@ -484,181 +412,3 @@
|
|||
: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]]))])))])
|
||||
|
|
|
@ -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])))))
|
||||
|
|
|
@ -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
|
||||
|
@ -153,52 +131,3 @@
|
|||
: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})
|
||||
|
|
|
@ -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))]])
|
||||
|
@ -231,20 +238,26 @@
|
|||
;; 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-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)
|
||||
[react/view {:style (style/message-body message)
|
||||
:pointer-events :box-none}
|
||||
[react/view (style/message-author-userpic)
|
||||
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
|
||||
(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}}
|
||||
[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}]]
|
||||
[react/text
|
||||
{:style (merge
|
||||
{:padding-left 5
|
||||
:margin-top 2}
|
||||
(style/message-timestamp-text))
|
||||
:accessibility-label :message-timestamp}
|
||||
timestamp-str]])
|
||||
[message-author-name from {:modal modal}]])
|
||||
;;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]]]
|
||||
content
|
||||
[link-preview/link-preview-wrapper (:links (:content message)) outgoing false]]]
|
||||
; delivery status
|
||||
[react/view (style/delivery-status)
|
||||
[message-delivery-status message]]]))
|
||||
[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,141 +422,110 @@
|
|||
(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?
|
||||
|
@ -564,12 +533,24 @@
|
|||
(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?
|
||||
|
@ -599,96 +571,65 @@
|
|||
(re-frame/dispatch [:stickers/open-sticker-pack (str pack)]))
|
||||
(react/dismiss-keyboard!))
|
||||
:delay-long-press 100
|
||||
:on-long-press on-long-press})
|
||||
: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]))
|
||||
: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-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
|
||||
[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]))))
|
||||
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]}]
|
||||
(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]}]
|
||||
: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})}
|
||||
:render ->message}]
|
||||
(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
|
||||
]))
|
||||
[react/view {:style (style/pin-indicator-container outgoing)}
|
||||
[pinned-by-indicator outgoing display-photo? pinned-by]])])
|
|
@ -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]])])
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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,7 +333,7 @@
|
|||
:edit-enabled edit-enabled
|
||||
:in-pinned-view? in-pinned-view?}))
|
||||
|
||||
(defn messages-view-old [{:keys [chat
|
||||
(defn messages-view [{:keys [chat
|
||||
bottom-space
|
||||
pan-responder
|
||||
mutual-contact-requests-enabled?
|
||||
|
@ -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
|
||||
|
@ -423,122 +384,13 @@
|
|||
: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,14 +436,14 @@
|
|||
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
|
||||
[messages-view {:chat chat
|
||||
:bottom-space max-bottom-space
|
||||
:pan-responder pan-responder
|
||||
:mutual-contact-requests-enabled? mutual-contact-requests-enabled?
|
||||
|
@ -602,7 +453,7 @@
|
|||
[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,43 +475,6 @@
|
|||
[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 []
|
||||
(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 []
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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)}]]
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)))
|
||||
: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?})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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))]
|
||||
|
|
|
@ -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)}]])]))
|
|
@ -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))]))
|
|
@ -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}}]]))])
|
|
@ -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]]))))
|
|
@ -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})))
|
|
@ -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]]))])))])
|
|
@ -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
|
|
@ -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?])]))
|
|
@ -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}))
|
|
@ -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)
|
||||
|
|
|
@ -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"))
|
||||
|
|
Loading…
Reference in New Issue