cleaning, introduce react-native.red-black-tree and move messages list events
This commit is contained in:
flexsurfer 2023-01-18 12:16:33 +01:00 committed by GitHub
parent 9a60fc1600
commit ed348e0871
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 147 additions and 5335 deletions

View File

@ -1,7 +1,7 @@
status-im.utils.build/warning-handler
status-im.utils.build/get-current-sha
status-im.chat.constants/spacing-char
status-im.chat.constants/arg-wrapping-char
status-im2.common.constants/spacing-char
status-im2.common.constants/arg-wrapping-char
status-im.ios.core/init
status-im.ui.components.camera/aspects
status-im.ui.components.camera/capture-targets

View File

@ -1,134 +0,0 @@
{
"chats": [
["Featured", [
"status",
"support",
"crypto",
"chitchat",
"defi",
"markets",
"dap-ps",
"devcon",
"eth2"
]],
["General", [
"chitchat",
"hello",
"worldnews",
"status",
"support"
]],
["Entertainment", [
"music",
"movies",
"podcasts",
"books",
"gaming",
"adult",
"tv-shows"
]],
["Interests", [
"sports",
"travel",
"design",
"food",
"automotive"
]],
["Society", [
"climatechange",
"blacklivesmatter",
"politics",
"hongkong",
"privacy"
]],
["Crypto", [
"crypto",
"markets",
"crypto-education",
"ethereum",
"bitcoin",
"chainlink",
"avalanche",
"eth2",
"eips",
"dap-ps",
"cryptolife",
"governance",
"staking",
"defi",
"cryptopayments",
"tokenomics",
"web3",
"web3design",
"devcon",
"exchange",
"validators"
]],
["Technologies", [
"tech",
"ai",
"vr-ar",
"networks"
]],
["Status", [
"status",
"support",
"statusphere",
"status-townhall-questions",
"status-core-ui",
"status-keycard",
"nimbus-general",
"status-assemble",
"status-marketing",
"status-protocol",
"status-desktop",
"status-watercooler",
"status-security",
"waku",
"status-docs",
"status-general",
"status-design"
]],
["Development", [
"ethereum-clients",
"storage",
"indexing",
"sidechains",
"layer2",
"devops",
"smart-contracts",
"embark-community",
"subspace",
"open-source",
"security"
]],
["Languages", [
"status-espanol",
"statusbrasil",
"status-german",
"status-french",
"status-italiano",
"status-dutch",
"status-russian",
"status-chinese",
"status-korean",
"status-japanese",
"status-farsi",
"status-turkish",
"status-filipino",
"status-naija",
"status-indian",
"status-arabic",
"indonesian"
]]
]
}

View File

@ -58,8 +58,7 @@
;; in the SHADOW_HOST env variable to make sure that
;; it will use the right interface
:local-ip #shadow/env "SHADOW_HOST"}
:chunks {:fleets status-im.fleet.default-fleet/default-fleets
:chats status-im.chat.default-chats/default-chats}
:chunks {:fleets status-im.fleet.default-fleet/default-fleets}
:release
{:closure-defines
{status-im.utils.config/POKT_TOKEN #shadow/env "POKT_TOKEN"

View File

@ -1,8 +1,7 @@
(ns mocks.js-dependencies
(:require-macros [status-im.utils.slurp :refer [slurp]])
(:require [status-im.fleet.default-fleet :refer (default-fleets)])
(:require [status-im.utils.test :as utils.test])
(:require [status-im.chat.default-chats :refer (default-chats)]))
(:require [status-im.utils.test :as utils.test]))
;; to generate a js Proxy at js/__STATUS_MOBILE_JS_IDENTITY_PROXY__ that accept any (.xxx) call and
;; return itself
@ -398,7 +397,6 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
"../src/js/bottom_sheet.js" bottom-sheet
"../src/js/record_audio_worklets.js" record-audio-worklets
"./fleets.js" default-fleets
"./chats.js" default-chats
"@walletconnect/client" wallet-connect-client
"../translations/ar.json" (js/JSON.parse (slurp "./translations/ar.json"))
"../translations/de.json" (js/JSON.parse (slurp "./translations/de.json"))

View File

@ -109,3 +109,11 @@
(let [fn-ref (use-ref f)]
(oops/oset! fn-ref "current" f)
(use-effect-once (fn [] (fn [] (oops/ocall! fn-ref "current"))))))
(def layout-animation (.-LayoutAnimation ^js react-native))
(def configure-next (.-configureNext ^js layout-animation))
(def layout-animation-presets
{:ease-in-ease-out (-> ^js layout-animation .-Presets .-easeInEaseOut)
:linear (-> ^js layout-animation .-Presets .-linear)
:spring (-> ^js layout-animation .-Presets .-spring)})

View File

@ -1,16 +0,0 @@
(ns status-im.chat.constants)
(def command-char "/")
(def spacing-char " ")
(def arg-wrapping-char "\"")
(def spam-message-frequency-threshold 4)
(def spam-interval-ms 1000)
(def default-cooldown-period-ms 10000)
(def cooldown-reset-threshold 3)
(def cooldown-periods-ms
{1 2000
2 5000
3 10000})
(def max-text-size 4096)

View File

@ -5,10 +5,6 @@
[{:keys [public? name]}]
(str (when public? "#") name))
(defn datemark?
[{:keys [type]}]
(= type :datemark))
(defn intersperse-datemark
"Reduce step which expects the input list of messages to be sorted by clock value.
It makes best effort to group them by day.

View File

@ -1,4 +0,0 @@
(ns status-im.chat.default-chats (:require-macros [status-im.utils.slurp :refer [slurp]]))
(def default-chats
(slurp "resources/chats.json"))

View File

@ -4,27 +4,22 @@
[re-frame.core :as re-frame]
[status-im.add-new.db :as new-public-chat.db]
[status-im.chat.models.loading :as loading]
[status-im.chat.models.message-list :as message-list]
[status-im2.contexts.chat.messages.list.events :as message-list]
[status-im.constants :as constants]
[status-im.data-store.chats :as chats-store]
[status-im.data-store.contacts :as contacts-store]
[i18n.i18n :as i18n]
[status-im.mailserver.core :as mailserver]
[status-im.multiaccounts.model :as multiaccounts.model]
[status-im.ui.screens.chat.state :as chat.state]
[status-im2.contexts.chat.messages.list.state :as chat.state]
[status-im.utils.clocks :as utils.clocks]
[utils.re-frame :as rf]
[status-im.utils.types :as types]
[status-im.utils.utils :as utils]
[status-im2.contexts.chat.messages.delete-message-for-me.events :as delete-for-me]
[status-im2.contexts.chat.messages.delete-message.events :as delete-message]
[status-im2.navigation.events :as navigation]
[taoensso.timbre :as log]))
(defn chats
[]
(:chats (types/json->clj (js/require "./chats.js"))))
(defn- get-chat
[cofx chat-id]
(get-in cofx [:db :chats chat-id]))

View File

@ -3,7 +3,7 @@
[clojure.string :as string]
[goog.object :as object]
[re-frame.core :as re-frame]
[status-im.chat.constants :as chat.constants]
[status-im2.common.constants :as chat.constants]
[status-im.chat.models :as chat]
[status-im.chat.models.mentions :as mentions]
[status-im.chat.models.message :as chat.message]

View File

@ -1,6 +1,6 @@
(ns status-im.chat.models.input-test
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.chat.constants :as constants]
[status-im2.common.constants :as constants]
[status-im.chat.models.input :as input]
[utils.datetime :as datetime]))

View File

@ -1,6 +1,6 @@
(ns status-im.chat.models.loading
(:require [re-frame.core :as re-frame]
[status-im.chat.models.message-list :as message-list]
[status-im2.contexts.chat.messages.list.events :as message-list]
[status-im.constants :as constants]
[status-im.data-store.chats :as data-store.chats]
[status-im.data-store.messages :as data-store.messages]

View File

@ -4,11 +4,11 @@
[status-im.chat.models :as chat-model]
[status-im.chat.models.loading :as chat.loading]
[status-im.chat.models.mentions :as mentions]
[status-im.chat.models.message-list :as message-list]
[status-im2.contexts.chat.messages.list.events :as message-list]
[status-im.constants :as constants]
[status-im.data-store.messages :as data-store.messages]
[status-im.transport.message.protocol :as protocol]
[status-im.ui.screens.chat.state :as view.state]
[status-im2.contexts.chat.messages.list.state :as view.state]
[utils.re-frame :as rf]
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.platform :as platform]

View File

@ -1,11 +1,6 @@
(ns status-im.chat.models.message-content
(:require [status-im.constants :as constants]))
(def stylings
[[:bold constants/regx-bold]
[:italic constants/regx-italic]
[:backquote constants/regx-backquote]])
(defn emoji-only-content?
"Determines if text is just an emoji"
[{:keys [text response-to]}]

View File

@ -2,7 +2,7 @@
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.chat.models.loading :as loading]
[status-im.chat.models.message :as message]
[status-im.ui.screens.chat.state :as view.state]))
[status-im2.contexts.chat.messages.list.state :as list.state]))
(deftest add-received-message-test
(with-redefs [message/add-message #(identity %1)]
@ -26,7 +26,7 @@
;; <- message
;; <- top of the chat
(testing "there's no hidden item"
(with-redefs [view.state/first-not-visible-item (atom nil)]
(with-redefs [list.state/first-not-visible-item (atom nil)]
(is
(=
{:db
@ -46,7 +46,7 @@
;; <- message
;; <- top of the chat
(testing "the hidden item has a clock value less than the current"
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (dec clock-value)})]
(with-redefs [list.state/first-not-visible-item (atom {:clock-value (dec clock-value)})]
(is
(=
{:db
@ -66,7 +66,7 @@
;; <- first-hidden-item
;; <- top of the chat
(testing "the message falls between the first-hidden-item and cursor"
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
(with-redefs [list.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
(let [result (dissoc (message/receive-many
cofx
#js {:messages (to-array [message])})
@ -81,7 +81,7 @@
;; <- first-hidden-item
;; <- top of the chat
(testing "the message falls between the first-hidden-item and cursor is nil"
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
(with-redefs [list.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
(let [result (dissoc (message/receive-many
(update-in cofx
[:db :pagination-info chat-id]
@ -101,7 +101,7 @@
;; <- first-hidden-item
;; <- top of the chat
(testing "the message falls before both the first-hidden-item and cursor"
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
(with-redefs [list.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
(let [message #js
{:localChatId chat-id
:clock (- clock-value 2)

View File

@ -1,6 +1,6 @@
(ns status-im.contact.block
(:require [re-frame.core :as re-frame]
[status-im.chat.models.message-list :as message-list]
[status-im2.contexts.chat.messages.list.events :as message-list]
[status-im.contact.db :as contact.db]
[status-im.data-store.chats :as chats-store]
[status-im.data-store.contacts :as contacts-store]

View File

@ -1,22 +0,0 @@
(ns status-im.profile.db
(:require [cljs.spec.alpha :as spec]
[clojure.string :as string]
[status-im.chat.constants :as chat.constants]))
(defn correct-name?
[username]
(when-let [username (some-> username
(string/trim))]
(every? false?
[(string/blank? username)
(string/includes? username chat.constants/command-char)])))
(defn base64-png?
[photo-path]
(string/starts-with? photo-path "data:image/png;base64,"))
(defn base64-jpeg?
[photo-path]
(string/starts-with? photo-path "data:image/jpeg;base64,"))
(spec/def :profile/name correct-name?)

View File

@ -4,7 +4,6 @@
[quo.react-native :as rn]
[re-frame.core :as re-frame.core]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.profile.db :as profile.db]
[status-im.ui.components.chat-icon.styles :as styles]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.screens.chat.photos :as photos]
@ -55,7 +54,8 @@
styles/container-chat-list
photo-container)
size (:width photo-container)
identicon? (when photo-path (profile.db/base64-png? photo-path))
identicon? (and photo-path
(string/starts-with? photo-path "data:image/png;base64,"))
dot-styles (visibility-status-utils/icon-visibility-status-dot
public-key
size

View File

@ -3,11 +3,9 @@
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[status-im.add-new.db :as db]
[status-im.chat.models :as chat.models]
[status-im.react-native.resources :as resources]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]
[status-im2.setup.i18n-resources :as i18n-resources]
[i18n.i18n :as i18n])
(:require-macros [status-im.utils.views :as views]))
@ -37,59 +35,6 @@
:auto-correct false
:error error}])
(defn render-topic
[topic]
[react/touchable-highlight
{:on-press #(start-chat topic)
:accessibility-label :chat-item}
[react/view
{:border-color colors/gray-lighter
:border-radius 36
:border-width 1
:padding-horizontal 8
:padding-vertical 5
:margin-right 8
:margin-vertical 8}
[react/text {:style {:color colors/blue :typography :main-medium}}
(str "#" topic)]]])
(def lang-names
{"es" "status-espanol"
"pt" "statusbrasil"
"de" "status-german"
"fr" "status-french"
"it" "status-italiano"
"ru" "status-russian"
"zh" "status-chinese"
"ko" "status-korean"
"ja" "status-japanese"
"fa" "status-farsi"
"tr" "status-turkish"
"id" "indonesian"
"in" "indonesian"
"hi" "status-indian"
"ar" "status-arabic"
"fil" "status-filipino"
"nl" "status-dutch"})
(defn get-language-topic
[]
(let [lang (subs (name i18n-resources/default-device-language) 0 2)
lang3 (subs (name i18n-resources/default-device-language) 0 3)
lang-name (or (get lang-names lang3) (get lang-names lang))]
(when-not (= lang "en")
(or lang-name (str "status-" lang)))))
(def section-featured "Featured")
(defn featured-public-chats
[]
(let [lang-topic (get-language-topic)
chats (some #(when (= section-featured (first %)) (second %)) (chat.models/chats))]
(if lang-topic
(conj chats lang-topic)
chats)))
(views/defview new-public-chat
[]
(views/letsubs [topic [:public-group-topic]

View File

@ -1,50 +0,0 @@
(ns status-im.ui.screens.chat.audio-message.styles
(:require [quo.design-system.colors :as colors]))
(def container
{:flex 1
:flex-direction :column
:justify-content :space-around})
(def timer
{:font-size 28
:line-height 38
:align-self :center})
(def buttons-container
{:flex 1
:max-height 80
:flex-direction :row
:align-items :center
:justify-content :space-around
:align-self :stretch
:padding-horizontal 80})
(def rec-button-base-size 61)
(def rec-button-container
{:width rec-button-base-size
:height rec-button-base-size
:align-items "center"})
(defn rec-outer-circle
[scale-anim]
{:position "absolute"
:width rec-button-base-size
:height rec-button-base-size
:top 0
:border-width 4
:transform [{:scale scale-anim}]
:border-color colors/red-audio-recorder
:border-radius rec-button-base-size})
(defn rec-inner-circle
[scale-anim border-radius-anim]
{:position "absolute"
:top 6
:left 6
:bottom 6
:right 6
:transform [{:scale scale-anim}]
:border-radius border-radius-anim
:background-color colors/red-audio-recorder})

View File

@ -1,323 +0,0 @@
(ns status-im.ui.screens.chat.audio-message.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [goog.string :as gstring]
[quo.components.animated.pressable :as pressable]
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.audio.core :as audio]
[i18n.i18n :as i18n]
[status-im.native-module.core :as status]
[status-im.ui.components.animation :as anim]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.audio-message.styles :as styles]
[status-im.ui.screens.chat.components.input :as input]
[status-im.ui.screens.chat.components.style :as input.style]
[status-im.utils.fs :as fs]
[utils.re-frame :as rf]
[status-im.utils.utils :as utils.utils]))
;; reference db levels
(def total-silence-db -160)
(def silence-db -35)
;; update interval for the pulsing rec button
(def metering-interval 100)
;; rec pulse animation target
(defonce visual-target-value (anim/create-value total-silence-db))
;;ensure animation finishes before next meter update
(defonce metering-anim-duration (int (* metering-interval 0.9)))
(defn update-meter
[meter-data]
(let [value (if meter-data
(.-value ^js meter-data)
total-silence-db)]
(anim/start (anim/timing visual-target-value
{:toValue value
:duration metering-anim-duration
:useNativeDriver true}))))
(def base-filename "am.")
(def default-format "aac")
(def rec-options
(merge
audio/default-recorder-options
{:filename (str base-filename default-format)
:meteringInterval metering-interval}))
;; maximum 1 minute of recordings time to keep data at certain size
(def max-recording-ms (* 1 60 1000))
;; audio objects
(defonce recorder-ref (atom nil))
(defonce player-ref (atom nil))
(defn destroy-recorder
[]
(audio/destroy-recorder @recorder-ref)
(reset! recorder-ref nil))
(defn destroy-player
[]
(audio/destroy-player @player-ref)
(reset! player-ref nil))
;; state update callback
(defonce state-cb (atom #()))
;; max recording ms reached callback
(defonce max-recording-reached-cb (atom #()))
;; to be called when app goes in background
(defonce on-background-cb (atom #()))
(rf/defn on-background
{:events [:audio-recorder/on-background]}
[_]
(when @on-background-cb
(@on-background-cb))
nil)
;; during recording
(defonce recording-timer (atom nil))
(defonce recording-start-ts (atom nil))
(defonce recording-backlog-ms (atom 0))
;; updates timer UI
(defn update-timer
[timer]
(let [ms (if @recording-start-ts
(+
(- (js/Date.now) @recording-start-ts)
@recording-backlog-ms)
@recording-backlog-ms)
s (quot ms 1000)]
(if (> ms max-recording-ms)
(@max-recording-reached-cb)
(reset! timer (gstring/format "%d:%02d" (quot s 60) (mod s 60))))))
(defn reset-timer
[timer]
(reset! timer "0:00")
(reset! recording-backlog-ms 0))
(defn animate-buttons
[rec? show-ctrl? {:keys [rec-button-anim-value ctrl-buttons-anim-value]}]
(anim/start
(anim/parallel
[(anim/timing rec-button-anim-value
{:toValue (if rec? 1 0)
:duration 100
:useNativeDriver true})
(anim/timing ctrl-buttons-anim-value
{:toValue (if show-ctrl? 1 0)
:duration 100
:useNativeDriver true})])))
(defn start-recording
[{:keys [timer] :as params}]
(if (> @recording-backlog-ms max-recording-ms)
(@max-recording-reached-cb)
(do
(animate-buttons true true params)
(reset! recording-start-ts (js/Date.now))
(reset! recording-timer (utils.utils/set-interval #(update-timer timer) 1000))
(audio/start-recording
@recorder-ref
@state-cb
#(utils.utils/show-popup (i18n/label :t/audio-recorder-error) (:message %))))))
(defn reload-recorder
[]
(when @recorder-ref
(destroy-recorder))
(reset! recorder-ref (audio/new-recorder rec-options #(update-meter %) @state-cb))
;; we skip preparation since if a recorder is prepared, player wont play
(@state-cb))
(defn reload-player
([] (reload-player nil))
([on-success]
(when @player-ref
(destroy-player))
(reset! player-ref (audio/new-player
(:filename rec-options)
{:autoDestroy false
:continuesToPlayInBackground false}
@state-cb))
(audio/prepare-player
@player-ref
#(do (@state-cb) (when on-success (on-success)))
#(utils.utils/show-popup (i18n/label :t/audio-recorder-error) (:message %)))))
(defn stop-recording
[{:keys [on-success timer max-recording-reached?] :as params}]
(when @recording-timer
(utils.utils/clear-interval @recording-timer)
(reset! recording-timer nil))
(if max-recording-reached?
(reset! recording-backlog-ms (+ @recording-backlog-ms (- (js/Date.now) @recording-start-ts)))
(reset-timer timer))
(audio/stop-recording
@recorder-ref
#(do
(update-meter nil)
(reload-recorder)
(reload-player on-success))
#(utils.utils/show-popup (i18n/label :t/audio-recorder-error) (:message %)))
(animate-buttons false max-recording-reached? params))
(defn pause-recording
[{:keys [timer] :as params}]
(when @recording-timer
(utils.utils/clear-interval @recording-timer)
(reset! recording-backlog-ms (+ @recording-backlog-ms (- (js/Date.now) @recording-start-ts)))
(reset! recording-start-ts nil)
(reset! recording-timer nil)
(update-timer timer))
(audio/pause-recording
@recorder-ref
#(do (update-meter nil)
(@state-cb))
#(utils.utils/show-popup (i18n/label :t/audio-recorder-error) (:message %)))
(animate-buttons false true params))
(defn update-state
"update main UI state.
general states are:
- :recording
- :playing
- :ready-to-send
- :recording-paused
- :ready-to-record"
[state-ref]
(let [player-state (audio/get-state @player-ref)
recorder-state (audio/get-state @recorder-ref)
output-file (or
(audio/get-recorder-file-path @recorder-ref)
(:output-file @state-ref))
general (cond
(= recorder-state audio/RECORDING) :recording
(= player-state audio/PLAYING) :playing
(= player-state audio/PREPARED) :ready-to-send
(= recorder-state audio/PAUSED) :recording-paused
:else :ready-to-record)
new-state {:general general
:cancel-disabled? (nil? (#{:recording :recording-paused :ready-to-send} general))
:output-file output-file
:duration (audio/get-player-duration @player-ref)}]
(if (#{:recording :recording-paused} general)
(status/activate-keep-awake)
(status/deactivate-keep-awake))
(when (not= @state-ref new-state)
(reset! state-ref new-state))))
(defn send-audio-msessage
[state-ref]
(re-frame/dispatch [:chat/send-audio
(:output-file @state-ref)
(int (:duration @state-ref))])
(destroy-player)
(@state-cb))
;; rec-button-anim-value 0 => stopped, 1 => recording
(defview rec-button-view
[{:keys [rec-button-anim-value state] :as params}]
(letsubs [outer-scale (anim/interpolate visual-target-value
{:inputRange [total-silence-db silence-db 0]
:outputRange [1 0.8 1.2]})
inner-scale (anim/interpolate rec-button-anim-value
{:inputRange [0 1]
:outputRange [1 0.5]})
inner-border-radius (anim/interpolate rec-button-anim-value
{:inputRange [0 1]
:outputRange [styles/rec-button-base-size 16]})]
[react/touchable-highlight
{:on-press #(if (= (:general @state) :recording)
(pause-recording params)
(start-recording params))
:accessibility-label :start-stop-audio-recording-button}
[react/view {:style styles/rec-button-container}
[react/animated-view {:style (styles/rec-outer-circle outer-scale)}]
[react/animated-view {:style (styles/rec-inner-circle inner-scale inner-border-radius)}]]]))
(defn- cancel-button
[disabled? on-press contact-request]
[pressable/pressable
{:type :scale
:disabled disabled?
:on-press on-press}
[react/view {:style (input.style/send-message-button)}
[icons/icon :main-icons/close
{:container-style (merge (input.style/send-message-container contact-request)
{:background-color colors/gray})
:accessibility-label :cancel-message-button
:color colors/white-persist}]]])
(defview audio-message-view
[]
(letsubs [rec-button-anim-value (anim/create-value 0)
ctrl-buttons-anim-value (anim/create-value 0)
timer (reagent/atom "")
state (reagent/atom nil)]
{:component-did-mount (fn []
(reset-timer timer)
(reset! state-cb #(update-state state))
(reset! max-recording-reached-cb
#(do
(when (= (:general @state) :recording)
(stop-recording {:rec-button-anim-value rec-button-anim-value
:ctrl-buttons-anim-value ctrl-buttons-anim-value
:timer timer
:max-recording-reached? true}))
(utils.utils/show-popup (i18n/label :t/audio-recorder)
(i18n/label
:t/audio-recorder-max-ms-reached))))
(reset! on-background-cb #(when (= (:general @state) :recording)
(pause-recording
{:rec-button-anim-value rec-button-anim-value
:ctrl-buttons-anim-value
ctrl-buttons-anim-value
:timer timer})))
(reload-recorder))
:component-will-unmount (fn []
(when @recording-timer
(utils.utils/clear-interval @recording-timer)
(reset! recording-timer nil))
(destroy-recorder)
(destroy-player)
(when (:output-file @state)
; possible issue if message is not yet sent?
(fs/unlink (:output-file @state)))
(reset! state-cb nil)
(reset! max-recording-reached-cb nil)
(reset! on-background-cb nil))}
(let [base-params {:rec-button-anim-value rec-button-anim-value
:ctrl-buttons-anim-value ctrl-buttons-anim-value
:timer timer}
contact-request @(re-frame/subscribe [:chats/sending-contact-request])]
[react/view {:style styles/container}
[react/text
{:style styles/timer
:accessibility-label :audio-message-recorded-time} @timer]
[react/view {:style styles/buttons-container}
[react/animated-view {:style {:opacity ctrl-buttons-anim-value}}
[cancel-button (:cancel-disabled? @state) #(stop-recording base-params) contact-request]]
[rec-button-view (merge base-params {:state state})]
[react/animated-view {:style {:opacity ctrl-buttons-anim-value}}
[input/send-button
(fn []
(cond
(= :ready-to-send (:general @state))
(do
(reset-timer timer)
(animate-buttons false false base-params)
(send-audio-msessage state))
(#{:recording :recording-paused} (:general @state))
(stop-recording (merge base-params
{:on-success
#(send-audio-msessage state)}))))]]]])))

View File

@ -9,7 +9,7 @@
[quo.react-native :as rn]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.chat.constants :as chat.constants]
[status-im2.common.constants :as chat.constants]
[status-im.chat.models.mentions :as mentions]
[i18n.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]

View File

@ -1,53 +0,0 @@
(ns status-im.ui.screens.chat.extensions.views
(:require [quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[i18n.i18n :as i18n]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]))
(defn extensions-view
[]
[react/view
{:style {:background-color colors/white
:flex 1}}
[react/view {:style {:flex-direction :row}}
[react/touchable-highlight
{:on-press #(re-frame/dispatch [:wallet/prepare-transaction-from-chat])}
[react/view
{:width 128
:height 128
:justify-content :space-between
:padding-horizontal 10
:padding-vertical 12
:background-color (colors/alpha colors/purple 0.2)
:border-radius 16
:margin-left 8}
[react/view
{:background-color colors/purple
:width 40
:height 40
:border-radius 20
:align-items :center
:justify-content :center}
[icons/icon :main-icons/send {:color colors/white}]]
[react/text {:typography :medium} (i18n/label :t/send-transaction)]]]
[react/touchable-highlight
{:on-press #(re-frame/dispatch [:wallet/prepare-request-transaction-from-chat])}
[react/view
{:width 128
:height 128
:justify-content :space-between
:padding-horizontal 10
:padding-vertical 12
:background-color (colors/alpha colors/orange 0.2)
:border-radius 16
:margin-left 8}
[react/view
{:background-color colors/orange
:width 40
:height 40
:border-radius 20
:align-items :center
:justify-content :center}
[icons/icon :main-icons/receive {:color colors/white}]]
[react/text {:typography :medium} (i18n/label :t/request-transaction)]]]]])

View File

@ -4,11 +4,8 @@
[re-frame.core :as re-frame]
[status-im.constants :as constants]
[i18n.i18n :as i18n]
[status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.main :as style]
[status-im.utils.platform :as platform]
[status-im.utils.universal-links.utils :as links]
[utils.debounce :as debounce])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
@ -76,124 +73,3 @@
(letsubs [invitations [:group-chat/invitations-by-chat-id chat-id]]
(when invitation-admin
[request-membership (first invitations)])))
(def group-chat-description-loading
[react/view
{:style (merge style/intro-header-description-container
{:margin-bottom 36
:height 44})}
[react/text {:style style/intro-header-description}
(i18n/label :t/loading)]
[react/activity-indicator
{:animating true
:size :small
:color colors/gray}]])
(defn calculate-quiet-time
[synced-to
synced-from]
(when synced-from
(let [quiet-hours (quot (- synced-to synced-from)
(* 60 60))]
(if (<= quiet-hours 24)
(i18n/label :t/quiet-hours
{:quiet-hours quiet-hours})
(i18n/label :t/quiet-days
{:quiet-days (quot quiet-hours 24)})))))
(defview no-messages-community-chat-description-container
[chat-id]
(letsubs [{:keys [synced-to synced-from]}
[:chats/synced-to-and-from chat-id]]
[react/text
{:style (merge style/intro-header-description
{:margin-bottom 36})}
(let [quiet-time (calculate-quiet-time synced-to
synced-from)]
(i18n/label :t/empty-chat-description-community
{:quiet-hours quiet-time}))]))
(defview no-messages-private-group-chat-description-container
[chat-id]
(letsubs [{:keys [synced-to synced-from]}
[:chats/synced-to-and-from chat-id]]
(let [quiet-time (calculate-quiet-time synced-to
synced-from)]
[react/nested-text
{:style (merge style/intro-header-description
{:margin-bottom 36})}
(if quiet-time
(i18n/label :t/empty-chat-description-public
{:quiet-hours quiet-time})
(i18n/label :t/cleared-chat-description-public))
[{:style {:color colors/blue}
:on-press #(list-selection/open-share
{:message
(i18n/label
:t/share-public-chat-text
{:link (links/generate-link :public-chat :external chat-id)})})}
(i18n/label :t/empty-chat-description-public-share-this)]])))
(defview pending-invitation-description
[inviter-pk chat-name]
(letsubs [inviter-name [:contacts/contact-name-by-identity inviter-pk]]
[react/nested-text {:style style/intro-header-description}
[{:style {:color colors/black}} inviter-name]
(i18n/label :t/join-group-chat-description
{:username ""
:group-name chat-name})]))
(defview joined-group-chat-description
[inviter-pk chat-name]
(letsubs [inviter-name [:contacts/contact-name-by-identity inviter-pk]]
[react/nested-text {:style style/intro-header-description}
(i18n/label :t/joined-group-chat-description
{:username ""
:group-name chat-name})
[{:style {:color colors/black}} inviter-name]]))
(defn created-group-chat-description
[chat-name]
[react/text {:style style/intro-header-description}
(i18n/label :t/created-group-chat-description
{:group-name chat-name})])
(defview group-chat-inviter-description-container
[chat-id chat-name]
(letsubs [{:keys [member? inviter-pk]}
[:group-chat/inviter-info chat-id]]
(cond
(not member?)
[pending-invitation-description inviter-pk chat-name]
inviter-pk
[joined-group-chat-description inviter-pk chat-name]
:else
[created-group-chat-description chat-name])))
(defn group-chat-membership-description
[]
[react/text {:style {:text-align :center :margin-horizontal 30}}
(i18n/label :t/membership-description)])
(defn group-chat-description-container
[{:keys [invitation-admin
chat-id
chat-name
chat-type
loading-messages?
no-messages?]}]
(cond
loading-messages?
group-chat-description-loading
(and no-messages? (= chat-type constants/public-chat-type))
[no-messages-private-group-chat-description-container chat-id]
(and no-messages? (= chat-type constants/community-chat-type))
[no-messages-community-chat-description-container chat-id]
invitation-admin
[group-chat-membership-description]
(= chat-type constants/private-group-chat-type)
[group-chat-inviter-description-container chat-id chat-name]))

View File

@ -1,128 +0,0 @@
(ns status-im.ui.screens.chat.image.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [quo.components.animated.pressable :as pressable]
[quo.core :as quo]
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[i18n.i18n :as i18n]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.permissions :as permissions]
[status-im.ui.components.react :as react]
[status-im.utils.config :as config]
[status-im.utils.utils :as utils]))
(defn take-picture
[]
(permissions/request-permissions
{:permissions [:camera]
:on-allowed #(re-frame/dispatch [:chat.ui/show-image-picker-camera])
:on-denied (fn []
(utils/set-timeout
#(utils/show-popup (i18n/label :t/error)
(i18n/label :t/camera-access-error))
50))}))
(defn show-image-picker
[]
(permissions/request-permissions
{:permissions [:read-external-storage :write-external-storage]
:on-allowed #(re-frame/dispatch [:chat.ui/open-image-picker])
:on-denied (fn []
(utils/set-timeout
#(utils/show-popup (i18n/label :t/error)
(i18n/label :t/external-storage-denied))
50))}))
(defn buttons
[]
[react/view
[pressable/pressable
{:type :scale
:accessibility-label :take-picture
:on-press take-picture}
[react/view {:style {:padding 10}}
[icons/icon :main-icons/camera]]]
[react/view {:style {:padding-top 8}}
[pressable/pressable
{:on-press show-image-picker
:accessibility-label :open-gallery
:type :scale}
[react/view {:style {:padding 10}}
[icons/icon :main-icons/gallery]]]]])
(defn image-preview
[uri all-selected first? panel-height]
(let [wh (/ (- panel-height 8) 2)
selected (get all-selected uri)
max-selected (>= (count all-selected) config/max-images-batch)]
[react/touchable-highlight
{:on-press #(if selected
(re-frame/dispatch [:chat.ui/image-unselected uri])
(re-frame/dispatch [:chat.ui/camera-roll-pick uri]))
:disabled (and max-selected (not selected))}
[react/view
{:style (merge {:width wh
:height wh
:border-radius 4
:overflow :hidden}
(when (and (not selected) max-selected)
{:opacity 0.5})
(when first?
{:margin-bottom 4}))}
[react/image
{:style (merge {:width wh
:height wh
:background-color :black
:resize-mode :cover
:border-radius 4})
:source {:uri uri}}]
(when selected
[react/view
{:style {:position :absolute
:top 0
:bottom 0
:left 0
:right 0
:padding 10
:background-color (:highlight @colors/theme)
:align-items :flex-end}}
[quo/radio {:value true}]])]]))
(defview photos
[]
(letsubs [camera-roll-photos [:camera-roll/photos]
selected [:chats/sending-image]
panel-height (reagent/atom nil)]
[react/view
{:style {:flex 1
:flex-direction :row}
:on-layout #(reset! panel-height (.-nativeEvent.layout.height ^js %))}
(let [height @panel-height]
(for [[first-img second-img] (partition 2 camera-roll-photos)]
^{:key (str "image" first-img)}
[react/view {:margin-left 4}
(when first-img
[image-preview first-img selected true height])
(when second-img
[image-preview second-img selected false height])]))]))
(defview image-view
[]
{:component-did-mount
(fn []
(permissions/request-permissions
{:permissions [:read-external-storage :write-external-storage]
:on-allowed #(re-frame/dispatch [:chat.ui/camera-roll-get-photos 20])
:on-denied (fn []
(utils/set-timeout
#(utils/show-popup (i18n/label :t/error)
(i18n/label :t/external-storage-denied))
50))}))}
[react/animated-view
{:style {:background-color (:ui-background @colors/theme)
:flex 1}}
[react/scroll-view {:horizontal true :style {:flex 1}}
[react/view {:flex 1 :flex-direction :row :margin-horizontal 4}
[buttons]
[photos]]]])

View File

@ -1,272 +0,0 @@
(ns status-im.ui.screens.chat.message.audio-old
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require ["react-native-blob-util" :default ReactNativeBlobUtil]
[goog.string :as gstring]
[quo.design-system.colors :as colors]
[reagent.core :as reagent]
[status-im.audio.core :as audio]
[status-im.ui.components.animation :as anim]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]
[status-im.ui.components.slider :as slider]
[status-im.ui.screens.chat.styles.message.audio-old :as style]
[utils.re-frame :as rf]
[status-im.utils.platform :as platform]
[status-im.utils.utils :as utils]
[taoensso.timbre :as log]))
(defonce player-ref (atom nil))
(defonce current-player-message-id (atom nil))
(defonce current-active-state-ref-ref (atom nil))
(defonce progress-timer (atom nil))
(defn start-stop-progress-timer
[{:keys [state-ref progress-ref progress-anim]} start?]
(when @progress-timer
(utils/clear-interval @progress-timer)
(when-not start?
(reset! progress-timer nil)))
(when start?
(when @progress-timer
(utils/clear-interval @progress-timer))
(reset! progress-timer
(utils/set-interval
#(when (and @state-ref (not (:slider-seeking @state-ref)))
(let [ct (audio/get-player-current-time @player-ref)]
(reset! progress-ref ct)
(when ct
(anim/start (anim/timing progress-anim
{:toValue @progress-ref
:duration 100
:easing (.-linear ^js anim/easing)
:useNativeDriver true})))))
100))))
(defn update-state
[{:keys [state-ref progress-ref progress-anim message-id seek-to-ms audio-duration-ms
slider-new-state-seeking? unloaded? error]}]
(let [player-state (audio/get-state @player-ref)
slider-seeking (if (some? slider-new-state-seeking?)
slider-new-state-seeking?
(:slider-seeking @state-ref))
general (cond
(some? error) :error
(or unloaded? (not= message-id @current-player-message-id)) :not-loaded
slider-seeking (:general
@state-ref) ; persist
; player
; state
; at
; the
; time
; user
; started
; sliding
(= player-state audio/PLAYING) :playing
(= player-state audio/PAUSED) :paused
(= player-state audio/SEEKING) :seeking
(= player-state audio/PREPARED) :ready-to-play
:else :preparing)
new-state {:general general
:error-msg error
:duration (cond (not (#{:preparing :not-loaded :error} general))
(audio/get-player-duration @player-ref)
audio-duration-ms audio-duration-ms
:else (:duration @state-ref))
:progress-ref (or progress-ref (:progress-ref @state-ref))
:progress-anim (or progress-anim (:progress-anim @state-ref))
:slider-seeking slider-seeking
; persist seek-to-ms while seeking or audio is not loaded
:seek-to-ms (when (or
slider-seeking
(#{:preparing :not-loaded :error} general))
(or seek-to-ms (:seek-to-ms @state-ref)))}]
; update state if needed
(when (not= @state-ref new-state)
(reset! state-ref new-state))
; update progress UI on slider release
(when (and (some? slider-new-state-seeking?) (not slider-new-state-seeking?) (some? seek-to-ms))
(reset! (:progress-ref new-state) seek-to-ms))
; update progres anim value to follow the slider
(when (and slider-seeking (some? seek-to-ms))
(anim/set-value (:progress-anim new-state) seek-to-ms))
; on unload, reset values
(when unloaded?
(reset! (:progress-ref new-state) 0)
(anim/set-value (:progress-anim new-state) 0))))
(defn destroy-player
[{:keys [message-id reloading?]}]
(when (and @player-ref
(or reloading?
(= message-id @current-player-message-id)))
(audio/destroy-player @player-ref)
(reset! player-ref nil)
(when @current-active-state-ref-ref
(update-state {:state-ref @current-active-state-ref-ref :unloaded? true}))
(reset! current-player-message-id nil)
(reset! current-active-state-ref-ref nil)))
(defonce last-seek (atom (js/Date.now)))
(defn seek
[{:keys [message-id] :as params} value immediate? on-success]
(when (and @player-ref (= message-id @current-player-message-id))
(let [now (js/Date.now)]
(when (or immediate? (> (- now @last-seek) 200))
(reset! last-seek (js/Date.now))
(audio/seek-player
@player-ref
value
#(do
(update-state params)
(when on-success (on-success)))
#(update-state (merge params {:error (:message %)}))))))
(update-state (merge params {:seek-to-ms value})))
(defn download-audio-http
[base64-uri on-success]
(-> (.config ReactNativeBlobUtil (clj->js {:trusty platform/ios?}))
(.fetch "GET" (str base64-uri))
(.then #(on-success (.base64 ^js %)))
(.catch #(log/error "could not fetch audio"))))
(defn reload-player
[{:keys [message-id state-ref] :as params} audio-url on-success]
;; to avoid reloading player while is initializing,
;; we go ahead only if there is no player or
;; if it is already prepared
(when (or (nil? @player-ref) (audio/can-play? @player-ref))
(when @player-ref
(destroy-player (merge params {:reloading? true})))
(download-audio-http
audio-url
(fn [base64-data]
(reset! player-ref (audio/new-player
(str "data:audio/acc;base64," base64-data)
{:autoDestroy false
:continuesToPlayInBackground false}
#(seek params 0 true nil)))
(audio/prepare-player
@player-ref
#(when on-success (on-success))
#(update-state (merge params {:error (:message %)})))
(reset! current-player-message-id message-id)
(reset! current-active-state-ref-ref state-ref)
(update-state params)))))
(defn play-pause
[{:keys [message-id state-ref] :as params} audio]
(if (not= message-id @current-player-message-id)
;; player has audio from another message, we need to reload
(reload-player params
audio
;; on-success: audio is loaded, do we have an existing value to seek to?
#(if-some [seek-time (:seek-to-ms @state-ref)]
;; check seek time against real audio duration and play
(let [checked-seek-time (min (audio/get-player-duration @player-ref) seek-time)]
(seek params
checked-seek-time
true
(fn [] (play-pause params audio))))
;; nothing to seek to, play
(play-pause params audio)))
;; loaded audio corresponds to current message we can play
(when @player-ref
(audio/toggle-playpause-player
@player-ref
#(do
(start-stop-progress-timer params true)
(update-state params))
#(do
(start-stop-progress-timer params false)
(update-state params))
#(update-state (merge params {:error (:message %)}))))))
(defn- play-pause-button
[state-ref outgoing on-press]
(let [color (if outgoing colors/blue colors/white-persist)]
(if (= (:general @state-ref) :preparing)
[react/view {:style (style/play-pause-container outgoing true)}
[react/small-loading-indicator color]]
[react/touchable-highlight {:on-press on-press}
[icons/icon
(case (:general @state-ref)
:playing :main-icons/pause
:main-icons/play)
{:container-style (style/play-pause-container outgoing false)
:accessibility-label :play-pause-audio-message-button
:color color}]])))
(rf/defn on-background
{:events [:audio-message/on-background]}
[_]
(when (and @current-active-state-ref-ref
@@current-active-state-ref-ref)
(update-state {:state-ref @current-active-state-ref-ref
:message-id @current-player-message-id}))
nil)
(defview message-content
[{:keys [audio audio-duration-ms message-id outgoing]}]
(letsubs [state (reagent/atom nil)
progress (reagent/atom 0)
progress-anim (anim/create-value 0)
width [:dimensions/window-width]]
{:component-did-mount (fn []
(update-state {:state-ref state
:audio-duration-ms audio-duration-ms
:message-id message-id
:unloaded? true
:progress-ref progress
:progress-anim progress-anim}))
:component-will-unmount (fn []
(destroy-player {:state-ref state :message-id message-id})
(when (= @current-player-message-id message-id)
(reset! current-active-state-ref-ref nil)
(reset! current-player-message-id nil))
(reset! state nil))}
(let [base-params {:state-ref state
:message-id message-id
:progress-ref progress
:progress-anim progress-anim}]
(if (= (:general @state) :error)
[react/text
{:style {:typography :main-medium
:margin-bottom 16}} (:error-msg @state)]
[react/view (style/container width)
[react/view style/play-pause-slider-container
[play-pause-button state outgoing #(play-pause base-params audio)]
[react/view style/slider-container
[slider/animated-slider
(merge (style/slider outgoing)
{:minimum-value 0
:maximum-value (:duration @state)
:value progress-anim
:on-value-change #(seek base-params % false nil)
:on-sliding-start #(seek (merge base-params {:slider-new-state-seeking? true})
%
true
nil)
:on-sliding-complete #(seek (merge base-params {:slider-new-state-seeking? false})
%
true
nil)})]]]
[react/view style/times-container
[react/text {:style (style/timestamp outgoing)}
(let [time (cond
(or (:slider-seeking @state) (> (:seek-to-ms @state) 0)) (:seek-to-ms @state)
(#{:playing :paused :seeking} (:general @state)) @progress
:else (:duration @state))
s (quot time 1000)]
(gstring/format "%02d:%02d" (quot s 60) (mod s 60)))]]]))))

View File

@ -1,285 +0,0 @@
(ns status-im.ui.screens.chat.message.command
(:require [quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[status-im.commands.core :as commands]
[status-im.constants :as constants]
[status-im.ethereum.transactions.core :as transactions]
[i18n.i18n :as i18n]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react]
[status-im.utils.money :as money]
[status-im.utils.utils :as utils]))
(defn- final-status?
[command-state]
(or (= command-state constants/command-state-request-address-for-transaction-declined)
(= command-state constants/command-state-request-transaction-declined)
(= command-state constants/command-state-transaction-sent)))
(defn- command-pending-status
[command-state direction to transaction-type]
[react/view
{:style {:flex-direction :row
:height 28
:align-items :center
:border-width 1
:border-color colors/gray-lighter
:border-radius 16
:padding-horizontal 8
:margin-right 12
:margin-bottom 2}}
[icons/icon :tiny-icons/tiny-pending
{:width 16
:height 16
:color colors/gray
:container-style {:margin-right 6}}]
[react/text
{:style {:color colors/gray
:font-weight "500"
:line-height 16
:margin-right 4
:font-size 13}}
(if (and (or (= command-state constants/command-state-request-transaction)
(= command-state constants/command-state-request-address-for-transaction-accepted))
(= direction :incoming))
(str (i18n/label :t/shared)
" '"
(or (:name @(re-frame/subscribe [:account-by-address to]))
(utils/get-shortened-checksum-address to))
"'")
(i18n/label (cond
(= command-state constants/command-state-transaction-pending)
:t/status-pending
(= command-state constants/command-state-request-address-for-transaction)
:t/address-requested
(= command-state constants/command-state-request-address-for-transaction-accepted)
:t/address-request-accepted
(= command-state constants/command-state-transaction-sent)
(case transaction-type
:pending :t/status-pending
:failed :t/transaction-failed
:t/status-confirmed)
(= command-state constants/command-state-request-transaction)
:t/address-received)))]])
(defn- command-final-status
[command-state _ transaction-type]
[react/view
{:style {:flex-direction :row
:height 28
:align-items :center
:border-width 1
:border-color colors/gray-lighter
:border-radius 16
:padding-horizontal 8
:margin-right 12
:margin-bottom 2}}
(if (or (= command-state constants/command-state-request-address-for-transaction-declined)
(= command-state constants/command-state-request-transaction-declined)
(= :failed transaction-type))
[icons/icon :tiny-icons/tiny-warning-background
{:width 16
:height 16
:container-style {:margin-right 6}}]
(if (= :pending transaction-type)
[icons/icon :tiny-icons/tiny-pending
{:color colors/gray
:width 16
:height 16
:container-style {:margin-right 6}}]
[icons/icon :tiny-icons/tiny-check
{:width 16
:height 16
:container-style {:margin-right 6}}]))
[react/text
{:style (merge {:margin-right 4
:line-height 16
:font-size 13}
(if (= transaction-type :pending)
{:color colors/gray}
{:font-weight "500"}))}
(i18n/label
(if (or (= command-state constants/command-state-request-address-for-transaction-declined)
(= command-state constants/command-state-request-transaction-declined))
:t/transaction-declined
(case transaction-type
:pending :t/status-pending
:failed :t/transaction-failed
:t/status-confirmed)))]])
(defn- command-status-and-timestamp
[command-state direction to timestamp-str transaction-type]
[react/view
{:style {:flex-direction :row
:align-items :flex-end
:justify-content :space-between}}
(if (final-status? command-state)
[command-final-status command-state direction transaction-type]
[command-pending-status command-state direction to transaction-type])
[react/text
{:style {:font-size 10
:line-height 12
:text-align-vertical :bottom
:color colors/gray}}
timestamp-str]])
(defn- command-actions
[accept-label on-accept on-decline]
[react/view
[react/touchable-highlight
{:on-press #(do (react/dismiss-keyboard!)
(on-accept))
:style {:border-color colors/gray-lighter
:border-top-width 1
:margin-top 8
:margin-horizontal -12
:padding-horizontal 15
:padding-vertical 10}}
[react/text
{:style {:text-align :center
:color colors/blue
:font-weight "500"
:font-size 15
:line-height 22}}
(i18n/label accept-label)]]
(when on-decline
[react/touchable-highlight
{:on-press on-decline
:style {:border-color colors/gray-lighter
:border-top-width 1
:margin-horizontal -12
:padding-top 10}}
[react/text
{:style {:text-align :center
:color colors/blue
:font-size 15
:line-height 22}}
(i18n/label :t/decline)]])])
(defn- command-transaction-info
[contract value]
(let [{:keys [symbol symbol-display icon decimals color] :as token}
(if (seq contract)
(get @(re-frame/subscribe [:wallet/all-tokens])
contract
transactions/default-erc20-token)
@(re-frame/subscribe [:ethereum/native-currency]))
symbol (or symbol-display symbol)
amount (money/internal->formatted value symbol decimals)
{:keys [code]}
@(re-frame/subscribe [:wallet/currency])
prices @(re-frame/subscribe [:prices])
amount-fiat (money/fiat-amount-value amount symbol (keyword code) prices)]
[react/view
{:style {:flex-direction :row
:margin-top 8
:margin-bottom 12}}
(if icon
[react/image
(-> icon
(assoc-in [:style :height] 24)
(assoc-in [:style :width] 24))]
[react/view
{:style {:margin-right 14
:padding-vertical 2
:justify-content :flex-start
:max-width 40
:align-items :center
:align-self :stretch}}
[chat-icon/custom-icon-view-list (:name token) color 24]])
[react/view {:style {:margin-left 6}}
[react/text
{:style {:margin-bottom 2
:font-size 20
:line-height 24}}
(str (money/to-fixed amount) " " (name symbol))]
[react/text
{:style {:font-size 12
:line-height 16
:color colors/gray}}
(str amount-fiat " " code)]]]))
(defn calculate-direction
[outgoing command-state]
(if (#{constants/command-state-request-address-for-transaction-accepted
constants/command-state-request-address-for-transaction-declined
constants/command-state-request-transaction}
command-state)
(if outgoing :incoming :outgoing)
(if outgoing :outgoing :incoming)))
(defn command-content
[wrapper
{:keys [message-id
chat-id
outgoing
command-parameters
timestamp-str]
:as message}]
(let [{:keys [contract value address command-state transaction-hash]} command-parameters
direction (calculate-direction outgoing command-state)
transaction (when transaction-hash
@(re-frame/subscribe
[:wallet/account-by-transaction-hash
transaction-hash]))]
[wrapper (assoc message :outgoing (= direction :outgoing))
[react/touchable-highlight
{:on-press #(when (:address transaction)
(re-frame/dispatch [:wallet.ui/show-transaction-details
transaction-hash (:address transaction)]))}
[react/view
{:padding-horizontal 12
:padding-bottom 10
:padding-top 10
:margin-top 4
:border-width 1
:border-color colors/gray-lighter
:border-radius 16
(case direction
:outgoing :border-bottom-right-radius
:incoming :border-bottom-left-radius)
4
:background-color colors/white}
[react/text
{:style {:font-size 13
:line-height 18
:font-weight "500"
:color colors/gray}}
(case direction
:outgoing (str "↑ " (i18n/label :t/outgoing-transaction))
:incoming (str "↓ " (i18n/label :t/incoming-transaction)))]
[command-transaction-info contract value]
[command-status-and-timestamp
;; If :type is nil it most likely means the transaction is pending, as those
;; are not stored and will be gone after logout
command-state direction address timestamp-str (or (:type transaction) :pending)]
(when (not outgoing)
(cond
(= command-state constants/command-state-request-transaction)
[command-actions
:t/sign-and-send
#(re-frame/dispatch
[:wallet.ui/accept-request-transaction-button-clicked-from-command
chat-id
command-parameters])
#(re-frame/dispatch [::commands/decline-request-transaction message-id])]
(= command-state
constants/command-state-request-address-for-transaction-accepted)
[command-actions
:t/sign-and-send
#(re-frame/dispatch
[:wallet.ui/accept-request-transaction-button-clicked-from-command
chat-id
command-parameters])]
(= command-state constants/command-state-request-address-for-transaction)
[command-actions
:t/accept-and-share-address
#(re-frame/dispatch
[::commands/prepare-accept-request-address-for-transaction
message])
#(re-frame/dispatch
[::commands/decline-request-address-for-transaction
message-id])]))]]]))

View File

@ -1,19 +0,0 @@
(ns status-im.ui.screens.chat.message.datemark
(:require [clojure.string :as string]
[quo2.components.markdown.text :as quo2.text]
[quo2.foundations.colors :as quo2.colors]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.message.datemark :as style]))
(defn chat-datemark
[value]
[react/touchable-without-feedback
{:on-press (fn [_]
(react/dismiss-keyboard!))}
[react/view style/datemark-mobile
[quo2.text/text
{:weight :medium
:accessibility-label :message-datemark-text
:style {:color quo2.colors/neutral-50}}
(string/capitalize value)]
[react/view (style/divider)]]])

View File

@ -1,13 +0,0 @@
(ns status-im.ui.screens.chat.message.datemark-old
(:require [clojure.string :as string]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.message.datemark-old :as style]))
(defn chat-datemark
[value]
[react/touchable-without-feedback
{:on-press (fn [_]
(react/dismiss-keyboard!))}
[react/view style/datemark-mobile
[react/text {:style (style/datemark-text)}
(string/capitalize value)]]])

View File

@ -1,181 +0,0 @@
(ns status-im.ui.screens.chat.message.link-preview
(:require [clojure.string :as string]
[quo.core :as quo]
[re-frame.core :as re-frame]
[status-im.chat.models.link-preview :as link-preview]
[status-im.constants :as constants]
[i18n.i18n :as i18n]
[status-im.react-native.resources :as resources]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.message.styles :as styles]
[status-im.ui.screens.communities.icon :as communities.icon]
[utils.security.core :as security])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn link-belongs-to-domain
[link domain]
(cond
(string/starts-with? link (str "https://" domain)) true
(string/starts-with? link (str "https://www." domain)) true
:else false))
(defn community-id-from-link
[link]
(nth (re-find constants/regx-community-universal-link link) 4))
(defn domain-info-if-whitelisted
[link whitelist]
(first (filter
#(link-belongs-to-domain link (:address %))
whitelist)))
(defn link-extended-info
[link whitelist enabled-list]
(let [domain-info (domain-info-if-whitelisted link whitelist)
community-id (community-id-from-link link)]
(if-not community-id
{:whitelisted (not (nil? domain-info))
:enabled (contains? enabled-list (:title domain-info))
:link link}
{:whitelisted true
:enabled true
:link link
:community true})))
(defn previewable-link
[links whitelist enabled-list]
(->> links
(map #(link-extended-info % whitelist enabled-list))
(filter #(:whitelisted %))
(first)))
(defview link-preview-enable-request
[]
[react/view (styles/link-preview-request-wrapper)
[react/view {:margin 12}
[react/image
{:source (resources/get-theme-image :unfurl)
:style styles/link-preview-request-image}]
[quo/text
{:size :small
:align :center
:style {:margin-top 6}}
(i18n/label :t/enable-link-previews)]
[quo/text
{:size :small
:color :secondary
:align :center
:style {:margin-top 2}}
(i18n/label :t/once-enabled-share-metadata)]]
[quo/separator]
[quo/button
{:on-press #(re-frame/dispatch [:open-modal :link-preview-settings])
:type :secondary}
(i18n/label :enable)]
[quo/separator]
[quo/button
{:on-press #(re-frame/dispatch
[::link-preview/should-suggest-link-preview false])
:type :secondary}
(i18n/label :t/dont-ask)]])
(defn is-gif?
[url]
(string/ends-with? url ".gif"))
(defview link-preview-loader
[link outgoing timeline]
(letsubs [cache [:link-preview/cache]]
(let [{:keys [site title thumbnailUrl error] :as preview-data} (get cache link)]
(if (not preview-data)
(do
(re-frame/dispatch
[::link-preview/load-link-preview-data link])
nil)
(when-not error
[react/touchable-highlight
{:style (when-not (is-gif? thumbnailUrl) {:align-self :stretch})
:on-press #(when (security/safe-link? link)
(re-frame/dispatch
[:browser.ui/message-link-pressed link]))}
[react/view (styles/link-preview-wrapper outgoing timeline)
(when-not (string/blank? thumbnailUrl)
[react/image
{:source {:uri thumbnailUrl}
:style (styles/link-preview-image outgoing
(select-keys preview-data
[:height :width]))
:accessibility-label :member-photo}])
(when-not (is-gif? thumbnailUrl)
[:<>
[quo/text
{:size :small
:style styles/link-preview-title}
title]
[quo/text
{:size :small
:color :secondary
:style styles/link-preview-site}
site]])]])))))
(defview community-preview
[community outgoing timeline]
(let [{:keys [name members description verified]} community
members-count (count members)]
[react/view (styles/link-preview-wrapper outgoing timeline)
(if verified
[quo/text
{:size :small
:color :link
:style styles/community-preview-header}
(i18n/label :t/verified-community)]
[quo/text
{:size :small
:color :secondary
:style styles/community-preview-header}
(i18n/label :t/community)])
[quo/separator]
[react/view {:flex-direction :row :align-self :flex-start :margin 12}
[communities.icon/community-icon community]
[react/view {:flex 1 :flex-direction :column :margin-left 12}
[quo/text {:weight :bold :size :large} name]
[quo/text description]
[quo/text
{:size :small
:color :secondary}
(i18n/label-pluralize members-count :t/community-members {:count members-count})]]]
[quo/separator]
[quo/button
{:on-press #(re-frame/dispatch [:navigate-to
:community
{:from-chat true
:community-id (:id community)}])
:type :secondary}
(i18n/label :t/view)]]))
(defview community-preview-loader
[community-link outgoing timeline]
(letsubs [cache [:link-preview/cache]]
{:component-did-mount (fn []
(let [community (get cache community-link)
community-id (community-id-from-link community-link)]
(when-not community
(re-frame/dispatch
[::link-preview/resolve-community-info community-id]))))}
(when-let [community (get cache community-link)]
[community-preview community outgoing timeline])))
(defview link-preview-wrapper
[links outgoing timeline]
(letsubs
[ask-user? [:link-preview/link-preview-request-enabled]
whitelist [:link-previews-whitelist]
enabled-sites [:link-preview/enabled-sites]]
(when links
(let [link-info (previewable-link links whitelist enabled-sites)
{:keys [link whitelisted enabled community]} link-info
link-whitelisted (and link whitelisted)]
(cond
community [community-preview-loader link outgoing timeline]
(and link-whitelisted enabled) [link-preview-loader link outgoing timeline]
(and link-whitelisted ask-user?) [link-preview-enable-request])))))

View File

@ -1,841 +0,0 @@
(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.images :as images]
[status-im.constants :as constants]
[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.components.reply :as components.reply]
[status-im.ui.screens.chat.image.preview.views :as preview]
[status-im.ui.screens.chat.message.audio-old :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-old :as reactions]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.screens.chat.styles.message.message-old :as style]
[status-im.ui.screens.chat.utils :as chat.utils]
[status-im.ui.screens.communities.icon :as communities.icon]
[status-im.utils.config :as config]
[utils.security.core :as security])
(: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]])))
(defn quoted-message
[_ {:keys [from parsed-text image audio sticker id] :as message} outgoing current-public-key public?
pinned chat-id]
(let [contact-name [:contacts/contact-name-by-identity from]
replied-message (get @(re-frame/subscribe [:chats/chat-messages chat-id]) id)] ;; the message
;; replied to
[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))]]
(cond
(and image (not public?)) ;; Disabling images for public-chats
[fast-image/fast-image
{:style {:width 56
:height 56
:background-color :black
:border-radius 4}
:source {:uri image}}]
(and audio (not public?)) ;; Disabling audio for public-chats
[react/view {:style (style/message-view message) :accessibility-label :audio-message}
[react/view {:style (style/message-view-content)}
[react/view {:style (style/style-message-text outgoing)}
[message.audio/message-content message]
[message-status message]]]]
sticker
(if replied-message
[fast-image/fast-image
{:style {:margin 4 :width 56 :height 56}
;; Get sticker url of the message replied to
:source {:uri (((replied-message :content) :sticker) :url)}}]
;; Let the user know if the message was deleted
[react/text {:style (style/quoted-message-text (and outgoing (not pinned)))}
;; This hardcorded text can be modified to come from the parsed-text. Also, translations can be
;; added.
"This message was deleted!"])
:else [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 [^js 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 [:pin-message/show-pin-limit-modal chat-id]))
(re-frame/dispatch [: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? chat-id] :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 chat-id])
[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 edit-enabled public? pinned in-popover?
message-pin-enabled content-type edited-at]
:as message}
{:keys [on-long-press modal]
:as reaction-picker}]
;; Makes sure to render a text-message and not an emoji-message if it has been edited with text
(if (= content-type constants/content-type-text)
[message-content-wrapper message
[collapsible-text-message message on-long-press modal] 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
(when (and outgoing edit-enabled)
[{:on-press #(re-frame/dispatch [:chat.ui/edit-message message])
:label (i18n/label :t/edit)
:id :edit}])
[{: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))}])
(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}]))))})
[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)}
(if edited-at (str (content :text) " ") (str (content :text)))]]
[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
(concat
(when-not outgoing
[{:on-press #(when pack
(re-frame/dispatch [:chat.ui/show-profile from]))
:label (i18n/label :t/view-details)}])
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:label (i18n/label :t/message-reply)}]
(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}]
[]))))})
[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
(concat
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:id :reply
:label (i18n/label :t/message-reply)}]
(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}])
[])))
:on-press (fn []
(reset! show-timestamp? true))})
[react/view (style/message-view-wrapper (:outgoing message))
[message-timestamp message show-timestamp?]
[react/view {:style (style/message-view message) :accessibility-label :audio-message}
[react/view {:style (style/message-view-content)}
[message.audio/message-content message] [message-status message]]]]]
reaction-picker])))
(defn contact-request-status-pending
[]
[react/view {:style {:flex-direction :row}}
[quo/text
{:style {:margin-right 5.27}
:weight :medium
:color :secondary}
(i18n/label :t/contact-request-pending)]
[react/activity-indicator
{:animating true
:size :small
:color colors/gray}]])
(defn contact-request-status-accepted
[]
[quo/text
{:style {:color colors/green}
:weight :medium}
(i18n/label :t/contact-request-accepted)])
(defn contact-request-status-declined
[]
[quo/text
{:style {:color colors/red}
:weight :medium}
(i18n/label :t/contact-request-declined)])
(defn contact-request-status-label
[state]
[react/view {:style (style/contact-request-status-label state)}
(case state
constants/contact-request-message-state-pending [contact-request-status-pending]
constants/contact-request-message-state-accepted [contact-request-status-accepted]
constants/contact-request-message-state-declined [contact-request-status-declined])])
(defmethod ->message constants/content-type-contact-request
[{:keys [outgoing] :as message} _]
[react/view {:style (style/content-type-contact-request outgoing)}
[react/image
{:source (resources/get-image :hand-wave)
:style {:width 112
:height 97}}]
[quo/text
{:style {:margin-top 6}
:weight :bold
:size :large}
(i18n/label :t/contact-request)]
[react/view {:style {:padding-horizontal 16}}
[quo/text
{:style {:margin-top 2
:margin-bottom 14}}
(get-in message [:content :text])]]
[contact-request-status-label (:contact-request-state message)]])
(defmethod ->message :default
[message]
[message-content-wrapper message
[unknown-content-type message]])
(defn chat-message
[{:keys [outgoing display-photo? pinned pinned-by] :as message} space-keeper]
[:<>
[reactions/with-reaction-picker
{:message message
:reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message)
(:chat-id message)])
:picker-on-open (fn []
(space-keeper true))
:picker-on-close (fn []
(space-keeper false))
:send-emoji (fn [{:keys [emoji-id]}]
(re-frame/dispatch [:models.reactions/send-emoji-reaction
{:message-id (:message-id message)
:emoji-id emoji-id}]))
:retract-emoji (fn [{:keys [emoji-id emoji-reaction-id]}]
(re-frame/dispatch [:models.reactions/send-emoji-reaction-retraction
{:message-id (:message-id message)
:emoji-id emoji-id
:emoji-reaction-id emoji-reaction-id}]))
:render ->message}]
(when pinned
[react/view {:style (style/pin-indicator-container outgoing)}
[pinned-by-indicator outgoing display-photo? pinned-by]])])

View File

@ -1,101 +0,0 @@
(ns status-im.ui.screens.chat.message.pinned-message
(:require [quo.core :as quo]
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[i18n.i18n :as i18n]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.message.message :as message]
[utils.re-frame :as rf]))
(def selected-unpin (reagent/atom nil))
(defn render-pin-fn
[{:keys [message-id outgoing] :as message}
_
_
{:keys [group-chat public? current-public-key space-keeper]}]
[react/touchable-without-feedback
{:style {:width "100%"}
:on-press #(reset! selected-unpin message-id)}
[react/view
{:style {:flex-direction :row
:align-items :center
:justify-content :space-between
:flex 1
:padding-right 20}}
[message/chat-message
(assoc message
:group-chat group-chat
:public? public?
:current-public-key current-public-key
:show-input? false
:pinned false
:display-username? (not outgoing)
:display-photo? false
:last-in-group? false
:in-popover? true)
space-keeper]
[react/view
{:style {:position :absolute
:right 18
:padding-top 4}}
[quo/radio {:value (= @selected-unpin message-id)}]]]])
(def list-key-fn #(or (:message-id %) (:value %)))
(defn pinned-messages-limit-list
[chat-id]
(let [pinned-messages @(re-frame/subscribe [:chats/pinned-sorted-list chat-id])]
[list/flat-list
{:key-fn list-key-fn
:data (reverse pinned-messages)
:render-data {:chat-id chat-id}
:render-fn render-pin-fn
:on-scroll-to-index-failed identity
:style {:flex-grow 0
:border-top-width 1
:border-bottom-width 1
:border-top-color colors/gray-lighter
:border-bottom-color colors/gray-lighter}
:content-container-style {:padding-bottom 10
:padding-top 10}}]))
(defn pin-limit-popover
[]
(let [{:keys [message]} (rf/sub [:popover/popover])]
[react/view {:style {:flex-shrink 1}}
[react/view
{:style {:height 60
:justify-content :center}}
[react/text
{:style {:padding-horizontal 40
:text-align :center}}
(i18n/label :t/pin-limit-reached)]]
[pinned-messages-limit-list (message :chat-id)]
[react/view
{:flex-direction :row
:padding-horizontal 16
:height 60
:justify-content :space-between
:align-items :center}
[quo/button
{:on-press #(do
(reset! selected-unpin nil)
(re-frame/dispatch [:hide-popover]))
:type :secondary}
(i18n/label :t/cancel)]
[quo/button
{:on-press #(do
(re-frame/dispatch [:pin-message/send-pin-message
{:chat-id (message :chat-id)
:message-id @selected-unpin
:pinned false}])
(re-frame/dispatch [:pin-message/send-pin-message (assoc message :pinned true)])
(re-frame/dispatch [:hide-popover])
(reset! selected-unpin nil))
:type :secondary
:disabled (nil? @selected-unpin)
:theme (if (nil? @selected-unpin) :disabled :negative)}
(i18n/label :t/unpin)]]]))

View File

@ -1,106 +0,0 @@
(ns status-im.ui.screens.chat.message.reactions
(:require [quo.animated :as animated]
[quo.react :as react]
[quo.react-native :as rn]
[reagent.core :as reagent]
[status-im.ui.screens.chat.message.reactions-picker :as reaction-picker]
[status-im.ui.screens.chat.message.reactions-row :as reaction-row]))
(defn measure-in-window
[ref cb]
(.measureInWindow ^js ref cb))
(defn get-picker-position
[^js ref cb]
(some-> ref
react/current-ref
(measure-in-window
(fn [x y width height]
(cb {:top y
:left x
:width width
:height height})))))
(defn extract-id
[reactions id]
(->> reactions
(filter (fn [{:keys [emoji-id]}] (= emoji-id id)))
first
:emoji-reaction-id))
(defn with-reaction-picker
[]
(let [ref (react/create-ref)
animated-state (animated/value 0)
spring-animation (animated/with-spring-transition
animated-state
(:jump animated/springs))
animation (animated/with-timing-transition
animated-state
{:duration reaction-picker/animation-duration
:easing (:ease-in-out animated/easings)})
visible (reagent/atom false)
actions (reagent/atom nil)
position (reagent/atom {})]
(fn [{:keys [message reactions outgoing outgoing-status render send-emoji retract-emoji
picker-on-open
picker-on-close timeline]}]
(let [own-reactions (reduce (fn [acc {:keys [emoji-id own]}]
(if own (conj acc emoji-id) acc))
[]
reactions)
on-emoji-press (fn [emoji-id]
(let [active ((set own-reactions) emoji-id)]
(if active
(retract-emoji {:emoji-id emoji-id
:emoji-reaction-id (extract-id reactions emoji-id)})
(send-emoji {:emoji-id emoji-id}))))
on-close (fn []
(animated/set-value animated-state 0)
(js/setTimeout
(fn []
(reset! actions nil)
(reset! visible false)
(picker-on-close))))
on-open (fn [pos]
(picker-on-open)
(reset! position pos)
(reset! visible true))]
[:<>
[rn/view
{:ref ref
:collapsable false}
[render message
{:modal false
:on-long-press (fn [act]
(when (or (not outgoing)
(and outgoing (= outgoing-status :sent)))
(reset! actions act)
(get-picker-position ref on-open)))}]
[reaction-row/message-reactions message reactions timeline #(on-emoji-press %) on-open]]
(when @visible
[rn/modal
{:on-request-close on-close
:on-show (fn []
(js/requestAnimationFrame
#(animated/set-value animated-state 1)))
:transparent true}
[reaction-picker/modal
{:outgoing (:outgoing message)
:display-photo (:display-photo? message)
:animation animation
:spring spring-animation
:top (:top @position)
:message-height (:height @position)
:on-close on-close
:actions @actions
:own-reactions own-reactions
:timeline timeline
:send-emoji (fn [emoji]
(on-close)
(js/setTimeout #(on-emoji-press emoji)
reaction-picker/animation-duration))}
[render message
{:modal true
:on-long-press #()
:close-modal on-close}]]])]))))

View File

@ -1,106 +0,0 @@
(ns status-im.ui.screens.chat.message.reactions-old
(:require [quo.animated :as animated]
[quo.react :as react]
[quo.react-native :as rn]
[reagent.core :as reagent]
[status-im.ui.screens.chat.message.reactions-picker :as reaction-picker]
[status-im.ui.screens.chat.message.reactions-row-old :as reaction-row]))
(defn measure-in-window
[ref cb]
(.measureInWindow ^js ref cb))
(defn get-picker-position
[^js ref cb]
(some-> ref
react/current-ref
(measure-in-window
(fn [x y width height]
(cb {:top y
:left x
:width width
:height height})))))
(defn- extract-id
[reactions id]
(->> reactions
(filter (fn [{:keys [emoji-id]}] (= emoji-id id)))
first
:emoji-reaction-id))
(defn with-reaction-picker
[]
(let [ref (react/create-ref)
animated-state (animated/value 0)
spring-animation (animated/with-spring-transition
animated-state
(:jump animated/springs))
animation (animated/with-timing-transition
animated-state
{:duration reaction-picker/animation-duration
:easing (:ease-in-out animated/easings)})
visible (reagent/atom false)
actions (reagent/atom nil)
position (reagent/atom {})]
(fn [{:keys [message reactions outgoing outgoing-status render send-emoji retract-emoji
picker-on-open
picker-on-close timeline]}]
(let [own-reactions (reduce (fn [acc {:keys [emoji-id own]}]
(if own (conj acc emoji-id) acc))
[]
reactions)
on-emoji-press (fn [emoji-id]
(let [active ((set own-reactions) emoji-id)]
(if active
(retract-emoji {:emoji-id emoji-id
:emoji-reaction-id (extract-id reactions emoji-id)})
(send-emoji {:emoji-id emoji-id}))))
on-close (fn []
(animated/set-value animated-state 0)
(js/setTimeout
(fn []
(reset! actions nil)
(reset! visible false)
(picker-on-close))))
on-open (fn [pos]
(picker-on-open)
(reset! position pos)
(reset! visible true))]
[:<>
[rn/view
{:ref ref
:collapsable false}
[render message
{:modal false
:on-long-press (fn [act]
(when (or (not outgoing)
(and outgoing (= outgoing-status :sent)))
(reset! actions act)
(get-picker-position ref on-open)))}]
[reaction-row/message-reactions message reactions timeline]]
(when @visible
[rn/modal
{:on-request-close on-close
:on-show (fn []
(js/requestAnimationFrame
#(animated/set-value animated-state 1)))
:transparent true}
[reaction-picker/modal
{:outgoing (:outgoing message)
:display-photo (:display-photo? message)
:animation animation
:spring spring-animation
:top (:top @position)
:message-height (:height @position)
:on-close on-close
:actions @actions
:own-reactions own-reactions
:timeline timeline
:send-emoji (fn [emoji]
(on-close)
(js/setTimeout #(on-emoji-press emoji)
reaction-picker/animation-duration))}
[render message
{:modal true
:on-long-press #()
:close-modal on-close}]]])]))))

View File

@ -1,137 +0,0 @@
(ns status-im.ui.screens.chat.message.reactions-picker
(:require [cljs-bean.core :as bean]
[quo.animated :as animated]
[quo.components.safe-area :as safe-area]
[quo.core :as quo]
[quo.react :as react]
[quo.react-native :as rn]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.screens.chat.message.styles :as styles]))
(def tabbar-height 36)
(def text-input-height 54)
(def animation-duration 150)
(def scale 0.8)
(def translate-x 27)
(def translate-y -24)
(def id-icon
{"edit" :main-icons/edit
"pin" :main-icons/pin
"unpin" :main-icons/pin
"copy" :main-icons/copy
"reply" :main-icons/reply
"save" :main-icons/download
"share" :main-icons/share-default
"delete" :main-icons/delete})
(defn picker
[{:keys [outgoing actions own-reactions on-close send-emoji timeline]}]
[rn/view {:style (styles/container-style {:outgoing outgoing :timeline timeline})}
[rn/view {:style (styles/reactions-picker-row)}
(doall
(for [[id resource] constants/reactions-old
:let [active (own-reactions id)]]
^{:key id}
[rn/touchable-opacity
{:accessibility-label (str "pick-emoji-" id)
:on-press #(send-emoji id)}
[rn/view {:style (styles/reaction-button active)}
[rn/image
{:source resource
:style {:height 32
:width 32}}]]]))]
(when (seq actions)
[rn/view {:style (styles/quick-actions-container)}
(doall
(for [action actions
:let [{:keys [id label on-press]} (bean/bean action)]]
^{:key id}
[rn/touchable-opacity
{:on-press (fn []
(on-close)
(js/setTimeout on-press animation-duration))}
[rn/view {:style (styles/quick-actions-row)}
[quo/text
{:color (if (= id "delete") :negative :link)
:weight :medium} label]
(when-let [icon (get id-icon id)]
[icons/icon icon
{:color (if (= id "delete") :red :blue)}])]]))])])
(def modal
(reagent/adapt-react-class
(fn [props]
(let [{outgoing :outgoing
animation :animation
spring :spring
top :top
message-height :messageHeight
display-photo :displayPhoto
on-close :onClose
actions :actions
send-emoji :sendEmoji
own-reactions :ownReactions
children :children
timeline :timeline}
(bean/bean props)
{bottom-inset :bottom} (safe-area/use-safe-area)
{window-height :height} (rn/use-window-dimensions)
{picker-height :height
on-picker-layout :on-layout}
(rn/use-layout)
full-height (+ message-height picker-height top)
max-height (- window-height bottom-inset tabbar-height text-input-height)
top-delta (max 0 (- full-height max-height))
translation-x (if (and outgoing (not timeline))
translate-x
(* -1 translate-x))]
(reagent/as-element
[:<>
[rn/view
{:style {:position :absolute
:flex 1
:top 0
:bottom 0
:left 0
:right 0}}
[rn/touchable-without-feedback
{:on-press on-close}
[animated/view
{:style {:flex 1
:opacity animation
:background-color "rgba(0,0,0,0.5)"}}]]]
[animated/view
{:pointer-events :box-none
:style {:top (- top top-delta)
:left 0
:right 0
:position :absolute
:opacity animation
:transform [{:translateY (animated/mix animation top-delta 0)}]}}
(into [:<>] (react/get-children children))
[animated/view
{:on-layout on-picker-layout
:pointer-events :box-none
:style (merge (styles/picker-wrapper-style
{:display-photo? display-photo
:timeline timeline
:outgoing (and outgoing (not timeline))})
{:opacity animation
:transform [{:translateX (animated/mix spring translation-x 0)}
{:translateY (animated/mix spring translate-y 0)}
{:scale (animated/mix spring scale 1)}]})}
[picker
{:outgoing (and outgoing (not timeline))
:timeline timeline
:actions actions
:on-close on-close
:own-reactions (into #{} (js->clj own-reactions))
:send-emoji send-emoji
:animation animation}]]]])))))

View File

@ -1,28 +0,0 @@
(ns status-im.ui.screens.chat.message.reactions-row-old
(:require [quo.core :as quo]
[quo.react-native :as rn]
[status-im.constants :as constants]
[status-im.ui.screens.chat.message.styles :as styles]))
(defn reaction
[{:keys [outgoing]} {:keys [own emoji-id quantity]} timeline]
[rn/view
{:style (styles/reaction-style {:outgoing (and outgoing (not timeline))
:own own})}
[rn/image
{:source (get constants/reactions-old emoji-id)
:style {:width 16
:height 16
:margin-right 4}}]
[quo/text
{:accessibility-label (str "emoji-" emoji-id "-is-own-" own)
:style (styles/reaction-quantity-style {:own own})}
quantity]])
(defn message-reactions
[message reactions timeline]
(when (seq reactions)
[rn/view {:style (styles/reactions-row-old message timeline)}
(for [emoji-reaction reactions]
^{:key (str emoji-reaction)}
[reaction message emoji-reaction timeline])]))

View File

@ -3,20 +3,6 @@
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.photos :as photos]))
(defn picker-wrapper-style
[{:keys [display-photo? outgoing timeline]}]
(merge {:flex-direction :row
:flex 1
:padding-top 4
:padding-right 8}
(if outgoing
{:justify-content :flex-end}
{:justify-content :flex-start})
(when-not timeline
(if display-photo?
{:padding-left (+ 16 photos/default-size)}
{:padding-left 8}))))
(defn container-style
[{:keys [outgoing timeline]}]
(merge {:border-top-left-radius 16
@ -31,72 +17,11 @@
{:border-top-right-radius 4}
{:border-top-left-radius 4}))))
(defn reactions-picker-row
[]
{:flex-direction :row
:padding-vertical 8
:padding-horizontal 8})
(defn quick-actions-container
[]
{:flex-direction :column
:justify-content :space-evenly
:border-top-width 1
:border-top-color (:ui-01 @colors/theme)})
(defn quick-actions-row
[]
{:flex-direction :row
:padding-horizontal 16
:padding-vertical 12
:justify-content :space-between
:border-top-width 1
:border-top-color (:ui-01 @colors/theme)})
(defn reaction-style
[{:keys [outgoing own]}]
(merge {:border-top-left-radius 10
:border-top-right-radius 10
:border-bottom-right-radius 10
:border-bottom-left-radius 10
:flex-direction :row
:margin-vertical 2
:padding-right 8
:padding-left 2
:padding-vertical 2}
(if own
{:background-color (:interactive-01 @colors/theme)}
{:background-color (:interactive-02 @colors/theme)})
(if outgoing
{:border-top-right-radius 2
:margin-left 4}
{:border-top-left-radius 2
:margin-right 4})))
(defn reaction-quantity-style
[{:keys [own]}]
{:font-size 12
:line-height 16
:color (if own
colors/white
(:text-01 @colors/theme))})
(def screen-width
(-> "window"
react/get-dimensions
:width))
(defn reactions-row-old
[{:keys [outgoing display-photo?]} timeline]
(merge {:flex-direction :row
:padding-right 8}
(if (and outgoing (not timeline))
{:justify-content :flex-end}
{:justify-content :flex-start})
(if (or display-photo? timeline)
{:padding-left (+ 30 photos/default-size (when timeline 8))}
{:padding-left 30})))
(defn reactions-row
[timeline margin-top]
{:flex-direction :row
@ -108,49 +33,9 @@
:max-width (- screen-width (+ 30 photos/default-size (when timeline 8)))
:margin-left (+ 30 photos/default-size (when timeline 8))})
(defn reaction-button
[active]
(merge {:width 40
:height 40
:border-radius 20
:justify-content :center
:align-items :center
:margin-horizontal 1
:border-width 1
:border-color :transparent}
(when active
{:background-color (:interactive-02 @colors/theme)
;; FIXME: Use broder color here
:border-color "rgba(67, 96, 223, 0.2)"})))
(defn link-preview-request-wrapper
[]
{:border-radius 16
:border-width 1
:border-color colors/gray-lighter
:margin-vertical 4
:background-color (:ui-background @colors/theme)})
(def link-preview-request-image
{:width 132
:height 94
:align-self :center})
(def community-preview-header
{:margin 8 :margin-left 12})
(defn link-preview-wrapper
[outgoing timeline]
{:overflow :hidden
:border-top-left-radius 16
:border-top-right-radius 16
:border-bottom-left-radius (if timeline 16 (if outgoing 16 4))
:border-bottom-right-radius (if timeline 16 (if outgoing 4 16))
:border-width 1
:border-color colors/gray-lighter
:background-color colors/white
:margin-vertical 4})
(defn scale-dimensions
"Scale a given height and width to be maximum percentage allowed of the screen width"
[{:keys [height width] :as dimensions}]
@ -177,8 +62,3 @@
(def link-preview-title
{:margin-horizontal 12
:margin-top 10})
(def link-preview-site
{:margin-horizontal 12
:margin-top 2
:margin-bottom 10})

View File

@ -40,13 +40,3 @@
[photo path
{:size style/default-size
:accessibility-label :own-account-photo}]))
(defn member-identicon
[identicon]
(let [size style/default-size]
[react/view {:style (style/photo-container size)}
[fast-image/fast-image
{:source {:uri identicon}
:style (style/photo size)
:accessibility-label :member-photo}]
[react/view {:style (style/photo-border size)}]]))

View File

@ -1,133 +0,0 @@
(ns status-im.ui.screens.chat.pinned-messages
(:require [quo.animated :as animated]
[quo.react :as quo.react]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[i18n.i18n :as i18n]
[status-im.ui.components.connectivity.view :as connectivity]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.chat.components.accessory :as accessory]
[status-im.ui.screens.chat.message.message :as message]
[status-im.ui.screens.chat.styles.main :as style]
[status-im.ui.screens.chat.views :as chat]
[utils.datetime :as datetime]
[status-im.utils.platform :as platform]))
(defn pins-topbar
[chat]
(let [{:keys [group-chat chat-id chat-name]} chat
pinned-messages @(re-frame/subscribe [:chats/pinned chat-id])
[first-name _] (when-not group-chat
@(re-frame.core/subscribe
[:contacts/contact-two-names-by-identity chat-id]))]
[topbar/topbar
{:show-border? true
:title (if group-chat chat-name first-name)
:subtitle (if (= (count pinned-messages) 0)
(i18n/label :t/no-pinned-messages)
(i18n/label-pluralize (count pinned-messages) :t/pinned-messages-count))}]))
(defn get-space-keeper-ios
[bottom-space panel-space active-panel text-input-ref]
(fn [state]
;; NOTE: Only iOS now because we use soft input resize screen on android
(when platform/ios?
(cond
(and state
(< @bottom-space @panel-space)
(not @active-panel))
(reset! bottom-space @panel-space)
(and (not state)
(< @panel-space @bottom-space))
(do
(some-> ^js (quo.react/current-ref text-input-ref)
.focus)
(reset! panel-space @bottom-space)
(reset! bottom-space 0))))))
(defn pinned-messages-empty
[]
[react/view
{:style {:flex 1
:align-items :center
:justify-content :center}}
[react/text {:style style/intro-header-description}
(i18n/label :t/pinned-messages-empty)]])
(defonce messages-list-ref (atom nil))
(def list-ref #(reset! messages-list-ref %))
(def list-key-fn #(or (:message-id %) (:value %)))
(defn render-fn
[{:keys [outgoing whisper-timestamp] :as message}
_
_
{:keys [group-chat public? current-public-key space-keeper show-input? message-pin-enabled
edit-enabled in-pinned-view?]}]
[react/view {:style (when (and platform/android? (not in-pinned-view?)) {:scaleY -1})}
[message/chat-message
(assoc message
:incoming-group (and group-chat (not outgoing))
:group-chat group-chat
:public? public?
:current-public-key current-public-key
:show-input? show-input?
:message-pin-enabled message-pin-enabled
:edit-enabled edit-enabled
:display-username? (not outgoing)
:pinned true
:timestamp-str (datetime/timestamp->time whisper-timestamp))
space-keeper]])
(defn pinned-messages-view
[{:keys [chat pan-responder space-keeper]}]
(let [{:keys [group-chat chat-id public? community-id admins]} chat
pinned-messages @(re-frame/subscribe
[:chats/pinned-sorted-list chat-id])]
(if (= (count pinned-messages) 0)
[pinned-messages-empty]
;;do not use anonymous functions for handlers
[list/flat-list
(merge
pan-responder
{:key-fn list-key-fn
:ref list-ref
:data (reverse pinned-messages)
:render-data (chat/get-render-data {:group-chat group-chat
:chat-id chat-id
:public? public?
:community-id community-id
:admins admins
:space-keeper space-keeper
:show-input? false
:edit-enabled false
:in-pinned-view? true})
:render-fn render-fn
:content-container-style {:padding-top 16
:padding-bottom 16}})])))
(defn pinned-messages
[]
(let [{:keys [chat-id]} @(re-frame/subscribe [:get-screen-params])]
(fn []
(let [bottom-space (reagent/atom 0)
panel-space (reagent/atom 52)
active-panel (reagent/atom nil)
position-y (animated/value 0)
pan-state (animated/value 0)
text-input-ref (quo.react/create-ref)
pan-responder (accessory/create-pan-responder position-y pan-state)
space-keeper (get-space-keeper-ios bottom-space panel-space active-panel text-input-ref)
chat @(re-frame/subscribe [:chat-by-id chat-id])]
[:<>
[pins-topbar chat]
[connectivity/loading-indicator]
[pinned-messages-view
{:chat chat
:pan-responder pan-responder
:space-keeper space-keeper}]]))))

View File

@ -3,7 +3,6 @@
[re-frame.core :as re-frame]
[status-im.constants :as constants]
[i18n.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.react :as react]
@ -191,52 +190,3 @@
:accessibility-label :delete-transaccent-button
:on-press #(hide-sheet-and-dispatch [:chat.ui/delete-message-not-used-any-more chat-id
message-id])}]]))
(defn image-long-press
[{:keys [content identicon from outgoing cant-be-replied] :as message} hide]
(let [contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from])]
[react/view
(when-not outgoing
[quo/list-item
{:theme :accent
:icon [chat-icon/contact-icon-contacts-tab
(multiaccounts/displayed-photo {:identicon identicon
:public-key from})]
:title contact-name
:subtitle (i18n/label :t/view-profile)
:accessibility-label :view-chat-details-button
:chevron true
:on-press #(do
(hide)
(re-frame/dispatch [:chat.ui/show-profile from]))}])
(when-not cant-be-replied
[quo/list-item
{:theme :accent
:title (i18n/label :t/message-reply)
:icon :main-icons/reply
:on-press #(do
(hide)
(re-frame/dispatch [:chat.ui/reply-to-message message]))}])
;; we have only base64 string for image, so we need to find a way how to copy it
#_[quo/list-item
{:theme :accent
:title :t/sharing-copy-to-clipboard
:icon :main-icons/copy
:on-press (fn []
(re-frame/dispatch [:bottom-sheet/hide])
(react/copy-to-clipboard (:image content)))}]
[quo/list-item
{:theme :accent
:title (i18n/label :t/save)
:icon :main-icons/download
:on-press (fn []
(hide)
(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)]))}]
;; we have only base64 string for image, so we need to find a way how to share it
#_[quo/list-item
{:theme :accent
:title :t/sharing-share
:icon :main-icons/share
:on-press (fn []
(re-frame/dispatch [:bottom-sheet/hide])
(list-selection/open-share {:message (:image content)}))}]]))

View File

@ -1,17 +1,11 @@
(ns status-im.ui.screens.chat.styles.main
(:require [quo.design-system.colors :as colors]
[status-im.ui.components.emoji-thumbnail.utils :as emoji-utils]))
(:require [quo.design-system.colors :as colors]))
(def toolbar-container
{:flex 1
:align-items :center
:flex-direction :row})
(def pins-name-view
{:flex 1
:justify-content :center
:align-items :center})
(def chat-name-view
{:flex 1
:justify-content :center})
@ -48,57 +42,6 @@
:padding-vertical 50
:margin-right 6})
(defn intro-header-container
[loading-messages? no-messages?]
(if (or loading-messages? no-messages?)
{:flex 1
:flex-direction :column
:justify-content :center
:align-items :center}
{:flex 1
:flex-direction :column
:justify-content :center
:align-items :center}))
(defn intro-header-icon
[diameter color]
{:width diameter
:height diameter
:align-items :center
:justify-content :center
:border-radius (/ diameter 2)
:background-color color
:border-width 0.5
:border-color "rgba(0,0,0,0.1)"})
(def intro-header-icon-text
{:color colors/white
:font-size 52
:font-weight "700"
:opacity 0.8
:line-height 72})
(defn emoji-intro-header-icon-text
[size]
{:font-size (emoji-utils/emoji-font-size size)
:margin-top (emoji-utils/emoji-top-margin-for-vertical-alignment size)}) ;; Required for vertical alignment bug - Check function defination for more info
(defn intro-header-chat-name
[]
{:font-size 22
:font-weight "700"
:line-height 28
:text-align :center
:margin-bottom 8
:margin-horizontal 32
:color colors/black})
(def intro-header-description-container
{:flex-wrap :wrap
:align-items :flex-start
:flex-direction :row
:margin-horizontal 32})
(def loading-text
{:color colors/gray
:font-size 15
@ -107,51 +50,6 @@
:margin-right 4
:text-align :center})
(def intro-header-description
{:color colors/gray
:line-height 22
:text-align :center
:margin-horizontal 32})
(def group-chat-join-footer
{:flex 1
:justify-content :center})
(def group-chat-join-container
{:flex 1
:padding-bottom 40
:align-items :center
:justify-content :center})
(def are-you-friends-bubble
{:border-radius 8
:border-width 1
:margin-top 4
:border-color colors/gray-lighter
:align-self :flex-start
:padding-vertical 12
:margin-horizontal 8
:padding-horizontal 16
:margin-bottom 50})
(def are-you-friends-text
{:line-height 22
:text-align :center
:font-size 15
:color colors/gray})
(def share-my-profile
{:color colors/blue
:text-align :center
:margin-top 11
:line-height 22
:font-size 15})
(def tribute-received-note
{:font-size 13
:line-height 18
:text-align :center})
(def contact-request
{:width "100%"
:justify-content :center

View File

@ -1,51 +0,0 @@
(ns status-im.ui.screens.chat.styles.message.audio-old
(:require [quo.design-system.colors :as colors]
[status-im.ui.screens.chat.styles.message.message-old :as message.style]
[status-im.utils.platform :as platform]))
(defn container
[window-width]
{:width (* window-width 0.60)
:flex-direction :column
:justify-content :space-between})
(def play-pause-slider-container
{:flex-direction :row
:align-items :center})
(def slider-container
{:flex-direction :column
:align-items :stretch
:flex-grow 1})
(defn slider
[outgoing]
{:style (merge {:margin-left 12
:height 34}
(when platform/ios? {:margin-bottom 4}))
:thumb-tint-color (if outgoing
colors/white
colors/blue)
:minimum-track-tint-color (if outgoing
colors/white
colors/blue)
:maximum-track-tint-color (if outgoing
colors/white-transparent
colors/gray-transparent-40)})
(defn play-pause-container
[outgoing? loading?]
{:background-color (if outgoing? colors/white-persist colors/blue)
:width 28
:height 28
:padding (if loading? 4 2)
:border-radius 15})
(def times-container
{:flex-direction :row
:justify-content :space-between})
(defn timestamp
[outgoing]
(merge (message.style/audio-message-timestamp-text outgoing)
{:margin-left 40}))

View File

@ -1,24 +0,0 @@
(ns status-im.ui.screens.chat.styles.message.datemark
(:require [quo2.foundations.colors :as quo2.colors]))
(def datemark-mobile
{:flex 1
:justify-content :center
:margin-vertical 16
:padding-left 65})
(defn divider
[]
{:flex 1
:width "100%"
:height 1
:padding-left 53
:background-color (quo2.colors/theme-colors quo2.colors/divider-light quo2.colors/divider-dark)
:margin-top 5})
(defn datemark-text
[]
{:color quo2.colors/neutral-50
:font-size 14
:line-height 16
:font-weight "500"})

View File

@ -1,12 +0,0 @@
(ns status-im.ui.screens.chat.styles.message.datemark-old
(:require [quo.design-system.colors :as colors]))
(def datemark-mobile
{:flex 1
:align-items :center
:margin-top 16
:height 22})
(defn datemark-text
[]
{:color colors/gray})

View File

@ -1,536 +0,0 @@
(ns status-im.ui.screens.chat.styles.message.message-old
(:require [quo.design-system.colors :as colors]
[status-im.constants :as constants]
[status-im.ui.components.react :as react]
[status-im.ui.components.typography :as typography]
[status-im.ui.screens.chat.styles.photos :as photos]))
(defn style-message-text
[outgoing]
{:color (if outgoing colors/white-persist colors/text)})
(defn system-message-body
[_]
{:margin-top 4
:margin-left 8
:margin-right 8
:align-self :center
:align-items :center})
(defn message-body
[{:keys [outgoing]}]
(let [align (if outgoing :flex-end :flex-start)
direction (if outgoing :row-reverse :row)]
{:flex-direction direction
:margin-top 4
:align-self align
:align-items align}))
(def message-timestamp
{:font-size 10})
(defn message-status-placeholder
[]
(merge message-timestamp {:opacity 0 :color "rgba(0,0,0,0)"}))
(defn message-timestamp-wrapper
[{:keys [last-in-group? outgoing group-chat]}]
{:justify-content :center
(if outgoing :margin-right :margin-left) 12 ;; horizontal margin is only required at the adjust side
;; of the message.
:margin-top (if (and last-in-group?
(or outgoing
(not group-chat)))
16
0) ;; Add gap between message groups
:opacity 0})
(defn message-timestamp-text
[]
(merge message-timestamp
{:color colors/gray
:text-align :center}))
(defn message-status-text
[outgoing]
{:font-size 10
:line-height 10
:color (if outgoing
colors/white-transparent-70-persist
colors/gray)})
(defn audio-message-timestamp-text
[outgoing]
(merge message-timestamp
{:line-height 10
:color (if outgoing
colors/white-transparent-70-persist
colors/gray)}))
(defn message-wrapper
[{:keys [outgoing in-popover?]}]
(if (and outgoing (not in-popover?))
{:margin-left 96}
{:margin-right 96}))
(defn message-author-wrapper
[outgoing display-photo? in-popover?]
(let [align (if (and outgoing (not in-popover?)) :flex-end :flex-start)]
(merge {:flex-direction :column
:flex-shrink 1
:align-items align}
(if (and outgoing (not in-popover?))
{:margin-right 8}
(when-not display-photo?
{:margin-left 8})))))
(defn delivery-status
[outgoing]
(if outgoing
{:align-self :flex-end
:padding-right 8}
{:align-self :flex-start
:padding-left 8}))
(defn pin-indicator
[outgoing display-photo?]
(merge
{:flex-direction :row
:border-top-left-radius (if outgoing 12 4)
:border-top-right-radius (if outgoing 4 12)
:border-bottom-left-radius 12
:border-bottom-right-radius 12
:padding-left 8
:padding-right 10
:padding-vertical 5
:background-color colors/gray-lighter
:justify-content :center
:max-width "80%"}
(if outgoing
{:align-self :flex-end
:align-items :flex-end}
{:align-self :flex-start
:align-items :flex-start})
(when display-photo?
{:margin-left 44})))
(defn pin-indicator-container
[outgoing]
(merge
{:margin-top 2
:align-items :center
:justify-content :center}
(if outgoing
{:align-self :flex-end
:align-items :flex-end
:padding-right 8}
{:align-self :flex-start
:align-items :flex-start
:padding-left 8})))
(defn pinned-by-text-icon-container
[]
{:flex-direction :row
:align-items :flex-start
:top 5
:left 8
:position :absolute})
(defn pin-icon-container
[]
{:flex-direction :row
:margin-top 1})
(defn pin-author-text
[]
{:margin-left 2
:margin-right 12
:padding-right 0
:left 12
:flex-direction :row
:flex-shrink 1
:align-self :flex-start
:overflow :hidden})
(defn pinned-by-text
[]
{:margin-left 5})
(def message-author-touchable
{:margin-left 12
:flex-direction :row})
(defn message-author-userpic
[outgoing]
(merge
{:width (+ 16 photos/default-size) ;; 16 is for the padding
:align-self :flex-end}
(if outgoing
{:padding-left 8}
{:padding-horizontal 8
:padding-right 8})))
(def delivery-text
{:color colors/gray
:margin-top 2
:font-size 12})
(def not-sent-view
{:flex-direction :row
:margin-bottom 2
:padding-top 2})
(def not-sent-text
(assoc delivery-text
:color colors/red
:text-align :right
:padding-top 4))
(def not-sent-icon
{:padding-top 3
:padding-left 3})
(defn emoji-message
[{:keys [incoming-group outgoing]}]
{:font-size 28
:line-height 34 ;TODO: Smaller crops the icon on the top
:margin-right (if outgoing 18 0) ;; Margin to display outgoing message status
:margin-top (if incoming-group 4 0)})
(defn collapse-button
[]
{:height 24
:width 24
:background-color colors/blue
:border-radius 12
:align-items :center
:justify-content :center
:elevation 4
:shadow-opacity 1
:shadow-radius 16
:shadow-color (:shadow-01 @colors/theme)
:shadow-offset {:width 0 :height 4}})
(defn message-view-wrapper
[outgoing]
{:align-self :flex-end
:flex-direction (if outgoing :row :row-reverse)})
(defn message-view
[{:keys [content-type outgoing group-chat last-in-group? mentioned pinned]}]
(merge
{:border-top-left-radius 16
:border-top-right-radius 16
:border-bottom-right-radius 16
:border-bottom-left-radius 16
:padding-top 6
:padding-horizontal 12
:border-radius 8
:margin-top (if (and last-in-group?
(or outgoing
(not group-chat)))
16
0)}
(if outgoing
{:border-bottom-right-radius 4}
{:border-bottom-left-radius 4})
(cond
pinned {:background-color colors/pin-background}
(= content-type constants/content-type-system-text) nil
outgoing {:background-color colors/blue}
mentioned {:background-color colors/mentioned-background
:border-color colors/mentioned-border
:border-width 1}
:else {:background-color colors/blue-light})
(when (= content-type constants/content-type-emoji)
{:flex-direction :row})))
(defn message-view-content
[]
{:padding-bottom 6
:overflow :hidden})
(def status-container
{:padding-horizontal 5})
(defn status-text
[]
{:margin-top 9
:font-size 14
:color colors/gray})
(defn message-author-name
[chosen?]
{:font-size (if chosen? 13 12)
:font-weight (if chosen? "500" "400")
:padding-top 6
:padding-left 12
:text-align-vertical :center})
(defn quoted-message-container
[outgoing]
{:margin-bottom 6
:padding-bottom 6
:border-bottom-color (if outgoing
colors/white-transparent-10
colors/black-transparent)
:border-bottom-width 2
:border-bottom-left-radius 2
:border-bottom-right-radius 2})
(def quoted-message-author-container
{:flex-direction :row
:align-items :center
:justify-content :flex-start})
(defn quoted-message-author
[outgoing chosen?]
(assoc (message-author-name chosen?)
:padding-bottom 6
:padding-top 0
:padding-left 0
:line-height 18
:font-weight "500"
:color (if outgoing
colors/white-transparent-70-persist
colors/gray)))
(defn quoted-message-text
[outgoing]
{:font-size 14
:color (if outgoing
colors/white-transparent-70-persist
colors/gray)})
;; Markdown styles
(defn default-text-style
[]
{:max-font-size-multiplier react/max-font-size-multiplier
:style (assoc (typography/default-style)
:line-height
22)})
(defn outgoing-text-style
[]
(update (default-text-style)
:style
assoc
:color colors/white-persist))
(defn system-text-style
[]
(update (default-text-style)
:style assoc
:color colors/gray
:line-height 20
:font-size 14
:text-align :center
:font-weight "400"))
(defn text-style
[outgoing content-type in-popover?]
(merge
(when in-popover? {:number-of-lines 2})
(cond
(= content-type constants/content-type-system-text) (system-text-style)
outgoing (outgoing-text-style)
:else (default-text-style))))
(defn emph-text-style
[]
(update (default-text-style)
:style
assoc
:font-style :italic))
(defn outgoing-emph-text-style
[]
(update (emph-text-style)
:style
assoc
:color colors/white-persist))
(defn emph-style
[outgoing]
(if outgoing
(outgoing-emph-text-style)
(emph-text-style)))
(defn strong-text-style
[]
(update (default-text-style)
:style
assoc
:font-weight "700"))
(defn outgoing-strong-text-style
[]
(update (strong-text-style)
:style
assoc
:color colors/white-persist))
(defn strong-style
[outgoing]
(if outgoing
(outgoing-strong-text-style)
(strong-text-style)))
(defn strong-emph-style
[outgoing]
(update (strong-style outgoing)
:style
assoc
:font-style :italic))
(defn strikethrough-style
[outgoing]
(cond-> (update (default-text-style)
:style
assoc
:text-decoration-line :line-through)
outgoing
(update :style assoc :color colors/white-persist)))
(def code-block-background "#2E386B")
(defn inline-code-style
[]
{:color colors/white-persist
:background-color code-block-background})
(def codeblock-style
{:padding 10
:background-color code-block-background
:border-radius 4})
(def codeblock-text-style
{:color colors/white-persist})
(defn default-blockquote-style
[]
{:style {:border-left-width 2
:padding-left 3
:border-left-color colors/gray-transparent-40}})
(defn outgoing-blockquote-style
[]
(update (default-blockquote-style)
:style
assoc
:border-left-color colors/white-transparent-70-persist))
(defn blockquote-style
[outgoing]
(if outgoing
(outgoing-blockquote-style)
(default-blockquote-style)))
(defn default-blockquote-text-style
[]
(update (default-text-style)
:style
assoc
:line-height 19
:font-size 14
:color colors/black-transparent-50))
(defn outgoing-blockquote-text-style
[]
(update (default-blockquote-text-style)
:style
assoc
:color colors/white-transparent-70-persist))
(defn blockquote-text-style
[outgoing]
(if outgoing
(outgoing-blockquote-text-style)
(default-blockquote-text-style)))
(defn image-message
[{:keys [outgoing width height]}]
{:overflow :hidden
:border-top-left-radius 16
:border-top-right-radius 16
:border-bottom-left-radius (if outgoing 16 4)
:border-bottom-right-radius (if outgoing 4 16)
:width width
:height height})
(defn image-message-border
[opts]
(merge (image-message opts)
{:opacity (:opacity opts)
:border-width 1
:top 0
:left 0
:position :absolute
:background-color :transparent
:border-color colors/black-transparent}))
(defn community-verified
[]
{:border-right-width 1
:border-left-width 1
:border-top-width 1
:border-left-color colors/gray-lighter
:border-right-color colors/gray-lighter
:border-top-left-radius 10
:border-top-right-radius 10
:padding-vertical 8
:padding-horizontal 15
:border-top-color colors/gray-lighter})
(defn community-message
[verified]
{:flex-direction :row
:padding-vertical 12
:border-top-left-radius (when-not verified 10)
:border-top-right-radius (when-not verified 10)
:border-right-width 1
:border-left-width 1
:border-top-width 1
:border-color colors/gray-lighter})
(defn community-view-button
[]
{:border-width 1
:padding-vertical 8
:border-bottom-left-radius 10
:border-bottom-right-radius 10
:border-color colors/gray-lighter})
(defn contact-request-status-label
[state]
{:width 136
:border-radius 8
:flex 1
:justify-content :center
:align-items :center
:background-color (when (= :retry state)
colors/blue-light)
:border-width 1
:border-color (case state
constants/contact-request-message-state-accepted colors/green-transparent-10
constants/contact-request-message-state-declined colors/red-light
constants/contact-request-message-state-pending colors/gray-lighter)
:padding-vertical 10
:padding-horizontal 16})
(defn content-type-contact-request
[outgoing]
{:width 168
:min-height 224.71
:border-radius 8
:border-width 1
:border-color colors/gray-lighter
:align-items :center
:padding-bottom 10
:margin-vertical 4
:align-self (if outgoing :flex-end :flex-start)
:margin-right (if outgoing 8 0)
:margin-left (if outgoing 0 8)})

View File

@ -1,58 +0,0 @@
(ns status-im.ui.screens.chat.toolbar-content
(:require [quo.react-native :as rn]
[re-frame.core :as re-frame]
[status-im.constants :as constants]
[i18n.i18n :as i18n]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.screens.chat.styles.main :as st]))
(defn- group-last-activity
[{:keys [contacts public?]}]
[rn/view {:flex-direction :row}
[rn/text {:style st/toolbar-subtitle}
(if public?
(i18n/label :t/public-group-status)
(let [cnt (count contacts)]
(if (zero? cnt)
(i18n/label :members-active-none)
(i18n/label-pluralize cnt :t/members-active))))]])
(defn one-to-one-name
[from]
(let [[first-name _] @(re-frame.core/subscribe [:contacts/contact-two-names-by-identity from])]
[rn/text
{:style st/chat-name-text
:number-of-lines 1
:accessibility-label :chat-name-text}
first-name]))
(defn contact-indicator
[contact-id]
(let [added? @(re-frame/subscribe [:contacts/contact-added? contact-id])]
[rn/view {:flex-direction :row}
[rn/text {:style st/toolbar-subtitle}
(if added?
(i18n/label :chat-is-a-contact)
(i18n/label :chat-is-not-a-contact))]]))
(defn toolbar-content-view-inner
[chat-info]
(let [{:keys [group-chat invitation-admin color chat-id contacts chat-type chat-name public? emoji]}
chat-info]
[rn/view {:style st/toolbar-container}
[rn/view {:margin-right 10}
[chat-icon.screen/chat-icon-view-toolbar chat-id group-chat chat-name color emoji 36]]
[rn/view {:style st/chat-name-view}
(if group-chat
[rn/text
{:style st/chat-name-text
:number-of-lines 1
:accessibility-label :chat-name-text}
chat-name]
[one-to-one-name chat-id])
(when-not group-chat
[contact-indicator chat-id])
(when (and group-chat (not invitation-admin) (not= chat-type constants/community-chat-type))
[group-last-activity
{:contacts contacts
:public? public?}])]]))

View File

@ -1,12 +1,9 @@
(ns status-im.ui.screens.chat.utils
(:require [quo.design-system.colors :as colors]
[status-im.ethereum.stateofus :as stateofus]
[i18n.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.react :as react]))
(def ^:private reply-symbol "↪ ")
(defn format-author-old
([contact] (format-author-old contact nil))
([{:keys [names] :as contact} {:keys [modal profile? you?]}]
@ -63,29 +60,3 @@
(if (and max-length (> (count first-name) max-length))
(str (subs first-name 0 max-length) "...")
first-name)]))))
(defn format-reply-author
[from username current-public-key style outgoing]
(let [contact-name (str reply-symbol username)]
(or (and (= from current-public-key)
[react/text {:style (style true)}
(str reply-symbol (i18n/label :t/You))])
(if (or (= (aget contact-name 0) "@")
;; in case of replies
(= (aget contact-name 1) "@"))
(let [trimmed-name (subs contact-name 0 81)]
[react/text
{:number-of-lines 2
:style (merge {:color colors/blue
:font-size 13
:line-height 18
:font-weight "500"})}
(or (stateofus/username trimmed-name) trimmed-name)])
[react/text
{:style (merge {:color (if outgoing
colors/white-transparent-70-persist
colors/gray)
:font-size 12
:line-height 18
:font-weight "400"})}
contact-name]))))

View File

@ -1,536 +0,0 @@
(ns status-im.ui.screens.chat.views
(:require
[clojure.string :as string]
[quo.animated :as animated]
[quo.core :as quo]
[quo.design-system.colors :as colors]
[quo.react :as quo.react]
[quo.react-native :as rn]
[re-frame.core :as rf]
re-frame.db
[reagent.core :as reagent]
[status-im.constants :as constants]
[i18n.i18n :as i18n]
[status-im.react-native.resources :as resources]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.connectivity.view :as connectivity]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.screens.chat.audio-message.views :as audio-message]
[status-im.ui.screens.chat.components.accessory :as accessory]
[status-im.ui.screens.chat.components.contact-request :as contact-request]
[status-im.ui.screens.chat.components.edit :as edit]
[status-im.ui.screens.chat.components.input :as components]
[status-im.ui.screens.chat.components.reply :as reply]
[status-im.ui.screens.chat.extensions.views :as extensions]
[status-im.ui.screens.chat.group :as chat.group]
[status-im.ui.screens.chat.image.views :as image]
[status-im.ui.screens.chat.message.datemark :as message-datemark]
[status-im.ui.screens.chat.message.gap :as gap]
[status-im.ui.screens.chat.message.message :as message]
[status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.screens.chat.state :as state]
[status-im.ui.screens.chat.stickers.views :as stickers]
[status-im.ui.screens.chat.styles.main :as style]
[status-im.ui.screens.chat.toolbar-content :as toolbar-content]
[status-im.utils.platform :as platform]
[status-im.utils.utils :as utils]
[status-im2.navigation.state :as navigation.state]
[utils.debounce :as debounce]))
(defn invitation-requests
[chat-id admins]
(let [current-pk @(rf/subscribe [:multiaccount/public-key])
admin? (get admins current-pk)]
(when admin?
(let [invitations @(rf/subscribe [:group-chat/pending-invitations-by-chat-id chat-id])]
(when (seq invitations)
[react/touchable-highlight
{:on-press #(rf/dispatch [:navigate-to :group-chat-invite])
:accessibility-label :invitation-requests-button}
[react/view {:style (style/add-contact)}
[react/text {:style style/add-contact-text}
(i18n/label :t/group-membership-request)]]])))))
(defn add-contact-bar
[public-key]
(when-not (or @(rf/subscribe [:contacts/contact-added? public-key])
@(rf/subscribe [:contacts/contact-blocked? public-key]))
[react/touchable-highlight
{:on-press
#(rf/dispatch [:contact.ui/add-to-contact-pressed public-key])
:accessibility-label :add-to-contacts-button}
[react/view {:style (style/add-contact)}
[icons/icon :main-icons/add
{:color colors/blue}]
[react/i18n-text {:style style/add-contact-text :key :add-to-contacts}]]]))
(defn contact-request
[]
(let [contact-request @(rf/subscribe [:chats/sending-contact-request])]
[react/view {:style style/contact-request}
[react/image
{:source (resources/get-image :hand-wave)
:style {:width 112
:height 96.71
:margin-top 17}}]
[quo/text
{:style {:margin-top 14}
:weight :bold
:size :large}
(i18n/label :t/say-hi)]
[quo/text
{:style {:margin-top 2
:margin-bottom 14}}
(i18n/label :t/send-contact-request-message)]
(when-not contact-request
[react/view
{:style {:padding-horizontal 16
:padding-bottom 8}}
[quo/button
{:style {:width "100%"}
:accessibility-label :contact-request--button
:on-press #(rf/dispatch [:chat.ui/send-contact-request])}
(i18n/label :t/contact-request)]])]))
(defn chat-intro
[{:keys [chat-id
chat-name
chat-type
group-chat
invitation-admin
contact-name
color
loading-messages?
no-messages?
contact-request-state
emoji]}]
[react/view
{:style (style/intro-header-container loading-messages? no-messages?)
:accessibility-label :history-chat}
;; Icon section
[react/view
{:style {:margin-top 52
:margin-bottom 24}}
[chat-icon.screen/emoji-chat-intro-icon-view
chat-name chat-id group-chat emoji
{:default-chat-icon (style/intro-header-icon 120 color)
:default-chat-icon-text (if (string/blank? emoji)
style/intro-header-icon-text
(style/emoji-intro-header-icon-text 120))
:size 120}]]
;; Chat title section
[react/text {:style (style/intro-header-chat-name)}
(if group-chat chat-name contact-name)]
;; Description section
(if group-chat
[chat.group/group-chat-description-container
{:chat-id chat-id
:invitation-admin invitation-admin
:loading-messages? loading-messages?
:chat-name chat-name
:chat-type chat-type
:no-messages? no-messages?}]
[react/text {:style (assoc style/intro-header-description :margin-bottom 32)}
(str (i18n/label :t/empty-chat-description-one-to-one) contact-name)])
(when
(= chat-type constants/one-to-one-chat-type)
(or (= contact-request-state constants/contact-request-state-none)
(= contact-request-state constants/contact-request-state-received)
(= contact-request-state constants/contact-request-state-dismissed)))
[contact-request]])
(defn chat-intro-one-to-one
[{:keys [chat-id] :as opts}]
(let [contact @(rf/subscribe [:contacts/contact-by-identity chat-id])
contact-names @(rf/subscribe [:contacts/contact-two-names-by-identity
chat-id])]
[chat-intro
(assoc opts
:contact-name (first contact-names)
:contact-request-state (or (:contact-request-state contact)
constants/contact-request-state-none))]))
(defn chat-intro-header-container
[{:keys [group-chat invitation-admin
chat-type
synced-to
color chat-id chat-name
public? emoji]}
no-messages]
[react/touchable-without-feedback
{:style {:flex 1
:align-items :flex-start}
:on-press (fn [_]
(react/dismiss-keyboard!))}
(let [opts
{:chat-id chat-id
:group-chat group-chat
:invitation-admin invitation-admin
:chat-type chat-type
:chat-name chat-name
:public? public?
:color color
:loading-messages? (not (pos? synced-to))
:no-messages? no-messages
:emoji emoji}]
(if group-chat
[chat-intro opts]
[chat-intro-one-to-one opts]))])
(defonce messages-list-ref (atom nil))
(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))))))
(defn bottom-sheet
[input-bottom-sheet]
(case input-bottom-sheet
:stickers
[stickers/stickers-view]
:extensions
[extensions/extensions-view]
:images
[image/image-view]
:audio
[audio-message/audio-message-view]
nil))
(defn invitation-bar
[chat-id]
(let [{:keys [state chat-id] :as invitation}
(first @(rf/subscribe [:group-chat/invitations-by-chat-id chat-id]))
{:keys [retry? message]} @(rf/subscribe [:chats/current-chat-membership])
message-length (count message)]
[react/view {:margin-horizontal 16 :margin-top 10}
(cond
(and invitation (= constants/invitation-state-requested state) (not retry?))
[toolbar/toolbar
{:show-border? true
:center
[quo/button
{:type :secondary
:disabled true}
(i18n/label :t/request-pending)]}]
(and invitation (= constants/invitation-state-rejected state) (not retry?))
[toolbar/toolbar
{:show-border? true
:right
[quo/button
{:type :secondary
:accessibility-label :retry-button
:on-press #(rf/dispatch [:group-chats.ui/membership-retry])}
(i18n/label :t/mailserver-retry)]
:left
[quo/button
{:type :secondary
:accessibility-label :remove-group-button
:on-press #(rf/dispatch [:group-chats.ui/remove-chat-confirmed chat-id])}
(i18n/label :t/remove-group)]}]
:else
[toolbar/toolbar
{:show-border? true
:center
[quo/button
{:type :secondary
:accessibility-label :introduce-yourself-button
:disabled (or (string/blank? message)
(> message-length chat.group/message-max-length))
:on-press #(rf/dispatch [:send-group-chat-membership-request])}
(i18n/label :t/request-membership)]}])]))
(defn get-space-keeper-ios
[bottom-space panel-space active-panel text-input-ref]
(fn [state]
;; NOTE: Only iOs now because we use soft input resize screen on android
(when platform/ios?
(cond
(and state
(< @bottom-space @panel-space)
(not @active-panel))
(reset! bottom-space @panel-space)
(and (not state)
(< @panel-space @bottom-space))
(do
(some-> ^js (quo.react/current-ref text-input-ref)
.focus)
(reset! panel-space @bottom-space)
(reset! bottom-space 0))))))
(defn get-set-active-panel
[active-panel]
(fn [panel]
(rn/configure-next
(:ease-opacity-200 rn/custom-animations))
(reset! active-panel panel)
(reagent/flush)
(when panel
(js/setTimeout #(react/dismiss-keyboard!) 100))))
(defn list-footer
[{:keys [chat-id] :as chat}]
(let [loading-messages? @(rf/subscribe [:chats/loading-messages? chat-id])
no-messages? @(rf/subscribe [:chats/chat-no-messages? chat-id])
all-loaded? @(rf/subscribe [:chats/all-loaded? chat-id])]
[react/view {:style (when platform/android? {:scaleY -1})}
(if (or loading-messages? (not chat-id) (not all-loaded?))
[react/view {:height 324 :align-items :center :justify-content :center}
[react/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)
[react/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 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/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)
space-keeper]))])
(def list-key-fn #(or (:message-id %) (:value %)))
(def list-ref #(reset! messages-list-ref %))
;;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
(rf/dispatch [:chat.ui/load-more-messages-for-current-chat])
(utils/set-timeout #(rf/dispatch [: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 @(rf/subscribe [:multiaccount/public-key])
community @(rf/subscribe [: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
space-keeper
show-input?]}]
(let [{:keys [group-chat chat-type chat-id public? community-id admins]} chat
messages @(rf/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? @(rf/subscribe [:contacts/contact-added? chat-id]))
should-send-contact-request?
(and
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
:space-keeper space-keeper
: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
;;TODO https://github.com/facebook/react-native/issues/30034
:inverted (when platform/ios? true)
:style (when platform/android? {:scaleY -1})})]))
(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)
(rf/dispatch [:close-chat])
(rf/dispatch [:navigate-back])))
(defn topbar-content
[]
(let [window-width @(rf/subscribe [:dimensions/window-width])
{:keys [group-chat chat-id] :as chat-info} @(rf/subscribe [:chats/current-chat])]
[react/touchable-highlight
{:on-press #(when-not group-chat
(debounce/dispatch-and-chill [:chat.ui/show-profile chat-id] 1000))
:style {:flex 1 :width (- window-width 120)}}
[toolbar-content/toolbar-content-view-inner chat-info]]))
(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
:accessibility-label :back-button
:style {:height 56
:width 40
:align-items :center
:justify-content :center
:padding-left 16}}
[icons/icon :main-icons/arrow-left {:color colors/black}]]
[react/view {:flex 1 :left 52 :right 52 :top 0 :bottom 0 :position :absolute}
[topbar-content]]
[react/touchable-highlight
{:on-press-in #(rf/dispatch [:bottom-sheet/show-sheet
{:content (fn [] [sheets/current-chat-actions])
:height 256}])
:accessibility-label :chat-menu-button
:style {:right 0
:top 0
:bottom 0
:position :absolute
:height 56
:width 40
:align-items :center
:justify-content :center
:padding-right 16}}
[icons/icon :main-icons/more {:color colors/black}]]])
(defn chat-render
[]
(let [bottom-space (reagent/atom 0)
panel-space (reagent/atom 52)
active-panel (reagent/atom nil)
position-y (animated/value 0)
pan-state (animated/value 0)
text-input-ref (quo.react/create-ref)
on-update #(when-not (zero? %) (reset! panel-space %))
pan-responder (accessory/create-pan-responder position-y pan-state)
space-keeper (get-space-keeper-ios bottom-space panel-space active-panel text-input-ref)
set-active-panel (get-set-active-panel active-panel)
on-close #(set-active-panel nil)]
(fn []
(let [{:keys [chat-id
show-input?
group-chat
admins
invitation-admin]
:as chat}
@(rf/subscribe [:chats/current-chat-chat-view])
max-bottom-space (max @bottom-space
@panel-space)]
[:<>
[topbar]
[connectivity/loading-indicator]
(when chat-id
(when group-chat
[invitation-requests chat-id admins]))
;;MESSAGES LIST
[messages-view
{:chat chat
:bottom-space max-bottom-space
:pan-responder pan-responder
:space-keeper space-keeper
:show-input? show-input?}]
(when (and group-chat invitation-admin)
[accessory/view
{:y position-y
:on-update-inset on-update}
[invitation-bar chat-id]])
[components/autocomplete-mentions text-input-ref max-bottom-space]
(when show-input?
;; NOTE: this only accepts two children
[accessory/view
{:y position-y
:pan-state pan-state
:has-panel (boolean @active-panel)
:on-close on-close
:on-update-inset on-update}
[react/view
[edit/edit-message-auto-focus-wrapper text-input-ref]
[reply/reply-message-auto-focus-wrapper text-input-ref]
;; We set the key so we can force a re-render as
;; it does not rely on ratom but just atoms
^{:key (str @components/chat-input-key "chat-input")}
[components/chat-toolbar
{:chat-id chat-id
:active-panel @active-panel
:set-active-panel set-active-panel
:text-input-ref text-input-ref}]
[contact-request/contact-request-message-auto-focus-wrapper text-input-ref]]
[bottom-sheet @active-panel]])]))))
(defn chat
[]
(reagent/create-class
{:component-did-mount (fn []
(react/hw-back-remove-listener navigate-back-handler)
(react/hw-back-add-listener navigate-back-handler))
:component-will-unmount (fn [] (react/hw-back-remove-listener navigate-back-handler))
:reagent-render chat-render}))

View File

@ -5,7 +5,6 @@
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.ens.core :as ens]
[status-im.ethereum.core :as ethereum]
[status-im.ethereum.ens :as ethereum.ens]
@ -21,7 +20,6 @@
[status-im.ui.components.react :as react]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.components.topbar :as topbar]
[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.ui.screens.profile.components.views :as profile.components]
@ -762,18 +760,12 @@
:action-fn #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content
(fn [] (name-list names preferred-name))}])}]])]
(let [message {:content {:parsed-text
[{:type "paragraph"
:children [{:literal (i18n/label :t/ens-test-message)}]}]}
:content-type constants/content-type-text
:timestamp-str "9:41 AM"}]
[react/view
[react/view {:padding-left 72}
[my-name]]
[react/view {:flex-direction :row}
[react/view {:padding-left 16 :padding-top 4}
[photos/photo (multiaccounts/displayed-photo account) {:size 36}]]
[message/->message message {:on-long-press identity}]]])]])
[react/view
[react/view {:padding-left 72}
[my-name]]
[react/view {:flex-direction :row}
[react/view {:padding-left 16 :padding-top 4}
[photos/photo (multiaccounts/displayed-photo account) {:size 36}]]]]]])
(views/defview main
[]

View File

@ -7,7 +7,6 @@
[status-im.ui.components.animation :as anim]
[status-im.ui.components.react :as react]
[status-im.ui.screens.biometric.views :as biometric]
[status-im.ui.screens.chat.message.pinned-message :as pinned-message]
[status-im.ui.screens.communities.views :as communities]
[status-im.ui.screens.keycard.frozen-card.view :as frozen-card]
[status-im.ui.screens.keycard.views :as keycard.views]
@ -181,9 +180,6 @@
(= :password-reset-popover view)
[reset-password.views/reset-password-popover]
(= :pin-limit view)
[pinned-message/pin-limit-popover]
(= :fees-warning view)
[signing-sheets/fees-warning]

View File

@ -11,14 +11,11 @@
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.keyboard-avoid-presentation :as kb-presentation]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.profile-header.view :as profile-header]
[status-im.ui.components.react :as react]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.profile.components.sheets :as sheets]
[status-im.ui.screens.profile.contact.styles :as styles]
[status-im.ui.screens.status.views :as status.views]
[status-im.utils.utils :as utils])
(:require-macros [status-im.utils.views :as views]))
@ -210,18 +207,6 @@
[]
(let [{:keys [public-key name ens-verified] :as contact} @(re-frame/subscribe
[:contacts/current-contact])
current-chat-id @(re-frame/subscribe
[:chats/current-profile-chat])
messages @(re-frame/subscribe
[:chats/profile-messages-stream
current-chat-id])
no-messages? @(re-frame/subscribe [:chats/chat-no-messages?
current-chat-id])
muted? @(re-frame/subscribe [:chats/muted
public-key])
pinned-messages @(re-frame/subscribe [:chats/pinned
public-key])
[first-name second-name] (multiaccounts/contact-two-names contact true)
on-share #(re-frame/dispatch
[:show-popover
(merge
@ -237,43 +222,4 @@
:on-press on-share}]
:left-accessories [{:icon :main-icons/close
:accessibility-label :back-button
:on-press #(re-frame/dispatch [:navigate-back])}]}]
[list/flat-list
{:key-fn #(or (:message-id %) (:value %))
:header [:<>
[(profile-header/extended-header
{:on-press on-share
:bottom-separator false
:title first-name
:photo (multiaccounts/displayed-photo contact)
:monospace (not ens-verified)
:subtitle second-name
:public-key public-key})]
[react/view
{:height 1 :background-color colors/gray-lighter :margin-top 8}]
[nickname-settings contact]
[pin-settings public-key (count pinned-messages)]
[react/view {:height 1 :background-color colors/gray-lighter}]
[react/view
{:padding-top 17
:flex-direction :row
:align-items :stretch
:flex 1}
(for [{:keys [label] :as action} (actions contact muted?)
:when label]
^{:key label}
[button-item action])]
[react/view
{:height 1 :background-color colors/gray-lighter :margin-top 16}]
(when no-messages?
[react/view {:padding-horizontal 32 :margin-top 32}
[react/view (styles/updates-descr-cont)
[react/text {:style {:color colors/gray :line-height 22}}
(i18n/label :t/status-updates-descr)]]])]
:ref #(reset! status.views/messages-list-ref %)
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages current-chat-id])
:on-scroll-to-index-failed #() ;;don't remove this
:render-data {:chat-id current-chat-id
:profile true}
:render-fn status.views/render-message
:data messages}]])))
:on-press #(re-frame/dispatch [:navigate-back])}]}]])))

View File

@ -11,7 +11,6 @@
[status-im.ui.components.profile-header.view :as profile-header]
[status-im.ui.components.react :as react]
[status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.chat.message.message :as message]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.sheets :as chat.sheets]
[status-im.ui.screens.chat.utils :as chat.utils]
@ -111,21 +110,16 @@
(debounce/dispatch-and-chill event 2000))
(defn invitation-sheet
[{:keys [introduction-message id]} contact]
[{:keys [id]} contact]
(let [members @(re-frame/subscribe [:contacts/current-chat-contacts])
allow-adding-members? (< (count members) constants/max-group-chat-participants)]
[react/view
(let [message {:content {:parsed-text
[{:type "paragraph"
:children [{:literal introduction-message}]}]}
:content-type constants/content-type-text}]
[react/view {:margin-bottom 8 :margin-right 16}
[react/view {:padding-left 72}
(chat.utils/format-author-old contact)]
[react/view {:flex-direction :row :align-items :flex-end}
[react/view {:padding-left 16 :padding-top 4}
[photos/photo (multiaccounts/displayed-photo contact) {:size 36}]]
[message/->message message {:on-long-press identity}]]])
[react/view {:margin-bottom 8 :margin-right 16}
[react/view {:padding-left 72}
(chat.utils/format-author-old contact)]
[react/view {:flex-direction :row :align-items :flex-end}
[react/view {:padding-left 16 :padding-top 4}
[photos/photo (multiaccounts/displayed-photo contact) {:size 36}]]]]
[quo/list-item
{:theme :accent
:disabled (not allow-adding-members?)

View File

@ -17,7 +17,6 @@
[status-im.ui.screens.browser.tabs.views :as browser.tabs]
[status-im.ui.screens.browser.views :as browser]
[status-im.ui.screens.bug-report :as bug-report]
[status-im.ui.screens.chat.pinned-messages :as pin-messages]
[status-im.ui.screens.communities.channel-details :as communities.channel-details]
[status-im.ui.screens.communities.community :as community]
[status-im.ui.screens.communities.community-emoji-thumbnail-picker :as
@ -86,8 +85,6 @@
[status-im.ui.screens.qr-scanner.views :as qr-scanner]
[status-im.ui.screens.reset-password.views :as reset-password]
[status-im.ui.screens.rpc-usage-info :as rpc-usage-info]
[status-im.ui.screens.status.new.views :as status.new]
[status-im.ui.screens.status.views :as status.views]
[status-im.ui.screens.stickers.views :as stickers]
[status-im.ui.screens.sync-settings.views :as sync-settings]
[status-im.ui.screens.terms-of-service.views :as terms-of-service]
@ -200,11 +197,6 @@
:component onboarding.phrase/wizard-recovery-success}
;;CHAT
;Pinned messages
{:name :chat-pinned-messages
;TODO custom subtitle
:options {:topBar {:visible false}}
:component pin-messages/pinned-messages}
{:name :group-chat-profile
;;TODO animated-header
@ -388,13 +380,6 @@
:options {:topBar {:visible false}}
:component wallet.swap/asset-selector}
;;MY STATUS
{:name :status
:on-focus [:init-timeline-chat]
:insets {:top true}
:component status.views/timeline}
;;PROFILE
{:name :my-profile
@ -696,12 +681,6 @@
:options {:topBar {:visible false}}
:component wallet.collectibles/nft-details-modal}
;My Status
{:name :my-status
:insets {:bottom true}
:options {:topBar {:title {:text (i18n/label :t/my-status)}}}
:component status.new/my-status}
;[Browser] New bookmark
{:name :new-bookmark
:insets {:bottom true}

View File

@ -1,34 +0,0 @@
(ns status-im.ui.screens.status.new.styles
(:require [quo.design-system.colors :as colors]))
(def buttons
{:padding-horizontal 14
:padding-vertical 10
:justify-content :space-between
:height 88})
(def image
{:width 72
:height 72
:background-color :black
:resize-mode :cover
:margin-right 4
:border-radius 4})
(defn photos-buttons
[]
{:height 88
:border-top-width 1
:border-top-color colors/gray-lighter
:flex-direction :row
:align-items :center})
(def count-container
{:top 0
:bottom 0
:left 0
:right 0
:align-items :center
:justify-content :center
:position :absolute
:pointerEvents :none})

View File

@ -1,128 +0,0 @@
(ns status-im.ui.screens.status.new.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [clojure.string :as string]
[quo.components.animated.pressable :as pressable]
[quo.core :as quo]
[quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[i18n.i18n :as i18n]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.keyboard-avoid-presentation :as kb-presentation]
[status-im.ui.components.react :as react]
[status-im.ui.components.toolbar :as toolbar]
[status-im.ui.screens.status.new.styles :as styles]
[status-im.ui.screens.status.views :as status.views]
[status-im.utils.platform :as platform]))
(defn buttons
[]
[react/view styles/buttons
[pressable/pressable
{:type :scale
:accessibility-label :take-picture
:on-press #(re-frame/dispatch [:chat.ui/show-image-picker-camera-timeline])}
[icons/icon :main-icons/camera]]
[react/view {:style {:padding-top 8}}
[pressable/pressable
{:on-press #(re-frame/dispatch [:chat.ui/open-image-picker-timeline])
:accessibility-label :open-gallery
:type :scale}
[icons/icon :main-icons/gallery]]]])
(defn image-preview
[uri]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/camera-roll-pick-timeline uri])}
[react/image
{:style styles/image
:source {:uri uri}}]])
(defview photos
[]
(letsubs [camera-roll-photos [:camera-roll/photos]]
{:component-did-mount #(re-frame/dispatch [:chat.ui/camera-roll-get-photos 20])}
[react/scroll-view
{:horizontal true
:style {:max-height 88}
:keyboard-should-persist-taps :handled}
[react/view (styles/photos-buttons)
[buttons]
(for [img camera-roll-photos]
^{:key (str "image" img)}
[image-preview img])]]))
(def message-max-length 600)
(defn my-status
[]
(let [images-opened (reagent/atom false)
scroll (reagent/atom nil)
autoscroll? (reagent/atom false)
scroll-height (reagent/atom nil)
input-text (re-frame/subscribe [:chats/timeline-chat-input-text])
sending-image (re-frame/subscribe [:chats/timeline-sending-image])]
(fn []
(let [{:keys [uri]} (first (vals @sending-image))
text-length (count @input-text)]
[kb-presentation/keyboard-avoiding-view {:style {:flex 1}}
[:<>
[react/scroll-view
{:style {:flex 1}
:ref #(reset! scroll %)
:on-layout #(reset! scroll-height
(.-nativeEvent.layout.height ^js %))
:keyboard-should-persist-taps :handled}
[react/text-input
{:style {:margin 16}
:scroll-enabled false
:accessibility-label :my-status-input
:max-length (if platform/android?
message-max-length
(when (>= text-length message-max-length)
text-length))
:auto-focus true
:multiline true
:on-selection-change (fn [args]
(let [selection (.-selection ^js (.-nativeEvent ^js args))
end (.-end ^js selection)]
(reset! autoscroll? (< (- (count @input-text) end) 10))))
:on-content-size-change #(when (and @autoscroll? @scroll @scroll-height)
(when-let [height (- (.-nativeEvent.contentSize.height ^js %)
@scroll-height
-40)]
(.scrollTo @scroll #js {:y height :animated true})))
:on-change-text #(re-frame/dispatch [:chat.ui/set-timeline-input-text %])
:default-value @input-text
:placeholder (i18n/label :t/whats-on-your-mind)}]
(when uri
[react/view {:margin-horizontal 16 :margin-bottom 16}
[status.views/message-content-image uri true]])]
[react/view
(when @images-opened
[photos])
[react/view
[toolbar/toolbar
{:show-border? true
:left
[quo/button
{:accessibility-label :open-images-panel-button
:type :secondary
:on-press #(swap! images-opened not)}
[icons/icon :main-icons/photo {:color (if @images-opened colors/blue colors/gray)}]]
:right
[quo/button
{:accessibility-label :send-my-status-button
:type :secondary
:after :main-icon/send
:disabled (or (> text-length message-max-length)
(and (string/blank? @input-text) (not uri)))
:on-press #(do
(re-frame/dispatch [:profile.ui/send-my-status-message])
(re-frame/dispatch [:navigate-back]))}
(i18n/label :t/wallet-send)]}]
[react/view styles/count-container
[react/text
{:style {:color (if (> text-length message-max-length)
colors/red
colors/gray)}}
(str text-length " / " message-max-length)]]]]]]))))

View File

@ -1,13 +0,0 @@
(ns status-im.ui.screens.status.styles
(:require [quo.design-system.colors :as colors]))
(defn descr-container
[]
{:border-width 1
:border-color colors/gray-lighter
:border-top-right-radius 16
:border-bottom-left-radius 16
:border-top-left-radius 16
:border-bottom-right-radius 4
:padding-horizontal 12
:padding-vertical 6})

View File

@ -1,244 +0,0 @@
(ns status-im.ui.screens.status.views
(:require [quo.design-system.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.chat.models :as chat]
[status-im.constants :as constants]
[i18n.i18n :as i18n]
[status-im.ui.components.fast-image :as fast-image]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.list.views :as list]
[status-im.ui.components.plus-button :as components.plus-button]
[status-im.ui.components.react :as react]
[status-im.ui.components.tabs :as tabs]
[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.gap :as gap]
[status-im.ui.screens.chat.message.link-preview :as link-preview]
[status-im.ui.screens.chat.message.message :as message]
[status-im.ui.screens.chat.message.reactions-old :as reactions]
[status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.status.styles :as styles]
[utils.datetime :as datetime]))
(defonce messages-list-ref (atom nil))
(def image-max-dimension 192)
(defn image-set-size
[width]
(fn [^js evt]
(reset! width (/ (.-width (.-nativeEvent evt))
(/ (.-height (.-nativeEvent evt)) image-max-dimension)))))
(defn message-content-image
[_ _]
(let [width (reagent/atom nil)]
(fn [uri show-close?]
[react/view
{:style {:width @width
:align-items :center
:height image-max-dimension
:max-width :100%
:overflow :hidden
:opacity (if @width 1 0)
:border-radius 16
:margin-top 8}
:accessibility-label :image-message}
[fast-image/fast-image
{:style {:width @width
:height image-max-dimension}
:on-load (image-set-size width)
:source {:uri uri}}]
[react/view
{:border-width 1
:top 0
:left 0
:width @width
:height image-max-dimension
:border-radius 16
:position :absolute
:background-color :transparent
:border-color colors/black-transparent}]
(when show-close?
[react/touchable-highlight
{:on-press #(re-frame/dispatch [:chat.ui/cancel-sending-image-timeline])
:accessibility-label :cancel-send-image
:style {:right 4 :top 12 :position :absolute}}
[react/view
{:width 24
:height 24
:background-color colors/black-persist
:border-radius 12
:align-items :center
:justify-content :center}
[icons/icon :main-icons/close-circle {:color colors/white-persist}]]])])))
(defn on-long-press-fn
[on-long-press content image]
(on-long-press
(when-not image
[{:id :copy
: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)}])))
(defn image-message
[]
(let [visible (reagent/atom false)]
(fn [{:keys [content] :as message} on-long-press]
[:<>
[preview/preview-image
{:message (assoc message :cant-be-replied true)
:visible @visible
:can-reply false
: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-fn on-long-press content true)}
[message-content-image (:image content) false]]])))
(defn message-item
[account profile]
(fn [{:keys [content-type content from timestamp outgoing] :as message}
{:keys [modal on-long-press close-modal]}]
[react/view
(merge {:padding-vertical 8
:flex-direction :row
:background-color (when modal colors/white)
:padding-horizontal 16}
(when modal
{:border-radius 16}))
[react/touchable-highlight
{:on-press #(do (when modal (close-modal))
(when profile (re-frame/dispatch [:navigate-back]))
(re-frame/dispatch [:chat.ui/show-profile from]))}
[react/view {:padding-top 2 :padding-right 8}
(if outgoing
[photos/account-photo account]
[photos/member-photo from])]]
[react/view {:flex 1}
[react/view
{:flex-direction :row
:justify-content :space-between
:width :98%
:word-break :break-all}
[react/touchable-highlight
{:on-press #(do (when modal (close-modal))
(when profile (re-frame/dispatch [:navigate-back]))
(re-frame/dispatch [:chat.ui/show-profile from]))}
(let [message-author-width (* @(re-frame/subscribe [:dimensions/window-width]) 0.75)]
(if outgoing
[react/view {:style {:width message-author-width}}
[message/message-my-name {:profile? true :you? false}]]
[react/view {:style {:width message-author-width}}
[message/message-author-name from {:profile? true}]]))]
[react/text {:style {:font-size 10 :color colors/gray :margin-left :auto}}
(datetime/time-ago (datetime/to-date timestamp))]]
[react/view
(if (= content-type constants/content-type-image)
[image-message message on-long-press]
[react/touchable-highlight
(when-not modal
{:on-long-press #(on-long-press-fn on-long-press content false)})
[message/render-parsed-text (assoc message :outgoing false) (:parsed-text content)]])
[link-preview/link-preview-wrapper (:links content) outgoing true]]]]))
(defn render-message
[{:keys [type] :as message} idx _ {:keys [timeline account chat-id profile]}]
(if (= type :datemark)
nil
(if (= type :gap)
(if timeline
nil
[gap/gap message idx messages-list-ref true chat-id])
;; for timeline for reactions we need to use :from as chat-id
(let [chat-id (chat/profile-chat-topic (:from message))]
[react/view
(merge {:accessibility-label :chat-item}
(when (:last-in-group? message)
{:padding-bottom 8
:margin-bottom 8
:border-bottom-width 1
:border-bottom-color colors/gray-lighter}))
[reactions/with-reaction-picker
{:message message
:timeline true
:reactions @(re-frame/subscribe [:chats/message-reactions (:message-id message)
constants/timeline-chat-id])
:picker-on-open (fn [])
:picker-on-close (fn [])
:send-emoji (fn [{:keys [emoji-id]}]
(re-frame/dispatch [:models.reactions/send-emoji-reaction
{:message-id (:message-id message)
:chat-id chat-id
: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)
:chat-id chat-id
:emoji-id emoji-id
:emoji-reaction-id emoji-reaction-id}]))
:render (message-item account profile)}]]))))
(def state (reagent/atom {:tab :timeline}))
(defn tabs
[]
(let [{:keys [tab]} @state]
[react/view
{:flex-direction :row
:padding-horizontal 4
:margin-top 8}
[tabs/tab-title state :timeline (i18n/label :t/timeline) (= tab :timeline)]
[tabs/tab-title state :status (i18n/label :t/my-status) (= tab :status)]]))
(defn timeline
[]
(let [messages @(re-frame/subscribe [:chats/timeline-messages-stream])
loading-messages? @(re-frame/subscribe [:chats/loading-messages? constants/timeline-chat-id])
no-messages? @(re-frame/subscribe [:chats/chat-no-messages? constants/timeline-chat-id])
account @(re-frame/subscribe [:multiaccount])]
[react/view {:flex 1}
[react/view
{:height 1
:background-color colors/gray-lighter}]
(if (and no-messages? loading-messages?)
[react/view {:flex 1 :align-items :center :justify-content :center}
[react/activity-indicator {:animating true}]]
(if no-messages?
[react/view
{:padding-horizontal 32
:margin-top 64}
[fast-image/fast-image
{:style {:width 140
:height 140
:align-self :center}
:source
{:uri
"https://bafybeieayj76s4vjlw5uwdvnakosy46rqyioqsp2ygl6sedivemhkxrbwi.ipfs.cf-ipfs.com"}}]
[react/view (styles/descr-container)
[react/text
{:style {:color colors/gray
:line-height 22}}
(if (= :timeline (:tab @state))
(i18n/label :t/statuses-descr)
(i18n/label :t/statuses-my-status-descr))]]]
[list/flat-list
{:key-fn #(or (:message-id %) (:value %))
:render-data {:timeline (= :timeline (:tab @state))
:account account}
:render-fn render-message
:data messages
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages
constants/timeline-chat-id])
;;don't remove :on-scroll-to-index-failed
:on-scroll-to-index-failed #()
:header [react/view {:height 8}]
:footer [react/view {:height 68}]}]))
[components.plus-button/plus-button-old
{:on-press #(re-frame/dispatch [:open-modal :my-status])}]]))

View File

@ -8,7 +8,7 @@
[quo2.foundations.colors :as colors]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.chat.constants :as chat.constants]
[status-im2.common.constants :as chat.constants]
[status-im.chat.models.mentions :as mentions]
[i18n.i18n :as i18n]
[status-im.ui.components.react :as react]

View File

@ -2,7 +2,6 @@
(:require
[quo.design-system.colors :as quo.colors]
[quo.react-native :as rn]
[quo2.components.avatars.user-avatar :as user-avatar]
[quo2.components.icon :as icons]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
@ -16,9 +15,7 @@
[status-im.ui.components.react :as react]
[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.sheets :as sheets]
[status-im.ui.screens.chat.styles.message.message :as style]
[status-im.ui.screens.chat.utils :as chat.utils]
@ -34,8 +31,6 @@
[quo2.core :as quo])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(def edited-at-text (str " ⌫ " (i18n/label :t/edited)))
(defn system-text?
[content-type]
(= content-type constants/content-type-system-text))
@ -208,46 +203,6 @@
(assoc props :accessibility-label :message-timestamp)
(datetime/to-short-str timestamp)]]))])
(defn message-content-wrapper
"Author, userpic and delivery wrapper"
[{:keys [last-in-group? timestamp pinned from chat-id]
:as message} content]
(let [response-to (:response-to (:content message))
display-name (first (rf/sub [:contacts/contact-two-names-by-identity from]))
contact (rf/sub [:contacts/contact-by-address from])
photo-path (when-not (empty? (:images contact)) (rf/sub [:chats/photo-path from]))
online? (rf/sub [:visibility-status-updates/online? from])]
[rn/view
{:style (style/message-wrapper message)
:pointer-events :box-none
:accessibility-label :chat-item}
(when (and (seq response-to) (:quoted-message message))
[quoted-message {:message-id response-to :chat-id chat-id} (:quoted-message message)])
[rn/view
{:style (style/message-body)
:pointer-events :box-none}
;; AVATAR
[rn/view {:style {:width 40}}
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
[user-avatar/user-avatar
{:full-name display-name
:profile-picture photo-path
:status-indicator? true
:online? online?
:size :small
:ring? false}]])]
[rn/view {:style (style/message-author-wrapper)}
;; AUTHOR NAME
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
[display-name-view display-name contact timestamp true])
;; MESSAGE CONTENT
content
[link-preview/link-preview-wrapper (:links (:content message)) false false]]]
;; delivery status
[rn/view (style/delivery-status)
[message-delivery-status message]]]))
(def image-max-width 260)
(def image-max-height 192)
@ -265,10 +220,6 @@
(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])

View File

@ -223,3 +223,16 @@
3 72.5
4 72.5
5 72.5}})
(def ^:const spam-message-frequency-threshold 4)
(def ^:const spam-interval-ms 1000)
(def ^:const default-cooldown-period-ms 10000)
(def ^:const cooldown-reset-threshold 3)
(def ^:const cooldown-periods-ms
{1 2000
2 5000
3 10000})
(def ^:const max-text-size 4096)
;; any message that comes after this amount of ms will be grouped separately
(def ^:const group-ms 300000)

View File

@ -2,7 +2,7 @@
(:require
[i18n.i18n :as i18n]
[quo2.foundations.colors :as colors]
[status-im.chat.models.message-list :as message-list]
[status-im2.contexts.chat.messages.list.events :as message-list]
[taoensso.timbre :as log]
[utils.datetime :as datetime]
[utils.re-frame :as rf]))

View File

@ -2,7 +2,7 @@
(:require
[i18n.i18n :as i18n]
[quo2.foundations.colors :as colors]
[status-im.chat.models.message-list :as message-list]
[status-im2.contexts.chat.messages.list.events :as message-list]
[taoensso.timbre :as log]
[utils.datetime :as datetime]
[utils.re-frame :as rf]))

View File

@ -1,12 +1,12 @@
(ns status-im.chat.models.message-list
(:require ["functional-red-black-tree" :as rb-tree]
[status-im.constants :as constants]
(ns status-im2.contexts.chat.messages.list.events
(:require [utils.red-black-tree :as red-black-tree]
[utils.datetime :as datetime]
[utils.re-frame :as rf]))
[utils.re-frame :as rf]
[status-im2.common.constants :as constants]))
(defn- add-datemark
[{:keys [whisper-timestamp] :as msg}]
;;TODO this is slow
;;NOTE(performance) this is slow
(assoc msg :datemark (datetime/day-relative whisper-timestamp)))
(defn- add-timestamp
@ -40,9 +40,6 @@
add-datemark
add-timestamp))
;; any message that comes after this amount of ms will be grouped separately
(def ^:private group-ms 300000)
(defn same-group?
"Whether a message is in the same group as the one after it.
We check the time, and the author"
@ -51,7 +48,7 @@
(not (:system-message? a))
(not (:system-message? b))
(= (:from a) (:from b))
(<= (js/Math.abs (- (:whisper-timestamp a) (:whisper-timestamp b))) group-ms)))
(<= (js/Math.abs (- (:whisper-timestamp a) (:whisper-timestamp b))) constants/group-ms)))
(defn display-photo?
"We display photos for other users, and not in 1-to-1 chats"
@ -126,50 +123,31 @@
(not one-to-one?))
:last-in-group? last-in-group?)))
(defn get-prev-element
"Get previous item in the iterator, and wind it back to the initial state"
[^js iter]
(.prev iter)
(let [e (.-value iter)]
(.next iter)
e))
(defn get-next-element
"Get next item in the iterator, and wind it back to the initial state"
[^js iter]
(.next iter)
(let [e (.-value iter)]
(.prev iter)
e))
(defn update-message
"Update the message and siblings with positional info"
[^js tree message]
(let [^js iter (.find tree message)
^js previous-message (when (.-hasPrev iter)
(get-prev-element iter))
^js next-message (when (.-hasNext iter)
(get-next-element iter))
^js message-with-pos-data (add-group-info message previous-message next-message)]
(cond-> (.update iter message-with-pos-data)
[tree message]
(let [iter (red-black-tree/find tree message)
previous-message (red-black-tree/get-prev iter)
next-message (red-black-tree/get-next iter)
message-with-pos-data (add-group-info message previous-message next-message)]
(cond-> (red-black-tree/update iter message-with-pos-data)
next-message
(-> ^js (.find next-message)
(.update (update-next-message message-with-pos-data next-message)))
(-> (red-black-tree/find next-message)
(red-black-tree/update (update-next-message message-with-pos-data next-message)))
(and previous-message
(not= :datemark (:type previous-message)))
(-> ^js (.find previous-message)
(.update (update-previous-message message-with-pos-data previous-message))))))
(-> (red-black-tree/find previous-message)
(red-black-tree/update (update-previous-message message-with-pos-data previous-message))))))
(defn remove-message
"Remove a message in the list"
[^js tree prepared-message]
(let [iter (.find tree prepared-message)]
[tree prepared-message]
(let [iter (red-black-tree/find tree prepared-message)]
(if (not iter)
tree
(let [^js new-tree (.remove iter)
^js next-message (when (.-hasNext iter)
(get-next-element iter))]
(let [new-tree (red-black-tree/remove iter)
next-message (red-black-tree/get-next iter)]
(if (not next-message)
new-tree
(update-message new-tree next-message))))))
@ -179,13 +157,13 @@
its positional metadata, and update the left & right messages if necessary,
this operation is O(logN) for insertion, and O(logN) for the updates, as
we need to re-find (there's probably a better way)"
[^js old-message-list prepared-message]
(let [^js tree (.insert old-message-list prepared-message prepared-message)]
[old-message-list prepared-message]
(let [tree (red-black-tree/insert old-message-list prepared-message)]
(update-message tree prepared-message)))
(defn add
[message-list message]
(insert-message (or message-list (rb-tree compare-fn)) (prepare-message message)))
(insert-message (or message-list (red-black-tree/tree compare-fn)) (prepare-message message)))
(defn add-many
[message-list messages]
@ -194,9 +172,9 @@
messages))
(defn ->seq
[^js message-list]
[message-list]
(if message-list
(array-seq (.-values message-list))
(array-seq (red-black-tree/get-values message-list))
[]))
;; NOTE(performance): this is too expensive, probably we could mark message somehow and just hide it in

View File

@ -1,6 +1,6 @@
(ns status-im.chat.models.message-list-test
(ns status-im2.contexts.chat.messages.list.events-test
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.chat.models.message-list :as s]
[status-im2.contexts.chat.messages.list.events :as s]
[taoensso.tufte :as tufte :refer-macros [defnp profile]]))
(deftest message-stream-tests

View File

@ -1,4 +1,4 @@
(ns status-im.ui.screens.chat.state)
(ns status-im2.contexts.chat.messages.list.state)
(defonce first-not-visible-item (atom nil))

View File

@ -1,20 +1,19 @@
(ns status-im2.contexts.chat.messages.list.view
(:require [i18n.i18n :as i18n]
[oops.core :as oops]
[quo.react-native :as quo.react]
[quo2.core :as quo]
[react-native.background-timer :as background-timer]
[react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]
[status-im.ui.screens.chat.group :as chat.group]
[status-im.ui.screens.chat.state :as state]
[utils.re-frame :as rf]
[status-im2.contexts.chat.messages.content.view :as message]
[status-im2.common.constants :as constants]
[utils.re-frame :as rf]
[status-im2.contexts.chat.messages.content.deleted.view :as content.deleted]
[status-im.ui.screens.chat.message.gap :as message.gap]
[status-im2.common.not-implemented :as not-implemented]))
[status-im2.common.not-implemented :as not-implemented]
[status-im.ui.screens.chat.group :as chat.group]
[status-im2.contexts.chat.messages.list.state :as state]
[status-im.ui.screens.chat.message.gap :as message.gap]))
(defonce messages-list-ref (atom nil))
@ -37,7 +36,7 @@
threshold-percentage-to-show-floating-scroll-down-button)
reached-threshold? (> y threshold-height)]
(when (not= reached-threshold? @show-floating-scroll-down-button)
(quo.react/configure-next (:ease-in-ease-out quo.react/layout-animation-presets))
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets))
(reset! show-floating-scroll-down-button reached-threshold?))))
(defn on-viewable-items-changed

View File

@ -1,6 +1,6 @@
(ns status-im2.contexts.chat.messages.pin.events
(:require [re-frame.core :as re-frame]
[status-im.chat.models.message-list :as message-list]
[status-im2.contexts.chat.messages.list.events :as message-list]
[status-im.constants :as constants]
[status-im.data-store.pin-messages :as data-store.pin-messages]
[status-im.transport.message.protocol :as protocol]

View File

@ -1,7 +1,7 @@
(ns status-im2.subs.chat.messages
(:require [re-frame.core :as re-frame]
[status-im.chat.db :as chat.db]
[status-im.chat.models.message-list :as models.message-list]
[status-im2.contexts.chat.messages.list.events :as models.message-list]
[status-im.chat.models.reactions :as models.reactions]
[utils.datetime :as datetime]
[status-im2.common.constants :as constants]))

View File

@ -0,0 +1,51 @@
(ns utils.red-black-tree
(:refer-clojure :exclude [remove update find])
(:require ["functional-red-black-tree" :as red-black-tree]))
(def tree ^js red-black-tree)
(defn find
[^js tree item]
(.find tree item))
(defn insert
[^js tree item]
(.insert tree item))
(defn update
[^js iter item]
(.update iter item))
(defn remove
[^js iter]
(.remove iter))
(defn get-values
[^js tree]
(.-values ^js tree))
(defn get-prev-element
"Get previous item in the iterator, and wind it back to the initial state"
[^js iter]
(.prev iter)
(let [e (.-value iter)]
(.next iter)
e))
(defn get-prev
[^js iter]
(when (.-hasPrev iter)
(get-prev-element iter)))
(defn get-next-element
"Get next item in the iterator, and wind it back to the initial state"
[^js iter]
(.next iter)
(let [e (.-value iter)]
(.prev iter)
e))
(defn get-next
[^js iter]
(when (.-hasNext iter)
(get-next-element iter)))