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/warning-handler
status-im.utils.build/get-current-sha status-im.utils.build/get-current-sha
status-im.chat.constants/spacing-char status-im2.common.constants/spacing-char
status-im.chat.constants/arg-wrapping-char status-im2.common.constants/arg-wrapping-char
status-im.ios.core/init status-im.ios.core/init
status-im.ui.components.camera/aspects status-im.ui.components.camera/aspects
status-im.ui.components.camera/capture-targets 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 ;; in the SHADOW_HOST env variable to make sure that
;; it will use the right interface ;; it will use the right interface
:local-ip #shadow/env "SHADOW_HOST"} :local-ip #shadow/env "SHADOW_HOST"}
:chunks {:fleets status-im.fleet.default-fleet/default-fleets :chunks {:fleets status-im.fleet.default-fleet/default-fleets}
:chats status-im.chat.default-chats/default-chats}
:release :release
{:closure-defines {:closure-defines
{status-im.utils.config/POKT_TOKEN #shadow/env "POKT_TOKEN" {status-im.utils.config/POKT_TOKEN #shadow/env "POKT_TOKEN"

View File

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

View File

@ -109,3 +109,11 @@
(let [fn-ref (use-ref f)] (let [fn-ref (use-ref f)]
(oops/oset! fn-ref "current" f) (oops/oset! fn-ref "current" f)
(use-effect-once (fn [] (fn [] (oops/ocall! fn-ref "current")))))) (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]}] [{:keys [public? name]}]
(str (when public? "#") name)) (str (when public? "#") name))
(defn datemark?
[{:keys [type]}]
(= type :datemark))
(defn intersperse-datemark (defn intersperse-datemark
"Reduce step which expects the input list of messages to be sorted by clock value. "Reduce step which expects the input list of messages to be sorted by clock value.
It makes best effort to group them by day. 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] [re-frame.core :as re-frame]
[status-im.add-new.db :as new-public-chat.db] [status-im.add-new.db :as new-public-chat.db]
[status-im.chat.models.loading :as loading] [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.constants :as constants]
[status-im.data-store.chats :as chats-store] [status-im.data-store.chats :as chats-store]
[status-im.data-store.contacts :as contacts-store] [status-im.data-store.contacts :as contacts-store]
[i18n.i18n :as i18n] [i18n.i18n :as i18n]
[status-im.mailserver.core :as mailserver] [status-im.mailserver.core :as mailserver]
[status-im.multiaccounts.model :as multiaccounts.model] [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] [status-im.utils.clocks :as utils.clocks]
[utils.re-frame :as rf] [utils.re-frame :as rf]
[status-im.utils.types :as types]
[status-im.utils.utils :as utils] [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-for-me.events :as delete-for-me]
[status-im2.contexts.chat.messages.delete-message.events :as delete-message] [status-im2.contexts.chat.messages.delete-message.events :as delete-message]
[status-im2.navigation.events :as navigation] [status-im2.navigation.events :as navigation]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
(defn chats
[]
(:chats (types/json->clj (js/require "./chats.js"))))
(defn- get-chat (defn- get-chat
[cofx chat-id] [cofx chat-id]
(get-in cofx [:db :chats chat-id])) (get-in cofx [:db :chats chat-id]))

View File

@ -3,7 +3,7 @@
[clojure.string :as string] [clojure.string :as string]
[goog.object :as object] [goog.object :as object]
[re-frame.core :as re-frame] [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 :as chat]
[status-im.chat.models.mentions :as mentions] [status-im.chat.models.mentions :as mentions]
[status-im.chat.models.message :as chat.message] [status-im.chat.models.message :as chat.message]

View File

@ -1,6 +1,6 @@
(ns status-im.chat.models.input-test (ns status-im.chat.models.input-test
(:require [cljs.test :refer-macros [deftest is testing]] (: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] [status-im.chat.models.input :as input]
[utils.datetime :as datetime])) [utils.datetime :as datetime]))

View File

@ -1,6 +1,6 @@
(ns status-im.chat.models.loading (ns status-im.chat.models.loading
(:require [re-frame.core :as re-frame] (: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.constants :as constants]
[status-im.data-store.chats :as data-store.chats] [status-im.data-store.chats :as data-store.chats]
[status-im.data-store.messages :as data-store.messages] [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 :as chat-model]
[status-im.chat.models.loading :as chat.loading] [status-im.chat.models.loading :as chat.loading]
[status-im.chat.models.mentions :as mentions] [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.constants :as constants]
[status-im.data-store.messages :as data-store.messages] [status-im.data-store.messages :as data-store.messages]
[status-im.transport.message.protocol :as protocol] [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] [utils.re-frame :as rf]
[status-im.utils.gfycat.core :as gfycat] [status-im.utils.gfycat.core :as gfycat]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]

View File

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

View File

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

View File

@ -1,6 +1,6 @@
(ns status-im.contact.block (ns status-im.contact.block
(:require [re-frame.core :as re-frame] (: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.contact.db :as contact.db]
[status-im.data-store.chats :as chats-store] [status-im.data-store.chats :as chats-store]
[status-im.data-store.contacts :as contacts-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] [quo.react-native :as rn]
[re-frame.core :as re-frame.core] [re-frame.core :as re-frame.core]
[status-im.multiaccounts.core :as multiaccounts] [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.chat-icon.styles :as styles]
[status-im.ui.components.icons.icons :as icons] [status-im.ui.components.icons.icons :as icons]
[status-im.ui.screens.chat.photos :as photos] [status-im.ui.screens.chat.photos :as photos]
@ -55,7 +54,8 @@
styles/container-chat-list styles/container-chat-list
photo-container) photo-container)
size (:width 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 dot-styles (visibility-status-utils/icon-visibility-status-dot
public-key public-key
size size

View File

@ -3,11 +3,9 @@
[quo.design-system.colors :as colors] [quo.design-system.colors :as colors]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.add-new.db :as db] [status-im.add-new.db :as db]
[status-im.chat.models :as chat.models]
[status-im.react-native.resources :as resources] [status-im.react-native.resources :as resources]
[status-im.ui.components.icons.icons :as icons] [status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im2.setup.i18n-resources :as i18n-resources]
[i18n.i18n :as i18n]) [i18n.i18n :as i18n])
(:require-macros [status-im.utils.views :as views])) (:require-macros [status-im.utils.views :as views]))
@ -37,59 +35,6 @@
:auto-correct false :auto-correct false
:error error}]) :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/defview new-public-chat
[] []
(views/letsubs [topic [:public-group-topic] (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] [quo.react-native :as rn]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[reagent.core :as reagent] [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] [status-im.chat.models.mentions :as mentions]
[i18n.i18n :as i18n] [i18n.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts] [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] [re-frame.core :as re-frame]
[status-im.constants :as constants] [status-im.constants :as constants]
[i18n.i18n :as i18n] [i18n.i18n :as i18n]
[status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.react :as react] [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.platform :as platform]
[status-im.utils.universal-links.utils :as links]
[utils.debounce :as debounce]) [utils.debounce :as debounce])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
@ -76,124 +73,3 @@
(letsubs [invitations [:group-chat/invitations-by-chat-id chat-id]] (letsubs [invitations [:group-chat/invitations-by-chat-id chat-id]]
(when invitation-admin (when invitation-admin
[request-membership (first invitations)]))) [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.components.react :as react]
[status-im.ui.screens.chat.styles.photos :as photos])) [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 (defn container-style
[{:keys [outgoing timeline]}] [{:keys [outgoing timeline]}]
(merge {:border-top-left-radius 16 (merge {:border-top-left-radius 16
@ -31,72 +17,11 @@
{:border-top-right-radius 4} {:border-top-right-radius 4}
{:border-top-left-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 (def screen-width
(-> "window" (-> "window"
react/get-dimensions react/get-dimensions
:width)) :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 (defn reactions-row
[timeline margin-top] [timeline margin-top]
{:flex-direction :row {:flex-direction :row
@ -108,49 +33,9 @@
:max-width (- screen-width (+ 30 photos/default-size (when timeline 8))) :max-width (- screen-width (+ 30 photos/default-size (when timeline 8)))
:margin-left (+ 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 (def community-preview-header
{:margin 8 :margin-left 12}) {: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 (defn scale-dimensions
"Scale a given height and width to be maximum percentage allowed of the screen width" "Scale a given height and width to be maximum percentage allowed of the screen width"
[{:keys [height width] :as dimensions}] [{:keys [height width] :as dimensions}]
@ -177,8 +62,3 @@
(def link-preview-title (def link-preview-title
{:margin-horizontal 12 {:margin-horizontal 12
:margin-top 10}) :margin-top 10})
(def link-preview-site
{:margin-horizontal 12
:margin-top 2
:margin-bottom 10})

View File

@ -40,13 +40,3 @@
[photo path [photo path
{:size style/default-size {:size style/default-size
:accessibility-label :own-account-photo}])) :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] [re-frame.core :as re-frame]
[status-im.constants :as constants] [status-im.constants :as constants]
[i18n.i18n :as i18n] [i18n.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.chat-icon.screen :as chat-icon] [status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.list-selection :as list-selection] [status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
@ -191,52 +190,3 @@
:accessibility-label :delete-transaccent-button :accessibility-label :delete-transaccent-button
:on-press #(hide-sheet-and-dispatch [:chat.ui/delete-message-not-used-any-more chat-id :on-press #(hide-sheet-and-dispatch [:chat.ui/delete-message-not-used-any-more chat-id
message-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 (ns status-im.ui.screens.chat.styles.main
(:require [quo.design-system.colors :as colors] (:require [quo.design-system.colors :as colors]))
[status-im.ui.components.emoji-thumbnail.utils :as emoji-utils]))
(def toolbar-container (def toolbar-container
{:flex 1 {:flex 1
:align-items :center :align-items :center
:flex-direction :row}) :flex-direction :row})
(def pins-name-view
{:flex 1
:justify-content :center
:align-items :center})
(def chat-name-view (def chat-name-view
{:flex 1 {:flex 1
:justify-content :center}) :justify-content :center})
@ -48,57 +42,6 @@
:padding-vertical 50 :padding-vertical 50
:margin-right 6}) :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 (def loading-text
{:color colors/gray {:color colors/gray
:font-size 15 :font-size 15
@ -107,51 +50,6 @@
:margin-right 4 :margin-right 4
:text-align :center}) :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 (def contact-request
{:width "100%" {:width "100%"
:justify-content :center :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 (ns status-im.ui.screens.chat.utils
(:require [quo.design-system.colors :as colors] (:require [quo.design-system.colors :as colors]
[status-im.ethereum.stateofus :as stateofus]
[i18n.i18n :as i18n] [i18n.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts] [status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.react :as react])) [status-im.ui.components.react :as react]))
(def ^:private reply-symbol "↪ ")
(defn format-author-old (defn format-author-old
([contact] (format-author-old contact nil)) ([contact] (format-author-old contact nil))
([{:keys [names] :as contact} {:keys [modal profile? you?]}] ([{:keys [names] :as contact} {:keys [modal profile? you?]}]
@ -63,29 +60,3 @@
(if (and max-length (> (count first-name) max-length)) (if (and max-length (> (count first-name) max-length))
(str (subs first-name 0 max-length) "...") (str (subs first-name 0 max-length) "...")
first-name)])))) 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] [quo.design-system.colors :as colors]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[reagent.core :as reagent] [reagent.core :as reagent]
[status-im.constants :as constants]
[status-im.ens.core :as ens] [status-im.ens.core :as ens]
[status-im.ethereum.core :as ethereum] [status-im.ethereum.core :as ethereum]
[status-im.ethereum.ens :as ethereum.ens] [status-im.ethereum.ens :as ethereum.ens]
@ -21,7 +20,6 @@
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.toolbar :as toolbar] [status-im.ui.components.toolbar :as toolbar]
[status-im.ui.components.topbar :as topbar] [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.photos :as photos]
[status-im.ui.screens.chat.utils :as chat.utils] [status-im.ui.screens.chat.utils :as chat.utils]
[status-im.ui.screens.profile.components.views :as profile.components] [status-im.ui.screens.profile.components.views :as profile.components]
@ -762,18 +760,12 @@
:action-fn #(re-frame/dispatch [:bottom-sheet/show-sheet :action-fn #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content {:content
(fn [] (name-list names preferred-name))}])}]])] (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
[react/view {:padding-left 72} [react/view {:padding-left 72}
[my-name]] [my-name]]
[react/view {:flex-direction :row} [react/view {:flex-direction :row}
[react/view {:padding-left 16 :padding-top 4} [react/view {:padding-left 16 :padding-top 4}
[photos/photo (multiaccounts/displayed-photo account) {:size 36}]] [photos/photo (multiaccounts/displayed-photo account) {:size 36}]]]]]])
[message/->message message {:on-long-press identity}]]])]])
(views/defview main (views/defview main
[] []

View File

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

View File

@ -11,14 +11,11 @@
[status-im.ui.components.chat-icon.screen :as chat-icon] [status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.icons.icons :as icons] [status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.keyboard-avoid-presentation :as kb-presentation] [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.react :as react]
[status-im.ui.components.toolbar :as toolbar] [status-im.ui.components.toolbar :as toolbar]
[status-im.ui.components.topbar :as topbar] [status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.profile.components.sheets :as sheets] [status-im.ui.screens.profile.components.sheets :as sheets]
[status-im.ui.screens.profile.contact.styles :as styles] [status-im.ui.screens.profile.contact.styles :as styles]
[status-im.ui.screens.status.views :as status.views]
[status-im.utils.utils :as utils]) [status-im.utils.utils :as utils])
(:require-macros [status-im.utils.views :as views])) (:require-macros [status-im.utils.views :as views]))
@ -210,18 +207,6 @@
[] []
(let [{:keys [public-key name ens-verified] :as contact} @(re-frame/subscribe (let [{:keys [public-key name ens-verified] :as contact} @(re-frame/subscribe
[:contacts/current-contact]) [: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 on-share #(re-frame/dispatch
[:show-popover [:show-popover
(merge (merge
@ -237,43 +222,4 @@
:on-press on-share}] :on-press on-share}]
:left-accessories [{:icon :main-icons/close :left-accessories [{:icon :main-icons/close
:accessibility-label :back-button :accessibility-label :back-button
:on-press #(re-frame/dispatch [:navigate-back])}]}] :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}]])))

View File

@ -11,7 +11,6 @@
[status-im.ui.components.profile-header.view :as profile-header] [status-im.ui.components.profile-header.view :as profile-header]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.topbar :as topbar] [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.photos :as photos]
[status-im.ui.screens.chat.sheets :as chat.sheets] [status-im.ui.screens.chat.sheets :as chat.sheets]
[status-im.ui.screens.chat.utils :as chat.utils] [status-im.ui.screens.chat.utils :as chat.utils]
@ -111,21 +110,16 @@
(debounce/dispatch-and-chill event 2000)) (debounce/dispatch-and-chill event 2000))
(defn invitation-sheet (defn invitation-sheet
[{:keys [introduction-message id]} contact] [{:keys [id]} contact]
(let [members @(re-frame/subscribe [:contacts/current-chat-contacts]) (let [members @(re-frame/subscribe [:contacts/current-chat-contacts])
allow-adding-members? (< (count members) constants/max-group-chat-participants)] allow-adding-members? (< (count members) constants/max-group-chat-participants)]
[react/view [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 {:margin-bottom 8 :margin-right 16}
[react/view {:padding-left 72} [react/view {:padding-left 72}
(chat.utils/format-author-old contact)] (chat.utils/format-author-old contact)]
[react/view {:flex-direction :row :align-items :flex-end} [react/view {:flex-direction :row :align-items :flex-end}
[react/view {:padding-left 16 :padding-top 4} [react/view {:padding-left 16 :padding-top 4}
[photos/photo (multiaccounts/displayed-photo contact) {:size 36}]] [photos/photo (multiaccounts/displayed-photo contact) {:size 36}]]]]
[message/->message message {:on-long-press identity}]]])
[quo/list-item [quo/list-item
{:theme :accent {:theme :accent
:disabled (not allow-adding-members?) :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.tabs.views :as browser.tabs]
[status-im.ui.screens.browser.views :as browser] [status-im.ui.screens.browser.views :as browser]
[status-im.ui.screens.bug-report :as bug-report] [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.channel-details :as communities.channel-details]
[status-im.ui.screens.communities.community :as community] [status-im.ui.screens.communities.community :as community]
[status-im.ui.screens.communities.community-emoji-thumbnail-picker :as [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.qr-scanner.views :as qr-scanner]
[status-im.ui.screens.reset-password.views :as reset-password] [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.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.stickers.views :as stickers]
[status-im.ui.screens.sync-settings.views :as sync-settings] [status-im.ui.screens.sync-settings.views :as sync-settings]
[status-im.ui.screens.terms-of-service.views :as terms-of-service] [status-im.ui.screens.terms-of-service.views :as terms-of-service]
@ -200,11 +197,6 @@
:component onboarding.phrase/wizard-recovery-success} :component onboarding.phrase/wizard-recovery-success}
;;CHAT ;;CHAT
;Pinned messages
{:name :chat-pinned-messages
;TODO custom subtitle
:options {:topBar {:visible false}}
:component pin-messages/pinned-messages}
{:name :group-chat-profile {:name :group-chat-profile
;;TODO animated-header ;;TODO animated-header
@ -388,13 +380,6 @@
:options {:topBar {:visible false}} :options {:topBar {:visible false}}
:component wallet.swap/asset-selector} :component wallet.swap/asset-selector}
;;MY STATUS
{:name :status
:on-focus [:init-timeline-chat]
:insets {:top true}
:component status.views/timeline}
;;PROFILE ;;PROFILE
{:name :my-profile {:name :my-profile
@ -696,12 +681,6 @@
:options {:topBar {:visible false}} :options {:topBar {:visible false}}
:component wallet.collectibles/nft-details-modal} :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 ;[Browser] New bookmark
{:name :new-bookmark {:name :new-bookmark
:insets {:bottom true} :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] [quo2.foundations.colors :as colors]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[reagent.core :as reagent] [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] [status-im.chat.models.mentions :as mentions]
[i18n.i18n :as i18n] [i18n.i18n :as i18n]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]

View File

@ -2,7 +2,6 @@
(:require (:require
[quo.design-system.colors :as quo.colors] [quo.design-system.colors :as quo.colors]
[quo.react-native :as rn] [quo.react-native :as rn]
[quo2.components.avatars.user-avatar :as user-avatar]
[quo2.components.icon :as icons] [quo2.components.icon :as icons]
[quo2.components.markdown.text :as text] [quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors] [quo2.foundations.colors :as colors]
@ -16,9 +15,7 @@
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.screens.chat.image.preview.views :as preview] [status-im.ui.screens.chat.image.preview.views :as preview]
[status-im.ui.screens.chat.message.audio :as message.audio] [status-im.ui.screens.chat.message.audio :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.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.sheets :as sheets]
[status-im.ui.screens.chat.styles.message.message :as style] [status-im.ui.screens.chat.styles.message.message :as style]
[status-im.ui.screens.chat.utils :as chat.utils] [status-im.ui.screens.chat.utils :as chat.utils]
@ -34,8 +31,6 @@
[quo2.core :as quo]) [quo2.core :as quo])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
(def edited-at-text (str " ⌫ " (i18n/label :t/edited)))
(defn system-text? (defn system-text?
[content-type] [content-type]
(= content-type constants/content-type-system-text)) (= content-type constants/content-type-system-text))
@ -208,46 +203,6 @@
(assoc props :accessibility-label :message-timestamp) (assoc props :accessibility-label :message-timestamp)
(datetime/to-short-str 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-width 260)
(def image-max-height 192) (def image-max-height 192)
@ -265,10 +220,6 @@
(defmulti ->message :content-type) (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 (defmethod ->message constants/content-type-gap
[message] [message]
[message.gap/gap message]) [message.gap/gap message])

View File

@ -223,3 +223,16 @@
3 72.5 3 72.5
4 72.5 4 72.5
5 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 (:require
[i18n.i18n :as i18n] [i18n.i18n :as i18n]
[quo2.foundations.colors :as colors] [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] [taoensso.timbre :as log]
[utils.datetime :as datetime] [utils.datetime :as datetime]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))

View File

@ -2,7 +2,7 @@
(:require (:require
[i18n.i18n :as i18n] [i18n.i18n :as i18n]
[quo2.foundations.colors :as colors] [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] [taoensso.timbre :as log]
[utils.datetime :as datetime] [utils.datetime :as datetime]
[utils.re-frame :as rf])) [utils.re-frame :as rf]))

View File

@ -1,12 +1,12 @@
(ns status-im.chat.models.message-list (ns status-im2.contexts.chat.messages.list.events
(:require ["functional-red-black-tree" :as rb-tree] (:require [utils.red-black-tree :as red-black-tree]
[status-im.constants :as constants]
[utils.datetime :as datetime] [utils.datetime :as datetime]
[utils.re-frame :as rf])) [utils.re-frame :as rf]
[status-im2.common.constants :as constants]))
(defn- add-datemark (defn- add-datemark
[{:keys [whisper-timestamp] :as msg}] [{:keys [whisper-timestamp] :as msg}]
;;TODO this is slow ;;NOTE(performance) this is slow
(assoc msg :datemark (datetime/day-relative whisper-timestamp))) (assoc msg :datemark (datetime/day-relative whisper-timestamp)))
(defn- add-timestamp (defn- add-timestamp
@ -40,9 +40,6 @@
add-datemark add-datemark
add-timestamp)) add-timestamp))
;; any message that comes after this amount of ms will be grouped separately
(def ^:private group-ms 300000)
(defn same-group? (defn same-group?
"Whether a message is in the same group as the one after it. "Whether a message is in the same group as the one after it.
We check the time, and the author" We check the time, and the author"
@ -51,7 +48,7 @@
(not (:system-message? a)) (not (:system-message? a))
(not (:system-message? b)) (not (:system-message? b))
(= (:from a) (:from 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? (defn display-photo?
"We display photos for other users, and not in 1-to-1 chats" "We display photos for other users, and not in 1-to-1 chats"
@ -126,50 +123,31 @@
(not one-to-one?)) (not one-to-one?))
:last-in-group? last-in-group?))) :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 (defn update-message
"Update the message and siblings with positional info" "Update the message and siblings with positional info"
[^js tree message] [tree message]
(let [^js iter (.find tree message) (let [iter (red-black-tree/find tree message)
^js previous-message (when (.-hasPrev iter) previous-message (red-black-tree/get-prev iter)
(get-prev-element iter)) next-message (red-black-tree/get-next iter)
^js next-message (when (.-hasNext iter) message-with-pos-data (add-group-info message previous-message next-message)]
(get-next-element iter)) (cond-> (red-black-tree/update iter message-with-pos-data)
^js message-with-pos-data (add-group-info message previous-message next-message)]
(cond-> (.update iter message-with-pos-data)
next-message next-message
(-> ^js (.find next-message) (-> (red-black-tree/find next-message)
(.update (update-next-message message-with-pos-data next-message))) (red-black-tree/update (update-next-message message-with-pos-data next-message)))
(and previous-message (and previous-message
(not= :datemark (:type previous-message))) (not= :datemark (:type previous-message)))
(-> ^js (.find previous-message) (-> (red-black-tree/find previous-message)
(.update (update-previous-message message-with-pos-data previous-message)))))) (red-black-tree/update (update-previous-message message-with-pos-data previous-message))))))
(defn remove-message (defn remove-message
"Remove a message in the list" "Remove a message in the list"
[^js tree prepared-message] [tree prepared-message]
(let [iter (.find tree prepared-message)] (let [iter (red-black-tree/find tree prepared-message)]
(if (not iter) (if (not iter)
tree tree
(let [^js new-tree (.remove iter) (let [new-tree (red-black-tree/remove iter)
^js next-message (when (.-hasNext iter) next-message (red-black-tree/get-next iter)]
(get-next-element iter))]
(if (not next-message) (if (not next-message)
new-tree new-tree
(update-message new-tree next-message)))))) (update-message new-tree next-message))))))
@ -179,13 +157,13 @@
its positional metadata, and update the left & right messages if necessary, 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 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)" we need to re-find (there's probably a better way)"
[^js old-message-list prepared-message] [old-message-list prepared-message]
(let [^js tree (.insert old-message-list prepared-message prepared-message)] (let [tree (red-black-tree/insert old-message-list prepared-message)]
(update-message tree prepared-message))) (update-message tree prepared-message)))
(defn add (defn add
[message-list message] [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 (defn add-many
[message-list messages] [message-list messages]
@ -194,9 +172,9 @@
messages)) messages))
(defn ->seq (defn ->seq
[^js message-list] [message-list]
(if 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 ;; 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]] (: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]])) [taoensso.tufte :as tufte :refer-macros [defnp profile]]))
(deftest message-stream-tests (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)) (defonce first-not-visible-item (atom nil))

View File

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

View File

@ -1,6 +1,6 @@
(ns status-im2.contexts.chat.messages.pin.events (ns status-im2.contexts.chat.messages.pin.events
(:require [re-frame.core :as re-frame] (: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.constants :as constants]
[status-im.data-store.pin-messages :as data-store.pin-messages] [status-im.data-store.pin-messages :as data-store.pin-messages]
[status-im.transport.message.protocol :as protocol] [status-im.transport.message.protocol :as protocol]

View File

@ -1,7 +1,7 @@
(ns status-im2.subs.chat.messages (ns status-im2.subs.chat.messages
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.chat.db :as chat.db] [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] [status-im.chat.models.reactions :as models.reactions]
[utils.datetime :as datetime] [utils.datetime :as datetime]
[status-im2.common.constants :as constants])) [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)))