cleaning (#14808)
cleaning, introduce react-native.red-black-tree and move messages list events
This commit is contained in:
parent
9a60fc1600
commit
ed348e0871
|
@ -1,7 +1,7 @@
|
|||
status-im.utils.build/warning-handler
|
||||
status-im.utils.build/get-current-sha
|
||||
status-im.chat.constants/spacing-char
|
||||
status-im.chat.constants/arg-wrapping-char
|
||||
status-im2.common.constants/spacing-char
|
||||
status-im2.common.constants/arg-wrapping-char
|
||||
status-im.ios.core/init
|
||||
status-im.ui.components.camera/aspects
|
||||
status-im.ui.components.camera/capture-targets
|
||||
|
|
|
@ -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"
|
||||
]]
|
||||
]
|
||||
}
|
|
@ -58,8 +58,7 @@
|
|||
;; in the SHADOW_HOST env variable to make sure that
|
||||
;; it will use the right interface
|
||||
:local-ip #shadow/env "SHADOW_HOST"}
|
||||
:chunks {:fleets status-im.fleet.default-fleet/default-fleets
|
||||
:chats status-im.chat.default-chats/default-chats}
|
||||
:chunks {:fleets status-im.fleet.default-fleet/default-fleets}
|
||||
:release
|
||||
{:closure-defines
|
||||
{status-im.utils.config/POKT_TOKEN #shadow/env "POKT_TOKEN"
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
(ns mocks.js-dependencies
|
||||
(:require-macros [status-im.utils.slurp :refer [slurp]])
|
||||
(:require [status-im.fleet.default-fleet :refer (default-fleets)])
|
||||
(:require [status-im.utils.test :as utils.test])
|
||||
(:require [status-im.chat.default-chats :refer (default-chats)]))
|
||||
(:require [status-im.utils.test :as utils.test]))
|
||||
|
||||
;; to generate a js Proxy at js/__STATUS_MOBILE_JS_IDENTITY_PROXY__ that accept any (.xxx) call and
|
||||
;; return itself
|
||||
|
@ -398,7 +397,6 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
|
|||
"../src/js/bottom_sheet.js" bottom-sheet
|
||||
"../src/js/record_audio_worklets.js" record-audio-worklets
|
||||
"./fleets.js" default-fleets
|
||||
"./chats.js" default-chats
|
||||
"@walletconnect/client" wallet-connect-client
|
||||
"../translations/ar.json" (js/JSON.parse (slurp "./translations/ar.json"))
|
||||
"../translations/de.json" (js/JSON.parse (slurp "./translations/de.json"))
|
||||
|
|
|
@ -109,3 +109,11 @@
|
|||
(let [fn-ref (use-ref f)]
|
||||
(oops/oset! fn-ref "current" f)
|
||||
(use-effect-once (fn [] (fn [] (oops/ocall! fn-ref "current"))))))
|
||||
|
||||
(def layout-animation (.-LayoutAnimation ^js react-native))
|
||||
(def configure-next (.-configureNext ^js layout-animation))
|
||||
|
||||
(def layout-animation-presets
|
||||
{:ease-in-ease-out (-> ^js layout-animation .-Presets .-easeInEaseOut)
|
||||
:linear (-> ^js layout-animation .-Presets .-linear)
|
||||
:spring (-> ^js layout-animation .-Presets .-spring)})
|
||||
|
|
|
@ -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)
|
|
@ -5,10 +5,6 @@
|
|||
[{:keys [public? name]}]
|
||||
(str (when public? "#") name))
|
||||
|
||||
(defn datemark?
|
||||
[{:keys [type]}]
|
||||
(= type :datemark))
|
||||
|
||||
(defn intersperse-datemark
|
||||
"Reduce step which expects the input list of messages to be sorted by clock value.
|
||||
It makes best effort to group them by day.
|
||||
|
|
|
@ -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"))
|
|
@ -4,27 +4,22 @@
|
|||
[re-frame.core :as re-frame]
|
||||
[status-im.add-new.db :as new-public-chat.db]
|
||||
[status-im.chat.models.loading :as loading]
|
||||
[status-im.chat.models.message-list :as message-list]
|
||||
[status-im2.contexts.chat.messages.list.events :as message-list]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.data-store.chats :as chats-store]
|
||||
[status-im.data-store.contacts :as contacts-store]
|
||||
[i18n.i18n :as i18n]
|
||||
[status-im.mailserver.core :as mailserver]
|
||||
[status-im.multiaccounts.model :as multiaccounts.model]
|
||||
[status-im.ui.screens.chat.state :as chat.state]
|
||||
[status-im2.contexts.chat.messages.list.state :as chat.state]
|
||||
[status-im.utils.clocks :as utils.clocks]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.utils.types :as types]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im2.contexts.chat.messages.delete-message-for-me.events :as delete-for-me]
|
||||
[status-im2.contexts.chat.messages.delete-message.events :as delete-message]
|
||||
[status-im2.navigation.events :as navigation]
|
||||
[taoensso.timbre :as log]))
|
||||
|
||||
(defn chats
|
||||
[]
|
||||
(:chats (types/json->clj (js/require "./chats.js"))))
|
||||
|
||||
(defn- get-chat
|
||||
[cofx chat-id]
|
||||
(get-in cofx [:db :chats chat-id]))
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
[clojure.string :as string]
|
||||
[goog.object :as object]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.chat.constants :as chat.constants]
|
||||
[status-im2.common.constants :as chat.constants]
|
||||
[status-im.chat.models :as chat]
|
||||
[status-im.chat.models.mentions :as mentions]
|
||||
[status-im.chat.models.message :as chat.message]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(ns status-im.chat.models.input-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.chat.constants :as constants]
|
||||
[status-im2.common.constants :as constants]
|
||||
[status-im.chat.models.input :as input]
|
||||
[utils.datetime :as datetime]))
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(ns status-im.chat.models.loading
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.chat.models.message-list :as message-list]
|
||||
[status-im2.contexts.chat.messages.list.events :as message-list]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.data-store.chats :as data-store.chats]
|
||||
[status-im.data-store.messages :as data-store.messages]
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
[status-im.chat.models :as chat-model]
|
||||
[status-im.chat.models.loading :as chat.loading]
|
||||
[status-im.chat.models.mentions :as mentions]
|
||||
[status-im.chat.models.message-list :as message-list]
|
||||
[status-im2.contexts.chat.messages.list.events :as message-list]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.data-store.messages :as data-store.messages]
|
||||
[status-im.transport.message.protocol :as protocol]
|
||||
[status-im.ui.screens.chat.state :as view.state]
|
||||
[status-im2.contexts.chat.messages.list.state :as view.state]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im.utils.gfycat.core :as gfycat]
|
||||
[status-im.utils.platform :as platform]
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
(ns status-im.chat.models.message-content
|
||||
(:require [status-im.constants :as constants]))
|
||||
|
||||
(def stylings
|
||||
[[:bold constants/regx-bold]
|
||||
[:italic constants/regx-italic]
|
||||
[:backquote constants/regx-backquote]])
|
||||
|
||||
(defn emoji-only-content?
|
||||
"Determines if text is just an emoji"
|
||||
[{:keys [text response-to]}]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.chat.models.loading :as loading]
|
||||
[status-im.chat.models.message :as message]
|
||||
[status-im.ui.screens.chat.state :as view.state]))
|
||||
[status-im2.contexts.chat.messages.list.state :as list.state]))
|
||||
|
||||
(deftest add-received-message-test
|
||||
(with-redefs [message/add-message #(identity %1)]
|
||||
|
@ -26,7 +26,7 @@
|
|||
;; <- message
|
||||
;; <- top of the chat
|
||||
(testing "there's no hidden item"
|
||||
(with-redefs [view.state/first-not-visible-item (atom nil)]
|
||||
(with-redefs [list.state/first-not-visible-item (atom nil)]
|
||||
(is
|
||||
(=
|
||||
{:db
|
||||
|
@ -46,7 +46,7 @@
|
|||
;; <- message
|
||||
;; <- top of the chat
|
||||
(testing "the hidden item has a clock value less than the current"
|
||||
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (dec clock-value)})]
|
||||
(with-redefs [list.state/first-not-visible-item (atom {:clock-value (dec clock-value)})]
|
||||
(is
|
||||
(=
|
||||
{:db
|
||||
|
@ -66,7 +66,7 @@
|
|||
;; <- first-hidden-item
|
||||
;; <- top of the chat
|
||||
(testing "the message falls between the first-hidden-item and cursor"
|
||||
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
|
||||
(with-redefs [list.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
|
||||
(let [result (dissoc (message/receive-many
|
||||
cofx
|
||||
#js {:messages (to-array [message])})
|
||||
|
@ -81,7 +81,7 @@
|
|||
;; <- first-hidden-item
|
||||
;; <- top of the chat
|
||||
(testing "the message falls between the first-hidden-item and cursor is nil"
|
||||
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
|
||||
(with-redefs [list.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
|
||||
(let [result (dissoc (message/receive-many
|
||||
(update-in cofx
|
||||
[:db :pagination-info chat-id]
|
||||
|
@ -101,7 +101,7 @@
|
|||
;; <- first-hidden-item
|
||||
;; <- top of the chat
|
||||
(testing "the message falls before both the first-hidden-item and cursor"
|
||||
(with-redefs [view.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
|
||||
(with-redefs [list.state/first-not-visible-item (atom {:clock-value (inc clock-value)})]
|
||||
(let [message #js
|
||||
{:localChatId chat-id
|
||||
:clock (- clock-value 2)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(ns status-im.contact.block
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.chat.models.message-list :as message-list]
|
||||
[status-im2.contexts.chat.messages.list.events :as message-list]
|
||||
[status-im.contact.db :as contact.db]
|
||||
[status-im.data-store.chats :as chats-store]
|
||||
[status-im.data-store.contacts :as contacts-store]
|
||||
|
|
|
@ -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?)
|
|
@ -4,7 +4,6 @@
|
|||
[quo.react-native :as rn]
|
||||
[re-frame.core :as re-frame.core]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.profile.db :as profile.db]
|
||||
[status-im.ui.components.chat-icon.styles :as styles]
|
||||
[status-im.ui.components.icons.icons :as icons]
|
||||
[status-im.ui.screens.chat.photos :as photos]
|
||||
|
@ -55,7 +54,8 @@
|
|||
styles/container-chat-list
|
||||
photo-container)
|
||||
size (:width photo-container)
|
||||
identicon? (when photo-path (profile.db/base64-png? photo-path))
|
||||
identicon? (and photo-path
|
||||
(string/starts-with? photo-path "data:image/png;base64,"))
|
||||
dot-styles (visibility-status-utils/icon-visibility-status-dot
|
||||
public-key
|
||||
size
|
||||
|
|
|
@ -3,11 +3,9 @@
|
|||
[quo.design-system.colors :as colors]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.add-new.db :as db]
|
||||
[status-im.chat.models :as chat.models]
|
||||
[status-im.react-native.resources :as resources]
|
||||
[status-im.ui.components.icons.icons :as icons]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im2.setup.i18n-resources :as i18n-resources]
|
||||
[i18n.i18n :as i18n])
|
||||
(:require-macros [status-im.utils.views :as views]))
|
||||
|
||||
|
@ -37,59 +35,6 @@
|
|||
:auto-correct false
|
||||
:error error}])
|
||||
|
||||
(defn render-topic
|
||||
[topic]
|
||||
[react/touchable-highlight
|
||||
{:on-press #(start-chat topic)
|
||||
:accessibility-label :chat-item}
|
||||
[react/view
|
||||
{:border-color colors/gray-lighter
|
||||
:border-radius 36
|
||||
:border-width 1
|
||||
:padding-horizontal 8
|
||||
:padding-vertical 5
|
||||
:margin-right 8
|
||||
:margin-vertical 8}
|
||||
[react/text {:style {:color colors/blue :typography :main-medium}}
|
||||
(str "#" topic)]]])
|
||||
|
||||
(def lang-names
|
||||
{"es" "status-espanol"
|
||||
"pt" "statusbrasil"
|
||||
"de" "status-german"
|
||||
"fr" "status-french"
|
||||
"it" "status-italiano"
|
||||
"ru" "status-russian"
|
||||
"zh" "status-chinese"
|
||||
"ko" "status-korean"
|
||||
"ja" "status-japanese"
|
||||
"fa" "status-farsi"
|
||||
"tr" "status-turkish"
|
||||
"id" "indonesian"
|
||||
"in" "indonesian"
|
||||
"hi" "status-indian"
|
||||
"ar" "status-arabic"
|
||||
"fil" "status-filipino"
|
||||
"nl" "status-dutch"})
|
||||
|
||||
(defn get-language-topic
|
||||
[]
|
||||
(let [lang (subs (name i18n-resources/default-device-language) 0 2)
|
||||
lang3 (subs (name i18n-resources/default-device-language) 0 3)
|
||||
lang-name (or (get lang-names lang3) (get lang-names lang))]
|
||||
(when-not (= lang "en")
|
||||
(or lang-name (str "status-" lang)))))
|
||||
|
||||
(def section-featured "Featured")
|
||||
|
||||
(defn featured-public-chats
|
||||
[]
|
||||
(let [lang-topic (get-language-topic)
|
||||
chats (some #(when (= section-featured (first %)) (second %)) (chat.models/chats))]
|
||||
(if lang-topic
|
||||
(conj chats lang-topic)
|
||||
chats)))
|
||||
|
||||
(views/defview new-public-chat
|
||||
[]
|
||||
(views/letsubs [topic [:public-group-topic]
|
||||
|
|
|
@ -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})
|
|
@ -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)}))))]]]])))
|
|
@ -9,7 +9,7 @@
|
|||
[quo.react-native :as rn]
|
||||
[re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.chat.constants :as chat.constants]
|
||||
[status-im2.common.constants :as chat.constants]
|
||||
[status-im.chat.models.mentions :as mentions]
|
||||
[i18n.i18n :as i18n]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
|
|
|
@ -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)]]]]])
|
|
@ -4,11 +4,8 @@
|
|||
[re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[i18n.i18n :as i18n]
|
||||
[status-im.ui.components.list-selection :as list-selection]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.screens.chat.styles.main :as style]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.universal-links.utils :as links]
|
||||
[utils.debounce :as debounce])
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
||||
|
||||
|
@ -76,124 +73,3 @@
|
|||
(letsubs [invitations [:group-chat/invitations-by-chat-id chat-id]]
|
||||
(when invitation-admin
|
||||
[request-membership (first invitations)])))
|
||||
|
||||
(def group-chat-description-loading
|
||||
[react/view
|
||||
{:style (merge style/intro-header-description-container
|
||||
{:margin-bottom 36
|
||||
:height 44})}
|
||||
[react/text {:style style/intro-header-description}
|
||||
(i18n/label :t/loading)]
|
||||
[react/activity-indicator
|
||||
{:animating true
|
||||
:size :small
|
||||
:color colors/gray}]])
|
||||
|
||||
(defn calculate-quiet-time
|
||||
[synced-to
|
||||
synced-from]
|
||||
(when synced-from
|
||||
(let [quiet-hours (quot (- synced-to synced-from)
|
||||
(* 60 60))]
|
||||
(if (<= quiet-hours 24)
|
||||
(i18n/label :t/quiet-hours
|
||||
{:quiet-hours quiet-hours})
|
||||
(i18n/label :t/quiet-days
|
||||
{:quiet-days (quot quiet-hours 24)})))))
|
||||
|
||||
(defview no-messages-community-chat-description-container
|
||||
[chat-id]
|
||||
(letsubs [{:keys [synced-to synced-from]}
|
||||
[:chats/synced-to-and-from chat-id]]
|
||||
[react/text
|
||||
{:style (merge style/intro-header-description
|
||||
{:margin-bottom 36})}
|
||||
(let [quiet-time (calculate-quiet-time synced-to
|
||||
synced-from)]
|
||||
(i18n/label :t/empty-chat-description-community
|
||||
{:quiet-hours quiet-time}))]))
|
||||
|
||||
(defview no-messages-private-group-chat-description-container
|
||||
[chat-id]
|
||||
(letsubs [{:keys [synced-to synced-from]}
|
||||
[:chats/synced-to-and-from chat-id]]
|
||||
(let [quiet-time (calculate-quiet-time synced-to
|
||||
synced-from)]
|
||||
[react/nested-text
|
||||
{:style (merge style/intro-header-description
|
||||
{:margin-bottom 36})}
|
||||
(if quiet-time
|
||||
(i18n/label :t/empty-chat-description-public
|
||||
{:quiet-hours quiet-time})
|
||||
(i18n/label :t/cleared-chat-description-public))
|
||||
[{:style {:color colors/blue}
|
||||
:on-press #(list-selection/open-share
|
||||
{:message
|
||||
(i18n/label
|
||||
:t/share-public-chat-text
|
||||
{:link (links/generate-link :public-chat :external chat-id)})})}
|
||||
(i18n/label :t/empty-chat-description-public-share-this)]])))
|
||||
|
||||
(defview pending-invitation-description
|
||||
[inviter-pk chat-name]
|
||||
(letsubs [inviter-name [:contacts/contact-name-by-identity inviter-pk]]
|
||||
[react/nested-text {:style style/intro-header-description}
|
||||
[{:style {:color colors/black}} inviter-name]
|
||||
(i18n/label :t/join-group-chat-description
|
||||
{:username ""
|
||||
:group-name chat-name})]))
|
||||
|
||||
(defview joined-group-chat-description
|
||||
[inviter-pk chat-name]
|
||||
(letsubs [inviter-name [:contacts/contact-name-by-identity inviter-pk]]
|
||||
[react/nested-text {:style style/intro-header-description}
|
||||
(i18n/label :t/joined-group-chat-description
|
||||
{:username ""
|
||||
:group-name chat-name})
|
||||
[{:style {:color colors/black}} inviter-name]]))
|
||||
|
||||
(defn created-group-chat-description
|
||||
[chat-name]
|
||||
[react/text {:style style/intro-header-description}
|
||||
(i18n/label :t/created-group-chat-description
|
||||
{:group-name chat-name})])
|
||||
|
||||
(defview group-chat-inviter-description-container
|
||||
[chat-id chat-name]
|
||||
(letsubs [{:keys [member? inviter-pk]}
|
||||
[:group-chat/inviter-info chat-id]]
|
||||
(cond
|
||||
(not member?)
|
||||
[pending-invitation-description inviter-pk chat-name]
|
||||
inviter-pk
|
||||
[joined-group-chat-description inviter-pk chat-name]
|
||||
:else
|
||||
[created-group-chat-description chat-name])))
|
||||
|
||||
(defn group-chat-membership-description
|
||||
[]
|
||||
[react/text {:style {:text-align :center :margin-horizontal 30}}
|
||||
(i18n/label :t/membership-description)])
|
||||
|
||||
(defn group-chat-description-container
|
||||
[{:keys [invitation-admin
|
||||
chat-id
|
||||
chat-name
|
||||
chat-type
|
||||
loading-messages?
|
||||
no-messages?]}]
|
||||
(cond
|
||||
loading-messages?
|
||||
group-chat-description-loading
|
||||
|
||||
(and no-messages? (= chat-type constants/public-chat-type))
|
||||
[no-messages-private-group-chat-description-container chat-id]
|
||||
|
||||
(and no-messages? (= chat-type constants/community-chat-type))
|
||||
[no-messages-community-chat-description-container chat-id]
|
||||
|
||||
invitation-admin
|
||||
[group-chat-membership-description]
|
||||
|
||||
(= chat-type constants/private-group-chat-type)
|
||||
[group-chat-inviter-description-container chat-id chat-name]))
|
||||
|
|
|
@ -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]]]])
|
|
@ -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)))]]]))))
|
|
@ -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])]))]]]))
|
|
@ -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)]]])
|
|
@ -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)]]])
|
|
@ -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])))))
|
|
@ -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]])])
|
|
@ -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)]]]))
|
|
@ -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}]]])]))))
|
|
@ -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}]]])]))))
|
|
@ -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}]]]])))))
|
|
@ -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])]))
|
|
@ -3,20 +3,6 @@
|
|||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.screens.chat.styles.photos :as photos]))
|
||||
|
||||
(defn picker-wrapper-style
|
||||
[{:keys [display-photo? outgoing timeline]}]
|
||||
(merge {:flex-direction :row
|
||||
:flex 1
|
||||
:padding-top 4
|
||||
:padding-right 8}
|
||||
(if outgoing
|
||||
{:justify-content :flex-end}
|
||||
{:justify-content :flex-start})
|
||||
(when-not timeline
|
||||
(if display-photo?
|
||||
{:padding-left (+ 16 photos/default-size)}
|
||||
{:padding-left 8}))))
|
||||
|
||||
(defn container-style
|
||||
[{:keys [outgoing timeline]}]
|
||||
(merge {:border-top-left-radius 16
|
||||
|
@ -31,72 +17,11 @@
|
|||
{:border-top-right-radius 4}
|
||||
{:border-top-left-radius 4}))))
|
||||
|
||||
(defn reactions-picker-row
|
||||
[]
|
||||
{:flex-direction :row
|
||||
:padding-vertical 8
|
||||
:padding-horizontal 8})
|
||||
|
||||
(defn quick-actions-container
|
||||
[]
|
||||
{:flex-direction :column
|
||||
:justify-content :space-evenly
|
||||
:border-top-width 1
|
||||
:border-top-color (:ui-01 @colors/theme)})
|
||||
|
||||
(defn quick-actions-row
|
||||
[]
|
||||
{:flex-direction :row
|
||||
:padding-horizontal 16
|
||||
:padding-vertical 12
|
||||
:justify-content :space-between
|
||||
:border-top-width 1
|
||||
:border-top-color (:ui-01 @colors/theme)})
|
||||
|
||||
(defn reaction-style
|
||||
[{:keys [outgoing own]}]
|
||||
(merge {:border-top-left-radius 10
|
||||
:border-top-right-radius 10
|
||||
:border-bottom-right-radius 10
|
||||
:border-bottom-left-radius 10
|
||||
:flex-direction :row
|
||||
:margin-vertical 2
|
||||
:padding-right 8
|
||||
:padding-left 2
|
||||
:padding-vertical 2}
|
||||
(if own
|
||||
{:background-color (:interactive-01 @colors/theme)}
|
||||
{:background-color (:interactive-02 @colors/theme)})
|
||||
(if outgoing
|
||||
{:border-top-right-radius 2
|
||||
:margin-left 4}
|
||||
{:border-top-left-radius 2
|
||||
:margin-right 4})))
|
||||
|
||||
(defn reaction-quantity-style
|
||||
[{:keys [own]}]
|
||||
{:font-size 12
|
||||
:line-height 16
|
||||
:color (if own
|
||||
colors/white
|
||||
(:text-01 @colors/theme))})
|
||||
|
||||
(def screen-width
|
||||
(-> "window"
|
||||
react/get-dimensions
|
||||
:width))
|
||||
|
||||
(defn reactions-row-old
|
||||
[{:keys [outgoing display-photo?]} timeline]
|
||||
(merge {:flex-direction :row
|
||||
:padding-right 8}
|
||||
(if (and outgoing (not timeline))
|
||||
{:justify-content :flex-end}
|
||||
{:justify-content :flex-start})
|
||||
(if (or display-photo? timeline)
|
||||
{:padding-left (+ 30 photos/default-size (when timeline 8))}
|
||||
{:padding-left 30})))
|
||||
|
||||
(defn reactions-row
|
||||
[timeline margin-top]
|
||||
{:flex-direction :row
|
||||
|
@ -108,49 +33,9 @@
|
|||
:max-width (- screen-width (+ 30 photos/default-size (when timeline 8)))
|
||||
:margin-left (+ 30 photos/default-size (when timeline 8))})
|
||||
|
||||
(defn reaction-button
|
||||
[active]
|
||||
(merge {:width 40
|
||||
:height 40
|
||||
:border-radius 20
|
||||
:justify-content :center
|
||||
:align-items :center
|
||||
:margin-horizontal 1
|
||||
:border-width 1
|
||||
:border-color :transparent}
|
||||
(when active
|
||||
{:background-color (:interactive-02 @colors/theme)
|
||||
;; FIXME: Use broder color here
|
||||
:border-color "rgba(67, 96, 223, 0.2)"})))
|
||||
|
||||
(defn link-preview-request-wrapper
|
||||
[]
|
||||
{:border-radius 16
|
||||
:border-width 1
|
||||
:border-color colors/gray-lighter
|
||||
:margin-vertical 4
|
||||
:background-color (:ui-background @colors/theme)})
|
||||
|
||||
(def link-preview-request-image
|
||||
{:width 132
|
||||
:height 94
|
||||
:align-self :center})
|
||||
|
||||
(def community-preview-header
|
||||
{:margin 8 :margin-left 12})
|
||||
|
||||
(defn link-preview-wrapper
|
||||
[outgoing timeline]
|
||||
{:overflow :hidden
|
||||
:border-top-left-radius 16
|
||||
:border-top-right-radius 16
|
||||
:border-bottom-left-radius (if timeline 16 (if outgoing 16 4))
|
||||
:border-bottom-right-radius (if timeline 16 (if outgoing 4 16))
|
||||
:border-width 1
|
||||
:border-color colors/gray-lighter
|
||||
:background-color colors/white
|
||||
:margin-vertical 4})
|
||||
|
||||
(defn scale-dimensions
|
||||
"Scale a given height and width to be maximum percentage allowed of the screen width"
|
||||
[{:keys [height width] :as dimensions}]
|
||||
|
@ -177,8 +62,3 @@
|
|||
(def link-preview-title
|
||||
{:margin-horizontal 12
|
||||
:margin-top 10})
|
||||
|
||||
(def link-preview-site
|
||||
{:margin-horizontal 12
|
||||
:margin-top 2
|
||||
:margin-bottom 10})
|
||||
|
|
|
@ -40,13 +40,3 @@
|
|||
[photo path
|
||||
{:size style/default-size
|
||||
:accessibility-label :own-account-photo}]))
|
||||
|
||||
(defn member-identicon
|
||||
[identicon]
|
||||
(let [size style/default-size]
|
||||
[react/view {:style (style/photo-container size)}
|
||||
[fast-image/fast-image
|
||||
{:source {:uri identicon}
|
||||
:style (style/photo size)
|
||||
:accessibility-label :member-photo}]
|
||||
[react/view {:style (style/photo-border size)}]]))
|
||||
|
|
|
@ -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}]]))))
|
|
@ -3,7 +3,6 @@
|
|||
[re-frame.core :as re-frame]
|
||||
[status-im.constants :as constants]
|
||||
[i18n.i18n :as i18n]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.ui.components.chat-icon.screen :as chat-icon]
|
||||
[status-im.ui.components.list-selection :as list-selection]
|
||||
[status-im.ui.components.react :as react]
|
||||
|
@ -191,52 +190,3 @@
|
|||
:accessibility-label :delete-transaccent-button
|
||||
:on-press #(hide-sheet-and-dispatch [:chat.ui/delete-message-not-used-any-more chat-id
|
||||
message-id])}]]))
|
||||
|
||||
(defn image-long-press
|
||||
[{:keys [content identicon from outgoing cant-be-replied] :as message} hide]
|
||||
(let [contact-name @(re-frame/subscribe [:contacts/contact-name-by-identity from])]
|
||||
[react/view
|
||||
(when-not outgoing
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:icon [chat-icon/contact-icon-contacts-tab
|
||||
(multiaccounts/displayed-photo {:identicon identicon
|
||||
:public-key from})]
|
||||
:title contact-name
|
||||
:subtitle (i18n/label :t/view-profile)
|
||||
:accessibility-label :view-chat-details-button
|
||||
:chevron true
|
||||
:on-press #(do
|
||||
(hide)
|
||||
(re-frame/dispatch [:chat.ui/show-profile from]))}])
|
||||
(when-not cant-be-replied
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:title (i18n/label :t/message-reply)
|
||||
:icon :main-icons/reply
|
||||
:on-press #(do
|
||||
(hide)
|
||||
(re-frame/dispatch [:chat.ui/reply-to-message message]))}])
|
||||
;; we have only base64 string for image, so we need to find a way how to copy it
|
||||
#_[quo/list-item
|
||||
{:theme :accent
|
||||
:title :t/sharing-copy-to-clipboard
|
||||
:icon :main-icons/copy
|
||||
:on-press (fn []
|
||||
(re-frame/dispatch [:bottom-sheet/hide])
|
||||
(react/copy-to-clipboard (:image content)))}]
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:title (i18n/label :t/save)
|
||||
:icon :main-icons/download
|
||||
:on-press (fn []
|
||||
(hide)
|
||||
(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)]))}]
|
||||
;; we have only base64 string for image, so we need to find a way how to share it
|
||||
#_[quo/list-item
|
||||
{:theme :accent
|
||||
:title :t/sharing-share
|
||||
:icon :main-icons/share
|
||||
:on-press (fn []
|
||||
(re-frame/dispatch [:bottom-sheet/hide])
|
||||
(list-selection/open-share {:message (:image content)}))}]]))
|
||||
|
|
|
@ -1,17 +1,11 @@
|
|||
(ns status-im.ui.screens.chat.styles.main
|
||||
(:require [quo.design-system.colors :as colors]
|
||||
[status-im.ui.components.emoji-thumbnail.utils :as emoji-utils]))
|
||||
(:require [quo.design-system.colors :as colors]))
|
||||
|
||||
(def toolbar-container
|
||||
{:flex 1
|
||||
:align-items :center
|
||||
:flex-direction :row})
|
||||
|
||||
(def pins-name-view
|
||||
{:flex 1
|
||||
:justify-content :center
|
||||
:align-items :center})
|
||||
|
||||
(def chat-name-view
|
||||
{:flex 1
|
||||
:justify-content :center})
|
||||
|
@ -48,57 +42,6 @@
|
|||
:padding-vertical 50
|
||||
:margin-right 6})
|
||||
|
||||
(defn intro-header-container
|
||||
[loading-messages? no-messages?]
|
||||
(if (or loading-messages? no-messages?)
|
||||
{:flex 1
|
||||
:flex-direction :column
|
||||
:justify-content :center
|
||||
:align-items :center}
|
||||
{:flex 1
|
||||
:flex-direction :column
|
||||
:justify-content :center
|
||||
:align-items :center}))
|
||||
|
||||
(defn intro-header-icon
|
||||
[diameter color]
|
||||
{:width diameter
|
||||
:height diameter
|
||||
:align-items :center
|
||||
:justify-content :center
|
||||
:border-radius (/ diameter 2)
|
||||
:background-color color
|
||||
:border-width 0.5
|
||||
:border-color "rgba(0,0,0,0.1)"})
|
||||
|
||||
(def intro-header-icon-text
|
||||
{:color colors/white
|
||||
:font-size 52
|
||||
:font-weight "700"
|
||||
:opacity 0.8
|
||||
:line-height 72})
|
||||
|
||||
(defn emoji-intro-header-icon-text
|
||||
[size]
|
||||
{:font-size (emoji-utils/emoji-font-size size)
|
||||
:margin-top (emoji-utils/emoji-top-margin-for-vertical-alignment size)}) ;; Required for vertical alignment bug - Check function defination for more info
|
||||
|
||||
(defn intro-header-chat-name
|
||||
[]
|
||||
{:font-size 22
|
||||
:font-weight "700"
|
||||
:line-height 28
|
||||
:text-align :center
|
||||
:margin-bottom 8
|
||||
:margin-horizontal 32
|
||||
:color colors/black})
|
||||
|
||||
(def intro-header-description-container
|
||||
{:flex-wrap :wrap
|
||||
:align-items :flex-start
|
||||
:flex-direction :row
|
||||
:margin-horizontal 32})
|
||||
|
||||
(def loading-text
|
||||
{:color colors/gray
|
||||
:font-size 15
|
||||
|
@ -107,51 +50,6 @@
|
|||
:margin-right 4
|
||||
:text-align :center})
|
||||
|
||||
(def intro-header-description
|
||||
{:color colors/gray
|
||||
:line-height 22
|
||||
:text-align :center
|
||||
:margin-horizontal 32})
|
||||
|
||||
(def group-chat-join-footer
|
||||
{:flex 1
|
||||
:justify-content :center})
|
||||
|
||||
(def group-chat-join-container
|
||||
{:flex 1
|
||||
:padding-bottom 40
|
||||
:align-items :center
|
||||
:justify-content :center})
|
||||
|
||||
(def are-you-friends-bubble
|
||||
{:border-radius 8
|
||||
:border-width 1
|
||||
:margin-top 4
|
||||
:border-color colors/gray-lighter
|
||||
:align-self :flex-start
|
||||
:padding-vertical 12
|
||||
:margin-horizontal 8
|
||||
:padding-horizontal 16
|
||||
:margin-bottom 50})
|
||||
|
||||
(def are-you-friends-text
|
||||
{:line-height 22
|
||||
:text-align :center
|
||||
:font-size 15
|
||||
:color colors/gray})
|
||||
|
||||
(def share-my-profile
|
||||
{:color colors/blue
|
||||
:text-align :center
|
||||
:margin-top 11
|
||||
:line-height 22
|
||||
:font-size 15})
|
||||
|
||||
(def tribute-received-note
|
||||
{:font-size 13
|
||||
:line-height 18
|
||||
:text-align :center})
|
||||
|
||||
(def contact-request
|
||||
{:width "100%"
|
||||
:justify-content :center
|
||||
|
|
|
@ -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}))
|
|
@ -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"})
|
|
@ -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})
|
|
@ -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)})
|
|
@ -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?}])]]))
|
|
@ -1,12 +1,9 @@
|
|||
(ns status-im.ui.screens.chat.utils
|
||||
(:require [quo.design-system.colors :as colors]
|
||||
[status-im.ethereum.stateofus :as stateofus]
|
||||
[i18n.i18n :as i18n]
|
||||
[status-im.multiaccounts.core :as multiaccounts]
|
||||
[status-im.ui.components.react :as react]))
|
||||
|
||||
(def ^:private reply-symbol "↪ ")
|
||||
|
||||
(defn format-author-old
|
||||
([contact] (format-author-old contact nil))
|
||||
([{:keys [names] :as contact} {:keys [modal profile? you?]}]
|
||||
|
@ -63,29 +60,3 @@
|
|||
(if (and max-length (> (count first-name) max-length))
|
||||
(str (subs first-name 0 max-length) "...")
|
||||
first-name)]))))
|
||||
|
||||
(defn format-reply-author
|
||||
[from username current-public-key style outgoing]
|
||||
(let [contact-name (str reply-symbol username)]
|
||||
(or (and (= from current-public-key)
|
||||
[react/text {:style (style true)}
|
||||
(str reply-symbol (i18n/label :t/You))])
|
||||
(if (or (= (aget contact-name 0) "@")
|
||||
;; in case of replies
|
||||
(= (aget contact-name 1) "@"))
|
||||
(let [trimmed-name (subs contact-name 0 81)]
|
||||
[react/text
|
||||
{:number-of-lines 2
|
||||
:style (merge {:color colors/blue
|
||||
:font-size 13
|
||||
:line-height 18
|
||||
:font-weight "500"})}
|
||||
(or (stateofus/username trimmed-name) trimmed-name)])
|
||||
[react/text
|
||||
{:style (merge {:color (if outgoing
|
||||
colors/white-transparent-70-persist
|
||||
colors/gray)
|
||||
:font-size 12
|
||||
:line-height 18
|
||||
:font-weight "400"})}
|
||||
contact-name]))))
|
||||
|
|
|
@ -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}))
|
|
@ -5,7 +5,6 @@
|
|||
[quo.design-system.colors :as colors]
|
||||
[re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.ens.core :as ens]
|
||||
[status-im.ethereum.core :as ethereum]
|
||||
[status-im.ethereum.ens :as ethereum.ens]
|
||||
|
@ -21,7 +20,6 @@
|
|||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.toolbar :as toolbar]
|
||||
[status-im.ui.components.topbar :as topbar]
|
||||
[status-im.ui.screens.chat.message.message :as message]
|
||||
[status-im.ui.screens.chat.photos :as photos]
|
||||
[status-im.ui.screens.chat.utils :as chat.utils]
|
||||
[status-im.ui.screens.profile.components.views :as profile.components]
|
||||
|
@ -762,18 +760,12 @@
|
|||
:action-fn #(re-frame/dispatch [:bottom-sheet/show-sheet
|
||||
{:content
|
||||
(fn [] (name-list names preferred-name))}])}]])]
|
||||
(let [message {:content {:parsed-text
|
||||
[{:type "paragraph"
|
||||
:children [{:literal (i18n/label :t/ens-test-message)}]}]}
|
||||
:content-type constants/content-type-text
|
||||
:timestamp-str "9:41 AM"}]
|
||||
[react/view
|
||||
[react/view {:padding-left 72}
|
||||
[my-name]]
|
||||
[react/view {:flex-direction :row}
|
||||
[react/view {:padding-left 16 :padding-top 4}
|
||||
[photos/photo (multiaccounts/displayed-photo account) {:size 36}]]
|
||||
[message/->message message {:on-long-press identity}]]])]])
|
||||
[react/view
|
||||
[react/view {:padding-left 72}
|
||||
[my-name]]
|
||||
[react/view {:flex-direction :row}
|
||||
[react/view {:padding-left 16 :padding-top 4}
|
||||
[photos/photo (multiaccounts/displayed-photo account) {:size 36}]]]]]])
|
||||
|
||||
(views/defview main
|
||||
[]
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
[status-im.ui.components.animation :as anim]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.screens.biometric.views :as biometric]
|
||||
[status-im.ui.screens.chat.message.pinned-message :as pinned-message]
|
||||
[status-im.ui.screens.communities.views :as communities]
|
||||
[status-im.ui.screens.keycard.frozen-card.view :as frozen-card]
|
||||
[status-im.ui.screens.keycard.views :as keycard.views]
|
||||
|
@ -181,9 +180,6 @@
|
|||
(= :password-reset-popover view)
|
||||
[reset-password.views/reset-password-popover]
|
||||
|
||||
(= :pin-limit view)
|
||||
[pinned-message/pin-limit-popover]
|
||||
|
||||
(= :fees-warning view)
|
||||
[signing-sheets/fees-warning]
|
||||
|
||||
|
|
|
@ -11,14 +11,11 @@
|
|||
[status-im.ui.components.chat-icon.screen :as chat-icon]
|
||||
[status-im.ui.components.icons.icons :as icons]
|
||||
[status-im.ui.components.keyboard-avoid-presentation :as kb-presentation]
|
||||
[status-im.ui.components.list.views :as list]
|
||||
[status-im.ui.components.profile-header.view :as profile-header]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.toolbar :as toolbar]
|
||||
[status-im.ui.components.topbar :as topbar]
|
||||
[status-im.ui.screens.profile.components.sheets :as sheets]
|
||||
[status-im.ui.screens.profile.contact.styles :as styles]
|
||||
[status-im.ui.screens.status.views :as status.views]
|
||||
[status-im.utils.utils :as utils])
|
||||
(:require-macros [status-im.utils.views :as views]))
|
||||
|
||||
|
@ -210,18 +207,6 @@
|
|||
[]
|
||||
(let [{:keys [public-key name ens-verified] :as contact} @(re-frame/subscribe
|
||||
[:contacts/current-contact])
|
||||
current-chat-id @(re-frame/subscribe
|
||||
[:chats/current-profile-chat])
|
||||
messages @(re-frame/subscribe
|
||||
[:chats/profile-messages-stream
|
||||
current-chat-id])
|
||||
no-messages? @(re-frame/subscribe [:chats/chat-no-messages?
|
||||
current-chat-id])
|
||||
muted? @(re-frame/subscribe [:chats/muted
|
||||
public-key])
|
||||
pinned-messages @(re-frame/subscribe [:chats/pinned
|
||||
public-key])
|
||||
[first-name second-name] (multiaccounts/contact-two-names contact true)
|
||||
on-share #(re-frame/dispatch
|
||||
[:show-popover
|
||||
(merge
|
||||
|
@ -237,43 +222,4 @@
|
|||
:on-press on-share}]
|
||||
:left-accessories [{:icon :main-icons/close
|
||||
:accessibility-label :back-button
|
||||
:on-press #(re-frame/dispatch [:navigate-back])}]}]
|
||||
[list/flat-list
|
||||
{:key-fn #(or (:message-id %) (:value %))
|
||||
:header [:<>
|
||||
[(profile-header/extended-header
|
||||
{:on-press on-share
|
||||
:bottom-separator false
|
||||
:title first-name
|
||||
:photo (multiaccounts/displayed-photo contact)
|
||||
:monospace (not ens-verified)
|
||||
:subtitle second-name
|
||||
:public-key public-key})]
|
||||
[react/view
|
||||
{:height 1 :background-color colors/gray-lighter :margin-top 8}]
|
||||
[nickname-settings contact]
|
||||
[pin-settings public-key (count pinned-messages)]
|
||||
[react/view {:height 1 :background-color colors/gray-lighter}]
|
||||
[react/view
|
||||
{:padding-top 17
|
||||
:flex-direction :row
|
||||
:align-items :stretch
|
||||
:flex 1}
|
||||
(for [{:keys [label] :as action} (actions contact muted?)
|
||||
:when label]
|
||||
^{:key label}
|
||||
[button-item action])]
|
||||
[react/view
|
||||
{:height 1 :background-color colors/gray-lighter :margin-top 16}]
|
||||
(when no-messages?
|
||||
[react/view {:padding-horizontal 32 :margin-top 32}
|
||||
[react/view (styles/updates-descr-cont)
|
||||
[react/text {:style {:color colors/gray :line-height 22}}
|
||||
(i18n/label :t/status-updates-descr)]]])]
|
||||
:ref #(reset! status.views/messages-list-ref %)
|
||||
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages current-chat-id])
|
||||
:on-scroll-to-index-failed #() ;;don't remove this
|
||||
:render-data {:chat-id current-chat-id
|
||||
:profile true}
|
||||
:render-fn status.views/render-message
|
||||
:data messages}]])))
|
||||
:on-press #(re-frame/dispatch [:navigate-back])}]}]])))
|
||||
|
|
|
@ -11,7 +11,6 @@
|
|||
[status-im.ui.components.profile-header.view :as profile-header]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.topbar :as topbar]
|
||||
[status-im.ui.screens.chat.message.message :as message]
|
||||
[status-im.ui.screens.chat.photos :as photos]
|
||||
[status-im.ui.screens.chat.sheets :as chat.sheets]
|
||||
[status-im.ui.screens.chat.utils :as chat.utils]
|
||||
|
@ -111,21 +110,16 @@
|
|||
(debounce/dispatch-and-chill event 2000))
|
||||
|
||||
(defn invitation-sheet
|
||||
[{:keys [introduction-message id]} contact]
|
||||
[{:keys [id]} contact]
|
||||
(let [members @(re-frame/subscribe [:contacts/current-chat-contacts])
|
||||
allow-adding-members? (< (count members) constants/max-group-chat-participants)]
|
||||
[react/view
|
||||
(let [message {:content {:parsed-text
|
||||
[{:type "paragraph"
|
||||
:children [{:literal introduction-message}]}]}
|
||||
:content-type constants/content-type-text}]
|
||||
[react/view {:margin-bottom 8 :margin-right 16}
|
||||
[react/view {:padding-left 72}
|
||||
(chat.utils/format-author-old contact)]
|
||||
[react/view {:flex-direction :row :align-items :flex-end}
|
||||
[react/view {:padding-left 16 :padding-top 4}
|
||||
[photos/photo (multiaccounts/displayed-photo contact) {:size 36}]]
|
||||
[message/->message message {:on-long-press identity}]]])
|
||||
[react/view {:margin-bottom 8 :margin-right 16}
|
||||
[react/view {:padding-left 72}
|
||||
(chat.utils/format-author-old contact)]
|
||||
[react/view {:flex-direction :row :align-items :flex-end}
|
||||
[react/view {:padding-left 16 :padding-top 4}
|
||||
[photos/photo (multiaccounts/displayed-photo contact) {:size 36}]]]]
|
||||
[quo/list-item
|
||||
{:theme :accent
|
||||
:disabled (not allow-adding-members?)
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
[status-im.ui.screens.browser.tabs.views :as browser.tabs]
|
||||
[status-im.ui.screens.browser.views :as browser]
|
||||
[status-im.ui.screens.bug-report :as bug-report]
|
||||
[status-im.ui.screens.chat.pinned-messages :as pin-messages]
|
||||
[status-im.ui.screens.communities.channel-details :as communities.channel-details]
|
||||
[status-im.ui.screens.communities.community :as community]
|
||||
[status-im.ui.screens.communities.community-emoji-thumbnail-picker :as
|
||||
|
@ -86,8 +85,6 @@
|
|||
[status-im.ui.screens.qr-scanner.views :as qr-scanner]
|
||||
[status-im.ui.screens.reset-password.views :as reset-password]
|
||||
[status-im.ui.screens.rpc-usage-info :as rpc-usage-info]
|
||||
[status-im.ui.screens.status.new.views :as status.new]
|
||||
[status-im.ui.screens.status.views :as status.views]
|
||||
[status-im.ui.screens.stickers.views :as stickers]
|
||||
[status-im.ui.screens.sync-settings.views :as sync-settings]
|
||||
[status-im.ui.screens.terms-of-service.views :as terms-of-service]
|
||||
|
@ -200,11 +197,6 @@
|
|||
:component onboarding.phrase/wizard-recovery-success}
|
||||
|
||||
;;CHAT
|
||||
;Pinned messages
|
||||
{:name :chat-pinned-messages
|
||||
;TODO custom subtitle
|
||||
:options {:topBar {:visible false}}
|
||||
:component pin-messages/pinned-messages}
|
||||
|
||||
{:name :group-chat-profile
|
||||
;;TODO animated-header
|
||||
|
@ -388,13 +380,6 @@
|
|||
:options {:topBar {:visible false}}
|
||||
:component wallet.swap/asset-selector}
|
||||
|
||||
;;MY STATUS
|
||||
|
||||
{:name :status
|
||||
:on-focus [:init-timeline-chat]
|
||||
:insets {:top true}
|
||||
:component status.views/timeline}
|
||||
|
||||
;;PROFILE
|
||||
|
||||
{:name :my-profile
|
||||
|
@ -696,12 +681,6 @@
|
|||
:options {:topBar {:visible false}}
|
||||
:component wallet.collectibles/nft-details-modal}
|
||||
|
||||
;My Status
|
||||
{:name :my-status
|
||||
:insets {:bottom true}
|
||||
:options {:topBar {:title {:text (i18n/label :t/my-status)}}}
|
||||
:component status.new/my-status}
|
||||
|
||||
;[Browser] New bookmark
|
||||
{:name :new-bookmark
|
||||
:insets {:bottom true}
|
||||
|
|
|
@ -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})
|
|
@ -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)]]]]]]))))
|
|
@ -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})
|
|
@ -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])}]]))
|
|
@ -8,7 +8,7 @@
|
|||
[quo2.foundations.colors :as colors]
|
||||
[re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.chat.constants :as chat.constants]
|
||||
[status-im2.common.constants :as chat.constants]
|
||||
[status-im.chat.models.mentions :as mentions]
|
||||
[i18n.i18n :as i18n]
|
||||
[status-im.ui.components.react :as react]
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
(:require
|
||||
[quo.design-system.colors :as quo.colors]
|
||||
[quo.react-native :as rn]
|
||||
[quo2.components.avatars.user-avatar :as user-avatar]
|
||||
[quo2.components.icon :as icons]
|
||||
[quo2.components.markdown.text :as text]
|
||||
[quo2.foundations.colors :as colors]
|
||||
|
@ -16,9 +15,7 @@
|
|||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.screens.chat.image.preview.views :as preview]
|
||||
[status-im.ui.screens.chat.message.audio :as message.audio]
|
||||
[status-im.ui.screens.chat.message.command :as message.command]
|
||||
[status-im.ui.screens.chat.message.gap :as message.gap]
|
||||
[status-im.ui.screens.chat.message.link-preview :as link-preview]
|
||||
[status-im.ui.screens.chat.sheets :as sheets]
|
||||
[status-im.ui.screens.chat.styles.message.message :as style]
|
||||
[status-im.ui.screens.chat.utils :as chat.utils]
|
||||
|
@ -34,8 +31,6 @@
|
|||
[quo2.core :as quo])
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
||||
|
||||
(def edited-at-text (str " ⌫ " (i18n/label :t/edited)))
|
||||
|
||||
(defn system-text?
|
||||
[content-type]
|
||||
(= content-type constants/content-type-system-text))
|
||||
|
@ -208,46 +203,6 @@
|
|||
(assoc props :accessibility-label :message-timestamp)
|
||||
(datetime/to-short-str timestamp)]]))])
|
||||
|
||||
(defn message-content-wrapper
|
||||
"Author, userpic and delivery wrapper"
|
||||
[{:keys [last-in-group? timestamp pinned from chat-id]
|
||||
:as message} content]
|
||||
(let [response-to (:response-to (:content message))
|
||||
display-name (first (rf/sub [:contacts/contact-two-names-by-identity from]))
|
||||
contact (rf/sub [:contacts/contact-by-address from])
|
||||
photo-path (when-not (empty? (:images contact)) (rf/sub [:chats/photo-path from]))
|
||||
online? (rf/sub [:visibility-status-updates/online? from])]
|
||||
[rn/view
|
||||
{:style (style/message-wrapper message)
|
||||
:pointer-events :box-none
|
||||
:accessibility-label :chat-item}
|
||||
(when (and (seq response-to) (:quoted-message message))
|
||||
[quoted-message {:message-id response-to :chat-id chat-id} (:quoted-message message)])
|
||||
[rn/view
|
||||
{:style (style/message-body)
|
||||
:pointer-events :box-none}
|
||||
;; AVATAR
|
||||
[rn/view {:style {:width 40}}
|
||||
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
|
||||
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
|
||||
[user-avatar/user-avatar
|
||||
{:full-name display-name
|
||||
:profile-picture photo-path
|
||||
:status-indicator? true
|
||||
:online? online?
|
||||
:size :small
|
||||
:ring? false}]])]
|
||||
[rn/view {:style (style/message-author-wrapper)}
|
||||
;; AUTHOR NAME
|
||||
(when (or (and (seq response-to) (:quoted-message message)) last-in-group? pinned)
|
||||
[display-name-view display-name contact timestamp true])
|
||||
;; MESSAGE CONTENT
|
||||
content
|
||||
[link-preview/link-preview-wrapper (:links (:content message)) false false]]]
|
||||
;; delivery status
|
||||
[rn/view (style/delivery-status)
|
||||
[message-delivery-status message]]]))
|
||||
|
||||
(def image-max-width 260)
|
||||
(def image-max-height 192)
|
||||
|
||||
|
@ -265,10 +220,6 @@
|
|||
|
||||
(defmulti ->message :content-type)
|
||||
|
||||
(defmethod ->message constants/content-type-command
|
||||
[message]
|
||||
[message.command/command-content message-content-wrapper message])
|
||||
|
||||
(defmethod ->message constants/content-type-gap
|
||||
[message]
|
||||
[message.gap/gap message])
|
||||
|
|
|
@ -223,3 +223,16 @@
|
|||
3 72.5
|
||||
4 72.5
|
||||
5 72.5}})
|
||||
|
||||
(def ^:const spam-message-frequency-threshold 4)
|
||||
(def ^:const spam-interval-ms 1000)
|
||||
(def ^:const default-cooldown-period-ms 10000)
|
||||
(def ^:const cooldown-reset-threshold 3)
|
||||
(def ^:const cooldown-periods-ms
|
||||
{1 2000
|
||||
2 5000
|
||||
3 10000})
|
||||
|
||||
(def ^:const max-text-size 4096)
|
||||
;; any message that comes after this amount of ms will be grouped separately
|
||||
(def ^:const group-ms 300000)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
(:require
|
||||
[i18n.i18n :as i18n]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[status-im.chat.models.message-list :as message-list]
|
||||
[status-im2.contexts.chat.messages.list.events :as message-list]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.datetime :as datetime]
|
||||
[utils.re-frame :as rf]))
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
(:require
|
||||
[i18n.i18n :as i18n]
|
||||
[quo2.foundations.colors :as colors]
|
||||
[status-im.chat.models.message-list :as message-list]
|
||||
[status-im2.contexts.chat.messages.list.events :as message-list]
|
||||
[taoensso.timbre :as log]
|
||||
[utils.datetime :as datetime]
|
||||
[utils.re-frame :as rf]))
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
(ns status-im.chat.models.message-list
|
||||
(:require ["functional-red-black-tree" :as rb-tree]
|
||||
[status-im.constants :as constants]
|
||||
(ns status-im2.contexts.chat.messages.list.events
|
||||
(:require [utils.red-black-tree :as red-black-tree]
|
||||
[utils.datetime :as datetime]
|
||||
[utils.re-frame :as rf]))
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.common.constants :as constants]))
|
||||
|
||||
(defn- add-datemark
|
||||
[{:keys [whisper-timestamp] :as msg}]
|
||||
;;TODO this is slow
|
||||
;;NOTE(performance) this is slow
|
||||
(assoc msg :datemark (datetime/day-relative whisper-timestamp)))
|
||||
|
||||
(defn- add-timestamp
|
||||
|
@ -40,9 +40,6 @@
|
|||
add-datemark
|
||||
add-timestamp))
|
||||
|
||||
;; any message that comes after this amount of ms will be grouped separately
|
||||
(def ^:private group-ms 300000)
|
||||
|
||||
(defn same-group?
|
||||
"Whether a message is in the same group as the one after it.
|
||||
We check the time, and the author"
|
||||
|
@ -51,7 +48,7 @@
|
|||
(not (:system-message? a))
|
||||
(not (:system-message? b))
|
||||
(= (:from a) (:from b))
|
||||
(<= (js/Math.abs (- (:whisper-timestamp a) (:whisper-timestamp b))) group-ms)))
|
||||
(<= (js/Math.abs (- (:whisper-timestamp a) (:whisper-timestamp b))) constants/group-ms)))
|
||||
|
||||
(defn display-photo?
|
||||
"We display photos for other users, and not in 1-to-1 chats"
|
||||
|
@ -126,50 +123,31 @@
|
|||
(not one-to-one?))
|
||||
:last-in-group? last-in-group?)))
|
||||
|
||||
(defn get-prev-element
|
||||
"Get previous item in the iterator, and wind it back to the initial state"
|
||||
[^js iter]
|
||||
(.prev iter)
|
||||
(let [e (.-value iter)]
|
||||
(.next iter)
|
||||
e))
|
||||
|
||||
(defn get-next-element
|
||||
"Get next item in the iterator, and wind it back to the initial state"
|
||||
[^js iter]
|
||||
(.next iter)
|
||||
(let [e (.-value iter)]
|
||||
(.prev iter)
|
||||
e))
|
||||
|
||||
(defn update-message
|
||||
"Update the message and siblings with positional info"
|
||||
[^js tree message]
|
||||
(let [^js iter (.find tree message)
|
||||
^js previous-message (when (.-hasPrev iter)
|
||||
(get-prev-element iter))
|
||||
^js next-message (when (.-hasNext iter)
|
||||
(get-next-element iter))
|
||||
^js message-with-pos-data (add-group-info message previous-message next-message)]
|
||||
(cond-> (.update iter message-with-pos-data)
|
||||
[tree message]
|
||||
(let [iter (red-black-tree/find tree message)
|
||||
previous-message (red-black-tree/get-prev iter)
|
||||
next-message (red-black-tree/get-next iter)
|
||||
message-with-pos-data (add-group-info message previous-message next-message)]
|
||||
(cond-> (red-black-tree/update iter message-with-pos-data)
|
||||
next-message
|
||||
(-> ^js (.find next-message)
|
||||
(.update (update-next-message message-with-pos-data next-message)))
|
||||
(-> (red-black-tree/find next-message)
|
||||
(red-black-tree/update (update-next-message message-with-pos-data next-message)))
|
||||
|
||||
(and previous-message
|
||||
(not= :datemark (:type previous-message)))
|
||||
(-> ^js (.find previous-message)
|
||||
(.update (update-previous-message message-with-pos-data previous-message))))))
|
||||
(-> (red-black-tree/find previous-message)
|
||||
(red-black-tree/update (update-previous-message message-with-pos-data previous-message))))))
|
||||
|
||||
(defn remove-message
|
||||
"Remove a message in the list"
|
||||
[^js tree prepared-message]
|
||||
(let [iter (.find tree prepared-message)]
|
||||
[tree prepared-message]
|
||||
(let [iter (red-black-tree/find tree prepared-message)]
|
||||
(if (not iter)
|
||||
tree
|
||||
(let [^js new-tree (.remove iter)
|
||||
^js next-message (when (.-hasNext iter)
|
||||
(get-next-element iter))]
|
||||
(let [new-tree (red-black-tree/remove iter)
|
||||
next-message (red-black-tree/get-next iter)]
|
||||
(if (not next-message)
|
||||
new-tree
|
||||
(update-message new-tree next-message))))))
|
||||
|
@ -179,13 +157,13 @@
|
|||
its positional metadata, and update the left & right messages if necessary,
|
||||
this operation is O(logN) for insertion, and O(logN) for the updates, as
|
||||
we need to re-find (there's probably a better way)"
|
||||
[^js old-message-list prepared-message]
|
||||
(let [^js tree (.insert old-message-list prepared-message prepared-message)]
|
||||
[old-message-list prepared-message]
|
||||
(let [tree (red-black-tree/insert old-message-list prepared-message)]
|
||||
(update-message tree prepared-message)))
|
||||
|
||||
(defn add
|
||||
[message-list message]
|
||||
(insert-message (or message-list (rb-tree compare-fn)) (prepare-message message)))
|
||||
(insert-message (or message-list (red-black-tree/tree compare-fn)) (prepare-message message)))
|
||||
|
||||
(defn add-many
|
||||
[message-list messages]
|
||||
|
@ -194,9 +172,9 @@
|
|||
messages))
|
||||
|
||||
(defn ->seq
|
||||
[^js message-list]
|
||||
[message-list]
|
||||
(if message-list
|
||||
(array-seq (.-values message-list))
|
||||
(array-seq (red-black-tree/get-values message-list))
|
||||
[]))
|
||||
|
||||
;; NOTE(performance): this is too expensive, probably we could mark message somehow and just hide it in
|
|
@ -1,6 +1,6 @@
|
|||
(ns status-im.chat.models.message-list-test
|
||||
(ns status-im2.contexts.chat.messages.list.events-test
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.chat.models.message-list :as s]
|
||||
[status-im2.contexts.chat.messages.list.events :as s]
|
||||
[taoensso.tufte :as tufte :refer-macros [defnp profile]]))
|
||||
|
||||
(deftest message-stream-tests
|
|
@ -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))
|
||||
|
|
@ -1,20 +1,19 @@
|
|||
(ns status-im2.contexts.chat.messages.list.view
|
||||
(:require [i18n.i18n :as i18n]
|
||||
[oops.core :as oops]
|
||||
[quo.react-native :as quo.react]
|
||||
[quo2.core :as quo]
|
||||
[react-native.background-timer :as background-timer]
|
||||
[react-native.core :as rn]
|
||||
[react-native.platform :as platform]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.ui.screens.chat.group :as chat.group]
|
||||
[status-im.ui.screens.chat.state :as state]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.contexts.chat.messages.content.view :as message]
|
||||
[status-im2.common.constants :as constants]
|
||||
[utils.re-frame :as rf]
|
||||
[status-im2.contexts.chat.messages.content.deleted.view :as content.deleted]
|
||||
[status-im.ui.screens.chat.message.gap :as message.gap]
|
||||
[status-im2.common.not-implemented :as not-implemented]))
|
||||
[status-im2.common.not-implemented :as not-implemented]
|
||||
[status-im.ui.screens.chat.group :as chat.group]
|
||||
[status-im2.contexts.chat.messages.list.state :as state]
|
||||
[status-im.ui.screens.chat.message.gap :as message.gap]))
|
||||
|
||||
(defonce messages-list-ref (atom nil))
|
||||
|
||||
|
@ -37,7 +36,7 @@
|
|||
threshold-percentage-to-show-floating-scroll-down-button)
|
||||
reached-threshold? (> y threshold-height)]
|
||||
(when (not= reached-threshold? @show-floating-scroll-down-button)
|
||||
(quo.react/configure-next (:ease-in-ease-out quo.react/layout-animation-presets))
|
||||
(rn/configure-next (:ease-in-ease-out rn/layout-animation-presets))
|
||||
(reset! show-floating-scroll-down-button reached-threshold?))))
|
||||
|
||||
(defn on-viewable-items-changed
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
(ns status-im2.contexts.chat.messages.pin.events
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.chat.models.message-list :as message-list]
|
||||
[status-im2.contexts.chat.messages.list.events :as message-list]
|
||||
[status-im.constants :as constants]
|
||||
[status-im.data-store.pin-messages :as data-store.pin-messages]
|
||||
[status-im.transport.message.protocol :as protocol]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
(ns status-im2.subs.chat.messages
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.chat.db :as chat.db]
|
||||
[status-im.chat.models.message-list :as models.message-list]
|
||||
[status-im2.contexts.chat.messages.list.events :as models.message-list]
|
||||
[status-im.chat.models.reactions :as models.reactions]
|
||||
[utils.datetime :as datetime]
|
||||
[status-im2.common.constants :as constants]))
|
||||
|
|
|
@ -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)))
|
Loading…
Reference in New Issue