chat screens performance

Signed-off-by: Andrey Shovkoplyas <motor4ik@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2020-03-10 16:43:27 +01:00
parent 4e567cf782
commit c4a7849c9d
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
35 changed files with 886 additions and 1588 deletions

View File

@ -314,9 +314,7 @@
(defn main-screen-modal-view [current-view & components] (defn main-screen-modal-view [current-view & components]
[(create-main-screen-view current-view) [(create-main-screen-view current-view)
styles/flex styles/flex
[(if (= current-view :chat-modal) [keyboard-avoiding-view
view
keyboard-avoiding-view)
(merge {:flex 1 :flex-direction :column} (merge {:flex 1 :flex-direction :column}
(when platform/android? (when platform/android?
{:background-color :white})) {:background-color :white}))

View File

@ -324,8 +324,6 @@
(views/letsubs [] (views/letsubs []
(let [main-screen-view (create-main-screen-view current-view)] (let [main-screen-view (create-main-screen-view current-view)]
[main-screen-view styles/flex [main-screen-view styles/flex
[(if (= current-view :chat-modal) [keyboard-avoiding-view
view
keyboard-avoiding-view)
{:flex 1 :flex-direction :column} {:flex 1 :flex-direction :column}
(apply vector view styles/flex components)]]))) (apply vector view styles/flex components)]])))

View File

@ -248,40 +248,33 @@
(fx/defn navigate-to-chat (fx/defn navigate-to-chat
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data" "Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
[cofx chat-id {:keys [modal? navigation-reset?]}] [cofx chat-id]
(cond
modal?
(fx/merge cofx
(navigation/navigate-to-cofx :chat-modal {})
(preload-chat-data chat-id))
:else
(fx/merge cofx (fx/merge cofx
(navigation/navigate-to-cofx :chat {}) (navigation/navigate-to-cofx :chat {})
(preload-chat-data chat-id)))) (preload-chat-data chat-id)))
(fx/defn start-chat (fx/defn start-chat
"Start a chat, making sure it exists" "Start a chat, making sure it exists"
[{:keys [db] :as cofx} chat-id opts] [{:keys [db] :as cofx} chat-id _]
;; don't allow to open chat with yourself ;; don't allow to open chat with yourself
(when (not= (multiaccounts.model/current-public-key cofx) chat-id) (when (not= (multiaccounts.model/current-public-key cofx) chat-id)
(fx/merge cofx (fx/merge cofx
(upsert-chat {:chat-id chat-id (upsert-chat {:chat-id chat-id
:is-active true}) :is-active true})
(transport.filters/load-chat chat-id) (transport.filters/load-chat chat-id)
(navigate-to-chat chat-id opts)))) (navigate-to-chat chat-id))))
(fx/defn start-public-chat (fx/defn start-public-chat
"Starts a new public chat" "Starts a new public chat"
[cofx topic {:keys [dont-navigate?] :as opts}] [cofx topic {:keys [dont-navigate?]}]
(if (active-chat? cofx topic) (if (active-chat? cofx topic)
(when-not dont-navigate? (when-not dont-navigate?
(navigate-to-chat cofx topic opts)) (navigate-to-chat cofx topic))
(fx/merge cofx (fx/merge cofx
(add-public-chat topic) (add-public-chat topic)
(transport.filters/load-chat topic) (transport.filters/load-chat topic)
#(when-not dont-navigate? #(when-not dont-navigate?
(navigate-to-chat % topic opts))))) (navigate-to-chat % topic)))))
(fx/defn disable-chat-cooldown (fx/defn disable-chat-cooldown
"Turns off chat cooldown (protection against message spamming)" "Turns off chat cooldown (protection against message spamming)"

View File

@ -110,8 +110,7 @@
content] :as message}] content] :as message}]
(let [{:keys [current-chat-id view-id]} db (let [{:keys [current-chat-id view-id]} db
cursor-clock-value (get-in db [:chats current-chat-id :cursor-clock-value]) cursor-clock-value (get-in db [:chats current-chat-id :cursor-clock-value])
current-chat? (and (or (= :chat view-id) current-chat? (and (= :chat view-id)
(= :chat-modal view-id))
(= current-chat-id chat-id))] (= current-chat-id chat-id))]
(when (and current-chat? (when (and current-chat?
(or (not cursor-clock-value) (or (not cursor-clock-value)
@ -165,8 +164,7 @@
message-id] :as message}] message-id] :as message}]
(when-not (= message-type constants/message-type-private-group-system-message) (when-not (= message-type constants/message-type-private-group-system-message)
(let [{:keys [current-chat-id view-id]} db (let [{:keys [current-chat-id view-id]} db
chat-view? (or (= :chat view-id) chat-view? (= :chat view-id)
(= :chat-modal view-id))
current-count (get-in db [:chats chat-id :unviewed-messages-count])] current-count (get-in db [:chats chat-id :unviewed-messages-count])]
(cond (cond
(= from (multiaccounts.model/current-public-key cofx)) (= from (multiaccounts.model/current-public-key cofx))

View File

@ -490,8 +490,8 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:chat.ui/navigate-to-chat :chat.ui/navigate-to-chat
(fn [cofx [_ chat-id opts]] (fn [cofx [_ chat-id _]]
(chat/navigate-to-chat cofx chat-id opts))) (chat/navigate-to-chat cofx chat-id)))
(handlers/register-handler-fx (handlers/register-handler-fx
:chat.ui/load-more-messages :chat.ui/load-more-messages

View File

@ -65,7 +65,7 @@
(data-store.messages/<-rpc %)) (data-store.messages/<-rpc %))
messages) messages)
navigate-fx #(if (get-in % [:db :chats chat-id :is-active]) navigate-fx #(if (get-in % [:db :chats chat-id :is-active])
(models.chat/navigate-to-chat % chat-id {:navigation-reset? true}) (models.chat/navigate-to-chat % chat-id)
(navigation/navigate-to-cofx % :home {}))] (navigation/navigate-to-cofx % :home {}))]
(apply fx/merge cofx (concat [chat-fx] (apply fx/merge cofx (concat [chat-fx]

View File

@ -86,10 +86,8 @@
(reg-root-key-sub :keyboard-height :keyboard-height) (reg-root-key-sub :keyboard-height :keyboard-height)
(reg-root-key-sub :keyboard-max-height :keyboard-max-height) (reg-root-key-sub :keyboard-max-height :keyboard-max-height)
(reg-root-key-sub :sync-data :sync-data) (reg-root-key-sub :sync-data :sync-data)
(reg-root-key-sub :layout-height :layout-height)
(reg-root-key-sub :mobile-network/remember-choice? :mobile-network/remember-choice?) (reg-root-key-sub :mobile-network/remember-choice? :mobile-network/remember-choice?)
(reg-root-key-sub :qr-modal :qr-modal) (reg-root-key-sub :qr-modal :qr-modal)
(reg-root-key-sub :content-layout-height :content-layout-height)
(reg-root-key-sub :bootnodes/manage :bootnodes/manage) (reg-root-key-sub :bootnodes/manage :bootnodes/manage)
(reg-root-key-sub :networks/current-network :networks/current-network) (reg-root-key-sub :networks/current-network :networks/current-network)
(reg-root-key-sub :networks/networks :networks/networks) (reg-root-key-sub :networks/networks :networks/networks)
@ -566,32 +564,6 @@
(fn [chats [_ chat-id]] (fn [chats [_ chat-id]]
(get chats chat-id))) (get chats chat-id)))
(re-frame/reg-sub
:chats/content-layout-height
:<- [:content-layout-height]
:<- [:chats/current-chat-ui-prop :input-height]
:<- [:chats/current-chat-ui-prop :input-focused?]
:<- [:keyboard-height]
:<- [:chats/current-chat-ui-prop :input-bottom-sheet]
(fn [[home-content-layout-height input-height input-focused? kheight stickers?]]
(- (+ home-content-layout-height tabs.styles/tabs-height)
(if platform/iphone-x?
(* 2 toolbar.styles/toolbar-height)
toolbar.styles/toolbar-height)
(if input-height input-height 0)
(if stickers?
(stickers.styles/stickers-panel-height)
kheight)
(if input-focused?
(cond
platform/iphone-x? 0
platform/ios? tabs.styles/tabs-diff
:else 0)
(cond
platform/iphone-x? (* 2 tabs.styles/minimized-tabs-height)
platform/ios? tabs.styles/tabs-height
:else tabs.styles/minimized-tabs-height)))))
(re-frame/reg-sub (re-frame/reg-sub
:chats/current-chat-ui-props :chats/current-chat-ui-props
:<- [::chat-ui-props] :<- [::chat-ui-props]
@ -703,11 +675,8 @@
(assoc :show-input? true)))) (assoc :show-input? true))))
(defn enrich-current-chat (defn enrich-current-chat
[{:keys [messages chat-id might-have-join-time-messages?] :as chat} [{:keys [messages chat-id might-have-join-time-messages?] :as chat} ranges]
ranges height input-height]
(assoc chat (assoc chat
:height height
:input-height input-height
:range :range
(get ranges chat-id) (get ranges chat-id)
:intro-status :intro-status
@ -735,12 +704,10 @@
:<- [:chats/current-raw-chat] :<- [:chats/current-raw-chat]
:<- [:multiaccount/public-key] :<- [:multiaccount/public-key]
:<- [:mailserver/ranges] :<- [:mailserver/ranges]
:<- [:chats/content-layout-height] (fn [[{:keys [group-chat chat-id messages] :as current-chat}
:<- [:chats/current-chat-ui-prop :input-height] my-public-key ranges]]
(fn [[{:keys [group-chat chat-id contact messages] :as current-chat}
my-public-key ranges height input-height]]
(when current-chat (when current-chat
(cond-> (enrich-current-chat current-chat ranges height input-height) (cond-> (enrich-current-chat current-chat ranges)
(empty? messages) (empty? messages)
(assoc :universal-link (assoc :universal-link
(links/generate-link :public-chat :external chat-id)) (links/generate-link :public-chat :external chat-id))
@ -830,6 +797,7 @@
:<- [:chats/all-loaded?] :<- [:chats/all-loaded?]
:<- [:chats/public?] :<- [:chats/public?]
(fn [[message-list messages messages-gaps range all-loaded? public?]] (fn [[message-list messages messages-gaps range all-loaded? public?]]
;;TODO (perf) we need to move all these to status-go
(-> (models.message-list/->seq message-list) (-> (models.message-list/->seq message-list)
(chat.db/add-datemarks) (chat.db/add-datemarks)
(hydrate-messages messages) (hydrate-messages messages)
@ -851,7 +819,7 @@
:<- [:contacts/contacts] :<- [:contacts/contacts]
:<- [:multiaccount] :<- [:multiaccount]
(fn [[contacts multiaccount] [_ id]] (fn [[contacts multiaccount] [_ id]]
(multiaccounts/displayed-photo (or (contacts id) (multiaccounts/displayed-photo (or (get contacts id)
(when (= id (:public-key multiaccount)) (when (= id (:public-key multiaccount))
multiaccount) multiaccount)
(contact.db/public-key->new-contact id))))) (contact.db/public-key->new-contact id)))))

View File

@ -1,22 +1,15 @@
(ns status-im.ui.components.connectivity.styles (ns status-im.ui.components.connectivity.styles
(:require [status-im.ui.components.colors :as colors] (:require [status-im.ui.components.colors :as colors]))
[status-im.utils.platform :as platform]))
(defn text-wrapper (defn text-wrapper
[{:keys [window-width height background-color opacity transform]}] [{:keys [window-width height background-color opacity transform]}]
(cond-> {:flex-direction :row {:flex-direction :row
:justify-content :center :justify-content :center
:transform [{:translateY transform}] :transform [{:translateY transform}]
:opacity opacity :opacity opacity
:background-color (or background-color colors/gray) :background-color (or background-color colors/gray)
:height height} :height height
:width window-width})
platform/desktop?
(assoc :left 0
:right 0)
(not platform/desktop?)
(assoc :width window-width)))
(def text (def text
{:color :white {:color :white

View File

@ -129,8 +129,8 @@ all connectivity views (we have at least one view in home and one in chat)"
(reset! status-hidden false))))) (reset! status-hidden false)))))
(defn connectivity-status (defn connectivity-status
[{:keys [connected?]} anim-translate-y status-hidden] [{:keys [connected?]} status-hidden]
(let [anim-translate-y (or anim-translate-y (animation/create-value 0)) (let [anim-translate-y (animation/create-value neg-connectivity-bar-height)
anim-opacity (animation/create-value 0)] anim-opacity (animation/create-value 0)]
(reagent/create-class (reagent/create-class
{:component-did-mount {:component-did-mount
@ -147,19 +147,16 @@ all connectivity views (we have at least one view in home and one in chat)"
(manage-visibility (:connected? (reagent/props comp)) true (manage-visibility (:connected? (reagent/props comp)) true
anim-opacity anim-translate-y status-hidden)) anim-opacity anim-translate-y status-hidden))
:reagent-render :reagent-render
(fn [{:keys [view-id message on-press-event connected? connecting?] :as opts}] (fn [{:keys [message on-press-event connected? connecting?] :as opts}]
(when-not @status-hidden
[react/animated-view {:style (styles/text-wrapper [react/animated-view {:style (styles/text-wrapper
(assoc opts (assoc opts
:height (if platform/desktop? :height connectivity-bar-height
anim-translate-y
connectivity-bar-height)
:background-color (if connected? :background-color (if connected?
colors/green colors/green
colors/gray) colors/gray)
;;TODO how does this affect desktop?
:transform anim-translate-y :transform anim-translate-y
:opacity anim-opacity :opacity anim-opacity))
:modal? (= view-id :chat-modal)))
:accessibility-label :connection-status-text} :accessibility-label :connection-status-text}
(when connecting? (when connecting?
[react/activity-indicator {:color colors/white :margin-right 6}]) [react/activity-indicator {:color colors/white :margin-right 6}])
@ -172,7 +169,7 @@ all connectivity views (we have at least one view in home and one in chat)"
(when message (when message
[react/text {:style styles/text [react/text {:style styles/text
:on-press (when on-press-event #(re-frame/dispatch [on-press-event]))} :on-press (when on-press-event #(re-frame/dispatch [on-press-event]))}
(i18n/label message)]))])}))) (i18n/label message)]))]))})))
;; timer updating the enqueued status ;; timer updating the enqueued status
(def timer (atom nil)) (def timer (atom nil))
@ -191,10 +188,15 @@ all connectivity views (we have at least one view in home and one in chat)"
;; reset queued with new state and start a timer if not yet started ;; reset queued with new state and start a timer if not yet started
(reset! enqueued-connectivity-status-properties status-properties) (reset! enqueued-connectivity-status-properties status-properties)
(when-not @timer (when-not @timer
(reset! timer (utils/set-timeout #(do (reset!
timer
(utils/set-timeout
#(do
(reset! timer nil) (reset! timer nil)
(when @enqueued-connectivity-status-properties (when @enqueued-connectivity-status-properties
(re-frame/dispatch [:set :connectivity/ui-status-properties @enqueued-connectivity-status-properties]) (re-frame/dispatch [:set
:connectivity/ui-status-properties
@enqueued-connectivity-status-properties])
(reset! enqueued-connectivity-status-properties nil))) (reset! enqueued-connectivity-status-properties nil)))
;; timeout choice: ;; timeout choice:
@ -223,66 +225,29 @@ all connectivity views (we have at least one view in home and one in chat)"
:reagent-render :reagent-render
#()})) #()}))
(defview connectivity-view [anim-translate-y] (defview connectivity [header footer]
(letsubs [status-properties [:connectivity/status-properties] (letsubs [status-properties [:connectivity/status-properties]
app-active-since [:app-active-since] app-active-since [:app-active-since]
logged-in-since [:logged-in-since] logged-in-since [:logged-in-since]
ui-status-properties [:connectivity/ui-status-properties] ui-status-properties [:connectivity/ui-status-properties]
status-hidden (reagent/atom true) status-hidden (reagent/atom true)
view-id [:view-id]
window-width (reagent/atom 0)] window-width (reagent/atom 0)]
(let [loading-indicator? (:loading-indicator? ui-status-properties)] (let [loading-indicator? (:loading-indicator? ui-status-properties)]
[react/view {:style {:align-items :stretch [react/view {:style {:flex 1}
:z-index 1}
:on-layout #(reset! window-width (-> % .-nativeEvent .-layout .-width))} :on-layout #(reset! window-width (-> % .-nativeEvent .-layout .-width))}
[react/view {:style {:z-index 2 :background-color :white}}
header
[react/view
(when (and loading-indicator? @status-hidden) (when (and loading-indicator? @status-hidden)
[loading-indicator @window-width]) [loading-indicator @window-width])]]
;; This view below exists only to hide the connectivity-status bar when "connected".
;; Ideally connectivity-status bar would be hidden under "toolbar/toolbar",
;; but that has to be transparent(enven though it sits above the bar)
;; to show through the "loading-indicator"
;; TODO consider making the height the same height as the "toolbar/toolbar"
[react/view {:position :absolute
:top neg-connectivity-bar-height
:width @window-width
:z-index 2
:height connectivity-bar-height
:background-color colors/white}]
[connectivity-status [connectivity-status
;on startup default connected
(merge (or ui-status-properties (merge (or ui-status-properties
{:connected? true :message :t/connected}) {:connected? true :message :t/connected})
{:view-id view-id {:window-width @window-width})
:window-width @window-width})
anim-translate-y
status-hidden] status-hidden]
;;TODO this is something weird, rework
[status-propagator-dummy-view {:status-properties status-properties [status-propagator-dummy-view {:status-properties status-properties
:app-active-since app-active-since :app-active-since app-active-since
:logged-in-since logged-in-since :logged-in-since logged-in-since
:ui-status-properties ui-status-properties}]]))) :ui-status-properties ui-status-properties}]
footer])))
;; "push?" determines whether "content" gets pushed down when disconnected
;; like in :home view, or stays put like in :chat view
;; TODO determine-how-this-affects/fix desktop
(defn connectivity-animation-wrapper [style anim-value push? & content]
(vec (concat
(if platform/desktop?
[react/view {:style {:flex 1}}]
[react/animated-view
{:style (merge {:flex 1
:margin-bottom neg-connectivity-bar-height}
;; A translated view (connectivity-view in this case)
;; prevents touch interaction to component below
;; them. If we don't bring this view on the same level
;; or above as the translated view, the top
;; portion(same height as connectivity-view) of
;; "content" (which now occupies translated view's
;; natural[untranslated] position) becomes
;; unresponsive to touch
(when-not @to-hide?
{:z-index 1})
(if push?
{:transform [{:translateY anim-value}]}
{:transform [{:translateY neg-connectivity-bar-height}]})
style)}])
content)))

View File

@ -199,28 +199,6 @@
(when header {:ListHeaderComponent (reagent/as-element header)}) (when header {:ListHeaderComponent (reagent/as-element header)})
(when footer {:ListFooterComponent (reagent/as-element footer)})))) (when footer {:ListFooterComponent (reagent/as-element footer)}))))
;; Workaround an issue in reagent that does not consider JS array as JS value
;; This forces clj <-> js serialization and breaks clj semantic
;; See https://github.com/reagent-project/reagent/issues/335
(deftype Item [value]
IEncodeJS
(-clj->js [x] (.-value x))
(-key->js [x] (.-value x))
IEncodeClojure
(-js->clj [x _] (.-value x)))
(defn- to-js-array
"Converts a collection to a JS array (but leave content as is)"
[coll]
(let [arr (array)]
(doseq [x coll]
(.push arr x))
arr))
(defn- wrap-data [o]
(Item. (to-js-array o)))
(defn flat-list (defn flat-list
"A wrapper for FlatList. "A wrapper for FlatList.
See https://facebook.github.io/react-native/docs/flatlist.html" See https://facebook.github.io/react-native/docs/flatlist.html"
@ -232,7 +210,7 @@
[class [class
(merge (base-list-props props) (merge (base-list-props props)
props props
{:data (wrap-data data)})]))) {:data (to-array data)})])))
(defn flat-list-generic-render-fn (defn flat-list-generic-render-fn
"A generic status-react specific `render-fn` for `list-item`. "A generic status-react specific `render-fn` for `list-item`.
@ -262,7 +240,7 @@
(if-let [f (:render-fn props)] (if-let [f (:render-fn props)]
(assoc (dissoc props :render-fn) :renderItem (wrap-render-fn f)) (assoc (dissoc props :render-fn) :renderItem (wrap-render-fn f))
props) props)
:data wrap-data)) :data to-array))
;;TODO DEPRECATED, use status-im.ui.components.list-item.views ;;TODO DEPRECATED, use status-im.ui.components.list-item.views
(defn section-list (defn section-list
"A wrapper for SectionList. "A wrapper for SectionList.

View File

@ -1,21 +1,14 @@
(ns status-im.ui.components.search-input.view (ns status-im.ui.components.search-input.view
(:require-macros [status-im.utils.views :as views]) (:require [status-im.i18n :as i18n]
(:require [reagent.core :as reagent]
[status-im.i18n :as i18n]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.ui.components.search-input.styles :as styles] [status-im.ui.components.search-input.styles :as styles]
[status-im.ui.components.icons.vector-icons :as icons])) [status-im.ui.components.icons.vector-icons :as icons]))
(views/defview search-input [{:keys [on-cancel (defn search-input [_]
on-focus (let [input-ref (atom nil)]
on-change (fn [{:keys [on-cancel on-focus on-change search-active?
search-active? search-container-style search-filter auto-focus]}]
search-container-style
search-filter
auto-focus]}]
(views/letsubs
[input-ref (reagent/atom nil)]
[react/view {:style (or search-container-style styles/search-container)} [react/view {:style (or search-container-style styles/search-container)}
[react/view {:style styles/search-input-container} [react/view {:style styles/search-input-container}
[icons/icon :main-icons/search {:color colors/gray [icons/icon :main-icons/search {:color colors/gray
@ -44,9 +37,8 @@
{:on-press (fn [] {:on-press (fn []
(.clear @input-ref) (.clear @input-ref)
(.blur @input-ref) (.blur @input-ref)
(when on-cancel (when on-cancel (on-cancel))
(on-cancel))
(reset! search-active? false)) (reset! search-active? false))
:style {:margin-left 16}} :style {:margin-left 16}}
[react/text {:style {:color colors/blue}} [react/text {:style {:color colors/blue}}
(i18n/label :t/cancel)]])])) (i18n/label :t/cancel)]])])))

View File

@ -35,7 +35,7 @@
(defn topbar [_] (defn topbar [_]
(let [title-padding (reagent/atom 16)] (let [title-padding (reagent/atom 16)]
(fn [& [{:keys [title navigation accessories show-border? modal?]}]] (fn [& [{:keys [title navigation accessories show-border? modal? content]}]]
(let [navigation (or navigation (default-navigation modal?))] (let [navigation (or navigation (default-navigation modal?))]
[react/view (cond-> {:height 56 :align-items :center :flex-direction :row} [react/view (cond-> {:height 56 :align-items :center :flex-direction :row}
show-border? show-border?
@ -49,6 +49,10 @@
(for [value accessories] (for [value accessories]
^{:key value} ^{:key value}
[button value false])]) [button value false])])
(when content
[react/view {:position :absolute :left @title-padding :right @title-padding
:top 0 :bottom 0}
content])
(when title (when title
[react/view {:position :absolute :left @title-padding :right @title-padding [react/view {:position :absolute :left @title-padding :right @title-padding
:top 0 :bottom 0 :align-items :center :justify-content :center} :top 0 :bottom 0 :align-items :center :justify-content :center}

View File

@ -1,16 +0,0 @@
(ns status-im.ui.screens.chat.bottom-info
(:require [status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.main :as styles]))
(defn overlay
[{:keys [on-click-outside]} items]
[react/view styles/bottom-info-overlay
[react/touchable-highlight {:on-press on-click-outside
:style styles/overlay-highlight}
[react/view nil]]
items])
(defn container
[height & children]
[react/view {:style (styles/bottom-info-container height)}
(into [react/view] children)])

View File

@ -0,0 +1,85 @@
(ns status-im.ui.screens.chat.group
(:require [re-frame.core :as re-frame]
[status-im.ui.components.button :as button]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.main :as style]
[status-im.i18n :as i18n]
[status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.colors :as colors]))
(defn join-chat-button [chat-id]
[button/button
{:type :secondary
:on-press #(re-frame/dispatch [:group-chats.ui/join-pressed chat-id])
:label :t/join-group-chat}])
(defn decline-chat [chat-id]
[react/touchable-highlight
{:on-press
#(re-frame/dispatch [:group-chats.ui/remove-chat-confirmed chat-id])}
[react/text {:style style/decline-chat}
(i18n/label :t/group-chat-decline-invitation)]])
(defn group-chat-footer
[chat-id]
[react/view {:style style/group-chat-join-footer}
[react/view {:style style/group-chat-join-container}
[join-chat-button chat-id]
[decline-chat chat-id]]])
(defn 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 group-chat-description-container
[{:keys [pending-invite-inviter-name inviter-name chat-name public?
universal-link range intro-status]}]
(let [{:keys [lowest-request-from highest-request-to]} range]
(case intro-status
:loading
[group-chat-description-loading]
:empty
(when public?
[react/nested-text {:style (merge style/intro-header-description
{:margin-bottom 36})}
(let [quiet-hours (quot (- highest-request-to lowest-request-from)
(* 60 60))
quiet-time (if (<= quiet-hours 24)
(i18n/label :t/quiet-hours
{:quiet-hours quiet-hours})
(i18n/label :t/quiet-days
{:quiet-days (quot quiet-hours 24)}))]
(i18n/label :t/empty-chat-description-public
{:quiet-hours quiet-time}))
[{:style {:color colors/blue}
:on-press #(list-selection/open-share
{:message
(i18n/label
:t/share-public-chat-text {:link universal-link})})}
(i18n/label :t/empty-chat-description-public-share-this)]])
:messages
(when (not public?)
(if pending-invite-inviter-name
[react/nested-text {:style style/intro-header-description}
[{:style {:color :black}} pending-invite-inviter-name]
(i18n/label :t/join-group-chat-description
{:username ""
:group-name chat-name})]
(if (not= inviter-name "Unknown")
[react/nested-text {:style style/intro-header-description}
(i18n/label :t/joined-group-chat-description
{:username ""
:group-name chat-name})
[{:style {:color :black}} inviter-name]]
[react/text {:style style/intro-header-description}
(i18n/label :t/created-group-chat-description
{:group-name chat-name})]))))))

View File

@ -1,47 +0,0 @@
(ns status-im.ui.screens.chat.input.animations.expandable
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [reagent.core :as reagent]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.animations :as style]
[status-im.ui.screens.chat.styles.input.input :as input-style]
[status-im.utils.platform :as platform]))
(def top-offset 100)
(defn expandable-view-on-update [anim-value]
(animation/start
(animation/spring anim-value {:toValue -53
:friction 10
:tension 60
:useNativeDriver true})))
(defview expandable-view [_ & elements]
(letsubs [;; Default value of translateY is 104, it is sufficient to
;; hide two commands below an input view.
;; With bigger view like assets parameter animation looks good
;; enough too, even if initially the view isn't fully covered by
;; input. It might be inferred for each case but it would require
;; more efforts and will not change too much the way how animation
;; looks atm.
anim-value (animation/create-value 104)
input-focused? [:chats/current-chat-ui-prop :input-focused?]
messages-focused? [:chats/current-chat-ui-prop :messages-focused?]
input-height [:chats/current-chat-ui-prop :input-height]
chat-input-margin [:chats/input-margin]
chat-layout-height [:layout-height]
keyboard-height [:keyboard-height]]
{:component-did-mount
(fn []
(expandable-view-on-update anim-value))}
(let [input-height (or input-height (+ input-style/padding-vertical
input-style/min-input-height
input-style/padding-vertical
input-style/border-height))
max-height (- chat-layout-height (when platform/ios? keyboard-height) input-height top-offset)]
[react/view style/overlap-container
[react/animated-view {:style (style/expandable-container anim-value chat-input-margin max-height)}
(into [react/scroll-view {:keyboard-should-persist-taps :always
:bounces false}]
(when (or input-focused? (not messages-focused?))
elements))]])))

View File

@ -9,15 +9,11 @@
[status-im.ui.screens.chat.photos :as photos] [status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.utils :as chat-utils] [status-im.ui.screens.chat.utils :as chat-utils]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.utils :as utils]
[status-im.utils.config :as config] [status-im.utils.config :as config]
[taoensso.timbre :as log]
[status-im.ui.screens.chat.stickers.views :as stickers] [status-im.ui.screens.chat.stickers.views :as stickers]
[status-im.ui.screens.chat.extensions.views :as extensions])) [status-im.ui.screens.chat.extensions.views :as extensions]))
@ -32,10 +28,7 @@
:default-value (or input-text "") :default-value (or input-text "")
:editable (not cooldown-enabled?) :editable (not cooldown-enabled?)
:blur-on-submit false :blur-on-submit false
:on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? true :on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-bottom-sheet nil}])
:input-bottom-sheet nil
:messages-focused? false}])
:on-blur #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? false}])
:on-submit-editing #(when single-line-input? :on-submit-editing #(when single-line-input?
(re-frame/dispatch [:chat.ui/send-current-message])) (re-frame/dispatch [:chat.ui/send-current-message]))
:on-layout #(set-container-width-fn (.-width (.-layout (.-nativeEvent %)))) :on-layout #(set-container-width-fn (.-width (.-layout (.-nativeEvent %))))
@ -63,10 +56,7 @@
:default-value @state-text :default-value @state-text
:editable (not cooldown-enabled?) :editable (not cooldown-enabled?)
:blur-on-submit false :blur-on-submit false
:on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? true :on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-bottom-sheet nil}])
:input-bottom-sheet nil
:messages-focused? false}])
:on-blur #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? false}])
:submit-shortcut {:key "Enter"} :submit-shortcut {:key "Enter"}
:on-submit-editing #(do :on-submit-editing #(do
(.clear @inp-ref) (.clear @inp-ref)
@ -162,12 +152,7 @@
input-text-empty? (if platform/desktop? input-text-empty? (if platform/desktop?
(string/blank? state-text) (string/blank? state-text)
(string/blank? input-text))] (string/blank? input-text))]
[react/view {:style (style/root margin) [react/view {:style (style/root margin)}
:on-layout #(let [h (-> (.-nativeEvent %)
(.-layout)
(.-height))]
(when (> h 0)
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-height h}])))}
[reply-message-view] [reply-message-view]
[react/view {:style style/input-container} [react/view {:style style/input-container}
[input-view {:single-line-input? single-line-input? :set-text set-text :state-text state-text}] [input-view {:single-line-input? single-line-input? :set-text set-text :state-text state-text}]

View File

@ -1,7 +1,6 @@
(ns status-im.ui.screens.chat.input.send-button (ns status-im.ui.screens.chat.input.send-button
(:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [clojure.string :as string] (:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.ui.screens.chat.styles.input.send-button :as style] [status-im.ui.screens.chat.styles.input.send-button :as style]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.icons.vector-icons :as vector-icons])) [status-im.ui.components.icons.vector-icons :as vector-icons]))

View File

@ -0,0 +1,257 @@
(ns status-im.ui.screens.chat.message.command
(:require [re-frame.core :as re-frame]
[status-im.commands.core :as commands]
[status-im.ui.components.react :as react]
[status-im.ui.components.colors :as colors]
[status-im.i18n :as i18n]
[status-im.constants :as constants]
[status-im.utils.money :as money]
[status-im.ethereum.transactions.core :as transactions]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.icons.vector-icons :as vector-icons]))
(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}}
[vector-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) " '" (:name @(re-frame/subscribe [:account-by-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 direction 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))
[vector-icons/icon :tiny-icons/tiny-warning
{:width 16
:height 16
:container-style {:margin-right 6}}]
(if (= :pending transaction-type)
[vector-icons/icon :tiny-icons/tiny-pending
{:color colors/gray
:width 16
:height 16
:container-style {:margin-right 6}}]
[vector-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 icon decimals color] :as token}
(if (seq contract)
(get @(re-frame/subscribe [:wallet/chain-tokens])
contract
transactions/default-erc20-token)
@(re-frame/subscribe [:ethereum/native-currency]))
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
(update :source #(%))
(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 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]
(case command-state
(constants/command-state-request-address-for-transaction-accepted
constants/command-state-request-address-for-transaction-declined
constants/command-state-request-transaction)
(if outgoing :incoming :outgoing)
(if outgoing :outgoing :incoming)))
(defn comand-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 :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
command-state direction address timestamp-str (:type transaction)]
(when (not outgoing)
(cond
(= command-state constants/command-state-request-transaction)
[command-actions
:t/sign-and-send
#(re-frame/dispatch
[:wallet.ui/accept-request-transaction-button-clicked-from-command
chat-id
command-parameters])
#(re-frame/dispatch [::commands/decline-request-transaction message-id])]
(= command-state
constants/command-state-request-address-for-transaction-accepted)
[command-actions
:t/sign-and-send
#(re-frame/dispatch
[:wallet.ui/accept-request-transaction-button-clicked-from-command
chat-id
command-parameters])]
(= command-state constants/command-state-request-address-for-transaction)
[command-actions
:t/accept-and-share-address
#(re-frame/dispatch
[::commands/prepare-accept-request-address-for-transaction
message])
#(re-frame/dispatch
[::commands/decline-request-address-for-transaction
message-id])]))]]]))

View File

@ -5,17 +5,10 @@
[status-im.ui.screens.chat.styles.message.datemark :as style])) [status-im.ui.screens.chat.styles.message.datemark :as style]))
(defn chat-datemark [value] (defn chat-datemark [value]
[react/view style/datemark-wrapper
[react/view style/datemark
[react/text {:style style/datemark-text}
(string/capitalize value)]]])
(defn chat-datemark-mobile [value]
[react/touchable-without-feedback [react/touchable-without-feedback
{:on-press (fn [_] {:on-press (fn [_]
(re-frame/dispatch (re-frame/dispatch
[:chat.ui/set-chat-ui-props {:messages-focused? true [:chat.ui/set-chat-ui-props {:input-bottom-sheet nil}])
:input-bottom-sheet nil}])
(react/dismiss-keyboard!))} (react/dismiss-keyboard!))}
[react/view style/datemark-mobile [react/view style/datemark-mobile
[react/text {:style style/datemark-text} [react/text {:style style/datemark-text}

View File

@ -1,25 +1,20 @@
(ns status-im.ui.screens.chat.message.message (ns status-im.ui.screens.chat.message.message
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.commands.core :as commands]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.utils.http :as http] [status-im.utils.http :as http]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ethereum.eip55 :as eip55]
[reagent.core :as reagent]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.utils.security :as security] [status-im.utils.security :as security]
[status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.popup-menu.views :as desktop.pop-up] [status-im.ui.components.popup-menu.views :as desktop.pop-up]
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.utils.money :as money]
[status-im.ethereum.transactions.core :as transactions]
[status-im.ui.screens.chat.sheets :as sheets] [status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.screens.chat.photos :as photos] [status-im.ui.screens.chat.photos :as photos]
[status-im.ui.screens.chat.styles.message.message :as style] [status-im.ui.screens.chat.styles.message.message :as style]
[status-im.ui.screens.chat.utils :as chat.utils] [status-im.ui.screens.chat.utils :as chat.utils]
[status-im.utils.contenthash :as contenthash] [status-im.utils.contenthash :as contenthash]
[status-im.utils.platform :as platform]) [status-im.utils.platform :as platform]
[status-im.ui.screens.chat.message.command :as message.command])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defview mention-element [from] (defview mention-element [from]
@ -34,7 +29,7 @@
(:rtl? content) (:rtl? content)
(= content-type constants/content-type-emoji))} t]) (= content-type constants/content-type-emoji))} t])
(defn message-view (defn message-bubble-wrapper
[{:keys [timestamp-str outgoing content content-type] :as message} [{:keys [timestamp-str outgoing content content-type] :as message}
message-content {:keys [justify-timestamp?]}] message-content {:keys [justify-timestamp?]}]
[react/view (style/message-view message) [react/view (style/message-view message)
@ -58,11 +53,6 @@
:number-of-lines 5} :number-of-lines 5}
(or text (:text quote))]]))) (or text (:text quote))]])))
(defn expand-button [expanded? chat-id message-id]
[react/text {:style style/message-expand-button
:on-press #(re-frame/dispatch [:chat.ui/message-expand-toggled chat-id message-id])}
(i18n/label (if expanded? :show-less :show-more))])
(defn render-inline [message-text outgoing acc {:keys [type literal destination]}] (defn render-inline [message-text outgoing acc {:keys [type literal destination]}]
(case type (case type
"" ""
@ -120,10 +110,7 @@
[react/text-class {:style style/status-text}] [react/text-class {:style style/status-text}]
(-> content :parsed-text peek :children))]]) (-> content :parsed-text peek :children))]])
(defn render-block [{:keys [chat-id message-id content (defn render-block [{:keys [content outgoing]} acc
timestamp-str group-chat outgoing
current-public-key expanded?] :as message}
acc
{:keys [type literal children]}] {:keys [type literal children]}]
(case type (case type
@ -145,14 +132,12 @@
acc)) acc))
(defn render-parsed-text [{:keys [timestamp-str (defn render-parsed-text [{:keys [timestamp-str outgoing] :as message} tree]
outgoing] :as message}
tree]
(let [elements (reduce (fn [acc e] (render-block message acc e)) [react/view {}] tree) (let [elements (reduce (fn [acc e] (render-block message acc e)) [react/view {}] tree)
timestamp [react/text {:style (style/message-timestamp-placeholder outgoing)} timestamp [react/text {:style (style/message-timestamp-placeholder outgoing)}
(str " " timestamp-str)] (str " " timestamp-str)]
last-element (peek elements)] last-element (peek elements)]
;; TODO (perf)
;; Using `nth` here as slightly faster than `first`, roughly 30% ;; Using `nth` here as slightly faster than `first`, roughly 30%
;; It's worth considering pure js structures for this code path as ;; It's worth considering pure js structures for this code path as
;; it's perfomance critical ;; it's perfomance critical
@ -163,9 +148,8 @@
(conj elements timestamp)))) (conj elements timestamp))))
(defn text-message (defn text-message
[{:keys [chat-id message-id content [{:keys [content outgoing current-public-key] :as message}]
timestamp-str group-chat outgoing current-public-key expanded?] :as message}] [message-bubble-wrapper message
[message-view message
(let [response-to (:response-to content)] (let [response-to (:response-to content)]
[react/view [react/view
(when (seq response-to) (when (seq response-to)
@ -176,275 +160,13 @@
(defn emoji-message (defn emoji-message
[{:keys [content current-public-key alias outgoing] :as message}] [{:keys [content current-public-key alias outgoing] :as message}]
(let [response-to (:response-to content)] (let [response-to (:response-to content)]
[message-view message [message-bubble-wrapper message
[react/view {:style (style/style-message-text outgoing)} [react/view {:style (style/style-message-text outgoing)}
(when response-to (when response-to
[quoted-message response-to (:quoted-message message) alias outgoing current-public-key]) [quoted-message response-to (:quoted-message message) alias outgoing current-public-key])
[react/text {:style (style/emoji-message message)} [react/text {:style (style/emoji-message message)}
(:text content)]]])) (:text content)]]]))
(defmulti message-content (fn [_ message _] (message :content-type)))
(defmethod message-content constants/content-type-text
[wrapper message]
[wrapper message [text-message message]])
(defmethod message-content constants/content-type-status
[wrapper message]
[wrapper message [message-content-status message]])
(defmethod message-content constants/content-type-emoji
[wrapper message]
[wrapper message [emoji-message message]])
(defmethod message-content constants/content-type-sticker
[wrapper {:keys [content] :as message}]
[wrapper message
[react/image {:style {:margin 10 :width 140 :height 140}
:source {:uri (contenthash/url (-> content :sticker :hash))}}]])
(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}}
[vector-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) " '" (:name @(re-frame/subscribe [:account-by-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 direction 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))
[vector-icons/icon :tiny-icons/tiny-warning
{:width 16
:height 16
:container-style {:margin-right 6}}]
(if (= :pending transaction-type)
[vector-icons/icon :tiny-icons/tiny-pending
{:color colors/gray
:width 16
:height 16
:container-style {:margin-right 6}}]
[vector-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 icon decimals color] :as token}
(if (seq contract)
(get @(re-frame/subscribe [:wallet/chain-tokens])
contract
transactions/default-erc20-token)
@(re-frame/subscribe [:ethereum/native-currency]))
amount (money/internal->formatted value symbol decimals)
{:keys [code] :as currency}
@(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
(update :source #(%))
(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 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]
(case command-state
(constants/command-state-request-address-for-transaction-accepted
constants/command-state-request-address-for-transaction-declined
constants/command-state-request-transaction)
(if outgoing :incoming :outgoing)
(if outgoing :outgoing :incoming)))
(defmethod message-content constants/content-type-command
[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 :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
command-state direction address timestamp-str (:type transaction)]
(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])]))]]]))
(defmethod message-content :default
[wrapper {:keys [content-type] :as message}]
[wrapper message
[message-view message
[react/text (str "Unhandled content-type " content-type)]]])
(defn message-activity-indicator (defn message-activity-indicator
[] []
[react/view style/message-activity-indicator [react/view style/message-activity-indicator
@ -473,84 +195,81 @@
(defn message-delivery-status (defn message-delivery-status
[{:keys [chat-id message-id outgoing-status [{:keys [chat-id message-id outgoing-status
first-outgoing? first-outgoing? message-type]}]
content message-type] :as message}]
(when (not= constants/message-type-private-group-system-message message-type) (when (not= constants/message-type-private-group-system-message message-type)
(case outgoing-status (case outgoing-status
:sending [message-activity-indicator] :sending [message-activity-indicator]
:not-sent [message-not-sent-text chat-id message-id] :not-sent [message-not-sent-text chat-id message-id]
:sent (when first-outgoing? :sent (when first-outgoing?
[react/view style/delivery-view
[react/text {:style style/delivery-text} [react/text {:style style/delivery-text}
(i18n/label :t/status-sent)]]) (i18n/label :t/status-sent)])
nil))) nil)))
(defview message-author-name [from alias] (defview message-author-name [from alias]
(letsubs [{:keys [ens-name]} [:contacts/contact-name-by-identity from]] (letsubs [{:keys [ens-name]} [:contacts/contact-name-by-identity from]]
(chat.utils/format-author alias style/message-author-name-container ens-name))) (chat.utils/format-author alias style/message-author-name-container ens-name)))
(defn message-body (defn message-press-handlers [{:keys [outgoing from content-type content] :as message}]
[{:keys [alias (let [pack (get-in content [:sticker :pack])]
last-in-group? {:on-press (fn [_]
first-in-group? (when (and (= content-type constants/content-type-sticker) pack)
display-photo? (re-frame/dispatch [:stickers/open-sticker-pack pack]))
identicon (re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-bottom-sheet nil}])
display-username? (react/dismiss-keyboard!))
from
outgoing
modal?
content]
:as message} child]
[react/view (style/group-message-wrapper message)
[react/view (style/message-body message)
(when display-photo?
[react/view (style/message-author outgoing)
(when first-in-group?
[react/touchable-highlight {:on-press #(when-not modal? (re-frame/dispatch [:chat.ui/show-profile from]))}
[react/view
[photos/member-photo from identicon]]])])
[react/view (style/group-message-view outgoing display-photo?)
(when display-username?
[react/touchable-opacity {:style style/message-author-touchable
:on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
[message-author-name from alias]])
[react/view {:style (style/timestamp-content-wrapper outgoing)}
child]]]
[react/view (style/delivery-status outgoing)
[message-delivery-status message]]])
(defn chat-message
[{:keys [outgoing from group-chat modal? current-public-key content-type content] :as message}]
(let [sticker (:sticker content)]
[react/view
[react/touchable-highlight
{:on-press (fn [arg]
(if (and platform/desktop? (= "right" (.-button (.-nativeEvent arg))))
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (sheets/message-long-press message)
:height 192}])
(do
(when (and (= content-type constants/content-type-sticker) (:pack sticker))
(re-frame/dispatch [:stickers/open-sticker-pack (:pack sticker)]))
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:messages-focused? true
:input-bottom-sheet nil}])
(when-not platform/desktop?
(react/dismiss-keyboard!)))))
:on-long-press #(cond (or (= content-type constants/content-type-text) :on-long-press #(cond (or (= content-type constants/content-type-text)
(= content-type constants/content-type-emoji)) (= content-type constants/content-type-emoji))
(re-frame/dispatch [:bottom-sheet/show-sheet (re-frame/dispatch [:bottom-sheet/show-sheet
{:content (sheets/message-long-press message) {:content (sheets/message-long-press message)
:height 192}]) :height 192}])
(and (= content-type constants/content-type-sticker) (and (= content-type constants/content-type-sticker)
from (not outgoing)) from (not outgoing))
(re-frame/dispatch [:bottom-sheet/show-sheet (re-frame/dispatch [:bottom-sheet/show-sheet
{:content (sheets/sticker-long-press message) {:content (sheets/sticker-long-press message)
:height 64}]))} :height 64}]))}))
[react/view {:accessibility-label :chat-item}
(let [incoming-group (and group-chat (not outgoing))] (defn message-content-wrapper
[message-content message-body (merge message "Author, userpic and delivery wrapper"
{:current-public-key current-public-key [{:keys [alias first-in-group? display-photo? identicon display-username?
:group-chat group-chat from outgoing]
:modal? modal? :as message} content]
:incoming-group incoming-group})])]]])) [react/view {:style (style/message-wrapper message)
:accessibility-label :chat-item}
[react/view (style/message-body message)
(when display-photo?
; userpic
[react/view (style/message-author-userpic outgoing)
(when first-in-group?
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
[photos/member-identicon identicon]])])
; username
[react/view (style/message-author-wrapper outgoing display-photo?)
(when display-username?
[react/touchable-opacity {:style style/message-author-touchable
:on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
;;TODO (perf) move to event
[message-author-name from alias]])
;;MESSAGE CONTENT
content]]
; delivery status
[react/view (style/delivery-status outgoing)
[message-delivery-status message]]])
(defn chat-message [{:keys [content content-type] :as message}]
(if (= content-type constants/content-type-command)
[message.command/comand-content message-content-wrapper message]
[react/touchable-highlight (message-press-handlers message)
[message-content-wrapper
message
(if (= content-type constants/content-type-text)
; text message
[text-message message]
(if (= content-type constants/content-type-status)
[message-content-status message]
(if (= content-type constants/content-type-emoji)
[emoji-message message]
(if (= content-type constants/content-type-sticker)
[react/image {:style {:margin 10 :width 140 :height 140}
;;TODO (perf) move to event
:source {:uri (contenthash/url (-> content :sticker :hash))}}]
[message-bubble-wrapper message
[react/text (str "Unhandled content-type " content-type)]]))))]]))

View File

@ -1,15 +1,11 @@
(ns status-im.ui.screens.chat.photos (ns status-im.ui.screens.chat.photos
(:require [clojure.string :as string] (:require [status-im.ui.components.react :as react]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.photos :as style] [status-im.ui.screens.chat.styles.photos :as style]
[status-im.ui.screens.profile.db :as profile.db] [status-im.ui.screens.profile.db :as profile.db]
[status-im.utils.identicon :as identicon]
[status-im.utils.image :as utils.image]) [status-im.utils.image :as utils.image])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn photo [photo-path {:keys [size (defn photo [photo-path {:keys [size accessibility-label]}]
accessibility-label]}]
(let [identicon? (when photo-path (profile.db/base64-png? photo-path))] (let [identicon? (when photo-path (profile.db/base64-png? photo-path))]
[react/view {:style (style/photo-container size)} [react/view {:style (style/photo-container size)}
[react/image {:source (utils.image/source photo-path) [react/image {:source (utils.image/source photo-path)
@ -24,3 +20,12 @@
(photo (or photo-path identicon) (photo (or photo-path identicon)
{:accessibility-label :member-photo {:accessibility-label :member-photo
:size (or size style/default-size)}))) :size (or size style/default-size)})))
(defn member-identicon [identicon]
(let [size style/default-size]
[react/view {:style (style/photo-container size)}
[react/image {:source {:uri identicon}
:style (style/photo size)
:resize-mode :cover
:accessibility-label :member-photo}]
[react/view {:style (style/photo-border size)}]]))

View File

@ -1,40 +1,11 @@
(ns status-im.ui.screens.chat.styles.main (ns status-im.ui.screens.chat.styles.main
(:require [status-im.ui.components.colors :as colors])) (:require [status-im.ui.components.colors :as colors]))
(def chat-view
{:flex 1})
(def toolbar-container (def toolbar-container
{:flex 1 {:flex 1
:align-items :center :align-items :center
:flex-direction :row}) :flex-direction :row})
(def messages-container
{:flex 1
:padding-bottom 0
:margin-bottom 0})
(def action
{:width 56
:height 56
:top 0
:align-items :center
:justify-content :center})
(def icon-view
{:width 56
:height 56})
(def back-icon
{:margin-top 21
:margin-left 23
:width 8
:height 14})
(def chat-toolbar-contents
{:flex-direction :row
:flex 1})
(def chat-name-view (def chat-name-view
{:flex 1 {:flex 1
:justify-content :center}) :justify-content :center})
@ -44,12 +15,6 @@
:font-size 15 :font-size 15
:line-height 22}) :line-height 22})
(def group-icon
{:margin-top 4
:margin-bottom 2.7
:width 14
:height 9})
(def toolbar-subtitle (def toolbar-subtitle
{:typography :caption {:typography :caption
:line-height 16 :line-height 16
@ -60,57 +25,6 @@
:margin-top 4 :margin-top 4
:color colors/text-gray}) :color colors/text-gray})
(defn actions-wrapper [status-bar-height]
{:background-color colors/white
:elevation 2
:position :absolute
:top (+ 55 status-bar-height)
:left 0
:right 0})
(def actions-separator
{:margin-left 16
:height 1.5
:background-color colors/black-transparent})
(def actions-view
{:margin-vertical 10})
(def action-icon-row
{:flex-direction :row
:height 56})
(def action-icon-view
(merge icon-view
{:align-items :center
:justify-content :center}))
(def action-view
{:flex 1
:align-items :flex-start
:justify-content :center})
(def action-title
{:margin-top -2.5
:color colors/text
:font-size 14})
(def typing-all
{:marginBottom 20})
(def typing-view
{:width 260
:margin-top 10
:padding-left 8
:padding-right 8
:align-items :flex-start
:align-self :flex-start})
(def typing-text
{:margin-top -2
:font-size 12
:color colors/text-gray})
(def overlay-highlight (def overlay-highlight
{:flex 1}) {:flex 1})
@ -134,31 +48,6 @@
:right 16 :right 16
:height height}) :height height})
(def bottom-info-list-container
{:padding-left 16
:padding-right 16
:padding-top 8
:padding-bottom 8})
(def item-height 60)
(def bottom-info-row
{:flex-direction "row"
:padding-top 4
:padding-bottom 4})
(def bottom-info-row-photo-size 42)
(def bottom-info-row-text-container
{:margin-left 16
:margin-right 16})
(def bottom-info-row-text1
{:color "black"})
(def bottom-info-row-text2
{:color "#888888"})
(def add-contact (def add-contact
{:flex-direction :row {:flex-direction :row
:align-items :center :align-items :center
@ -171,14 +60,6 @@
{:margin-left 4 {:margin-left 4
:color colors/blue}) :color colors/blue})
(def add-contact-close-icon
{:margin-right 12})
(defn message-view-animated [opacity]
{:opacity opacity
:flex 1
:background-color :white})
(def empty-chat-container (def empty-chat-container
{:flex 1 {:flex 1
:justify-content :center :justify-content :center
@ -187,18 +68,17 @@
:margin-right 6}) :margin-right 6})
(defn intro-header-container (defn intro-header-container
[height status no-messages] [status no-messages]
(let [adjusted-height (if (< height 280) 324 height)]
(if (or no-messages (= status (or :loading :empty))) (if (or no-messages (= status (or :loading :empty)))
{:flex 1 {:flex 1
:flex-direction :column :flex-direction :column
:justify-content :center :justify-content :center
:align-items :center :align-items :center
:height adjusted-height} :height 324}
{:flex 1 {:flex 1
:flex-direction :column :flex-direction :column
:justify-content :center :justify-content :center
:align-items :center}))) :align-items :center}))
(defn intro-header-icon [diameter color] (defn intro-header-icon [diameter color]
{:width diameter {:width diameter
@ -238,20 +118,12 @@
:margin-right 4 :margin-right 4
:text-align :center}) :text-align :center})
(def empty-chat-text-name
{:margin-bottom 5})
(def intro-header-description (def intro-header-description
{:color colors/gray {:color colors/gray
:line-height 22 :line-height 22
:text-align :center :text-align :center
:margin-horizontal 32}) :margin-horizontal 32})
(def group-chat-icon
{:color colors/white
:font-size 40
:font-weight "700"})
(def group-chat-join-footer (def group-chat-join-footer
{:flex 1 {:flex 1
:justify-content :center}) :justify-content :center})
@ -261,21 +133,10 @@
:align-items :center :align-items :center
:justify-content :center}) :justify-content :center})
(def group-chat-join-name
{:typography :header})
(def join-button
{:margin-bottom 15})
(def decline-chat (def decline-chat
{:color colors/blue {:color colors/blue
:margin-bottom 40}) :margin-bottom 40})
(def select-chat
{:color colors/gray})
(def messages-list-vertical-padding 46)
(def are-you-friends-bubble (def are-you-friends-bubble
{:border-radius 8 {:border-radius 8
:border-width 1 :border-width 1

View File

@ -47,29 +47,20 @@
:bottom 9 ; 6 Bubble bottom, 3 message baseline :bottom 9 ; 6 Bubble bottom, 3 message baseline
(if rtl? :left :right) 12}))) (if rtl? :left :right) 12})))
(def message-expand-button
{:color colors/gray
:font-size 12
:opacity 0.7
:margin-bottom 20})
(def selected-message (def selected-message
{:margin-top 18 {:margin-top 18
:margin-left 40 :margin-left 40
:font-size 12 :font-size 12
:color colors/text-gray}) :color colors/text-gray})
(defn group-message-wrapper [{:keys [outgoing] :as message}] (defn message-wrapper [{:keys [outgoing] :as message}]
(merge {:flex-direction :column} (merge {:flex-direction :column}
(if outgoing (if outgoing
{:margin-left 96} {:margin-left 96}
{:margin-right 52}) {:margin-right 52})
(last-message-padding message))) (last-message-padding message)))
(defn timestamp-content-wrapper [outgoing] (defn message-author-wrapper
{:flex-direction (if outgoing :row-reverse :row)})
(defn group-message-view
[outgoing display-photo?] [outgoing display-photo?]
(let [align (if outgoing :flex-end :flex-start)] (let [align (if outgoing :flex-end :flex-start)]
(merge {:flex-direction :column (merge {:flex-direction :column
@ -91,7 +82,7 @@
{:margin-left 12 {:margin-left 12
:padding-vertical 2}) :padding-vertical 2})
(defn message-author [outgoing] (defn message-author-userpic [outgoing]
(merge (merge
{:width (+ 16 photos/default-size) ;; 16 is for the padding {:width (+ 16 photos/default-size) ;; 16 is for the padding
:align-self :flex-end} :align-self :flex-end}
@ -100,18 +91,15 @@
{:padding-horizontal 8 {:padding-horizontal 8
:padding-right 8}))) :padding-right 8})))
(def delivery-view
{:flex-direction :row
:margin-top 2})
(def delivery-text (def delivery-text
{:color colors/gray {:color colors/gray
:margin-top 2
:font-size 12}) :font-size 12})
(def not-sent-view (def not-sent-view
(assoc delivery-view {:flex-direction :row
:margin-bottom 2 :margin-bottom 2
:padding-top 2)) :padding-top 2})
(def not-sent-text (def not-sent-text
(assoc delivery-text (assoc delivery-text

View File

@ -1,10 +1,8 @@
(ns status-im.ui.screens.chat.toolbar-content (ns status-im.ui.screens.chat.toolbar-content
(:require [cljs-time.core :as t] (:require [status-im.i18n :as i18n]
[status-im.i18n :as i18n]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen] [status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.main :as st] [status-im.ui.screens.chat.styles.main :as st])
[status-im.utils.datetime :as time])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn- in-progress-text [{:keys [highestBlock currentBlock startBlock]}] (defn- in-progress-text [{:keys [highestBlock currentBlock startBlock]}]
@ -47,8 +45,8 @@
(i18n/label :chat-is-not-a-contact))]]) (i18n/label :chat-is-not-a-contact))]])
(defview toolbar-content-view [] (defview toolbar-content-view []
(letsubs [{:keys [group-chat color online contacts chat-name contact (letsubs [{:keys [group-chat color online contacts chat-name contact public?]}
public? chat-id] :as chat} [:chats/current-chat] [:chats/current-chat]
sync-state [:sync-state]] sync-state [:sync-state]]
(let [has-subtitle? (or group-chat (not= :done sync-state))] (let [has-subtitle? (or group-chat (not= :done sync-state))]
[react/view {:style st/toolbar-container} [react/view {:style st/toolbar-container}

View File

@ -0,0 +1,92 @@
(ns status-im.ui.screens.chat.ttt
(:require [status-im.ui.screens.chat.styles.main :as style]
[status-im.ui.components.react :as react]
[status-im.ui.screens.profile.tribute-to-talk.views :as tribute-to-talk.views]
[status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors]
[re-frame.core :as re-frame]))
(defn tribute-to-talk-header
[name]
[react/nested-text {:style (assoc style/intro-header-description
:margin-bottom 32)}
(i18n/label :t/tribute-required-by-multiaccount {:multiaccount-name name})
[{:style {:color colors/blue}
:on-press #(re-frame/dispatch [:navigate-to :tribute-learn-more])}
(str " " (i18n/label :learn-more))]])
(defn pay-to-chat-messages
[snt-amount chat-id tribute-status tribute-label
fiat-amount fiat-currency token]
[tribute-to-talk.views/pay-to-chat-message
{:snt-amount snt-amount
:public-key chat-id
:tribute-status tribute-status
:tribute-label tribute-label
:fiat-amount fiat-amount
:fiat-currency fiat-currency
:token token
:style {:margin-horizontal 8
:align-items :flex-start
:align-self (if snt-amount :flex-start :flex-end)}}])
(defn one-to-one-chat-description-container
[{:keys [chat-id name contact show-input? tribute-to-talk]
:tribute-to-talk/keys [my-message received? message tribute-status
tribute-label snt-amount on-share-my-profile
fiat-amount fiat-currency token]}]
(case tribute-status
:loading
[react/view (assoc (dissoc style/empty-chat-container :flex)
:justify-content :flex-end)
[react/view {:style {:align-items :center :justify-content :flex-end}}
[react/view {:style {:flex-direction :row :justify-content :center}}
[react/text {:style style/loading-text}
(i18n/label :t/loading)]
[react/activity-indicator {:color colors/gray
:animating true}]]]]
:required
[react/view
[tribute-to-talk-header name]
[pay-to-chat-messages snt-amount chat-id tribute-status tribute-label
fiat-amount fiat-currency token]
[react/view {:style style/are-you-friends-bubble}
[react/text {:style (assoc style/are-you-friends-text
:font-weight "500")}
(i18n/label :t/tribute-to-talk-are-you-friends)]
[react/text {:style style/are-you-friends-text}
(i18n/label :t/tribute-to-talk-ask-to-be-added)]
[react/text {:style style/share-my-profile
:on-press on-share-my-profile}
(i18n/label :t/share-my-profile)]]]
:pending
[react/view
[tribute-to-talk-header name]
[pay-to-chat-messages snt-amount chat-id tribute-status tribute-label
fiat-amount fiat-currency token]]
(:paid :none)
[react/view
;[intro-header contact]
(when (= tribute-status :paid)
[pay-to-chat-messages snt-amount chat-id tribute-status tribute-label
fiat-amount fiat-currency token])
(when received?
[pay-to-chat-messages nil nil nil nil nil nil nil])
(when (or (= tribute-status :paid) received?)
[react/view {:style {:margin-top 16 :margin-horizontal 8}}
[react/nested-text {:style style/tribute-received-note}
(when received?
[{:style (assoc style/tribute-received-note :color colors/gray)}
(i18n/label :tribute-to-talk-tribute-received1)])
[{:style (assoc style/tribute-received-note :font-weight "500")}
name]
[{:style (assoc style/tribute-received-note :color colors/gray)}
(i18n/label (if received?
:tribute-to-talk-tribute-received2
:tribute-to-talk-contact-received-your-tribute))]]])]))
;[intro-header contact]))

View File

@ -1,15 +1,8 @@
(ns status-im.ui.screens.chat.utils (ns status-im.ui.screens.chat.utils
(:require [re-frame.core :as re-frame] (:require [status-im.ethereum.stateofus :as stateofus]
[status-im.ethereum.ens :as ens]
[status-im.ethereum.stateofus :as stateofus]
[status-im.utils.gfycat.core :as gfycat]
[status-im.utils.platform :as platform]
[status-im.utils.security :as security]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.utils.core :as core-utils]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]))
[status-im.utils.http :as http]))
(defn format-author [alias style name] (defn format-author [alias style name]
(let [additional-styles (style false)] (let [additional-styles (style false)]
@ -38,59 +31,3 @@
[react/text {:style (style true)} [react/text {:style (style true)}
(str reply-symbol (i18n/label :t/You))]) (str reply-symbol (i18n/label :t/You))])
(format-author (subs reply-name 0 80) style false)))) (format-author (subs reply-name 0 80) style false))))
(def ^:private styling->prop
{:bold {:style {:font-weight "700"}}
:italic {:style {:font-style :italic}}
:backquote {:style {:background-color colors/black
:color colors/green}}})
(def ^:private action->prop-fn
{:link (fn [text {:keys [outgoing] :as message}]
{:style {:color (if outgoing colors/white colors/blue)
:text-decoration-line :underline}
:on-press #(when (and (security/safe-link? text)
(security/safe-link-text? (-> message :content :text)))
(if platform/desktop?
(.openURL react/linking (http/normalize-url text))
(re-frame/dispatch [:browser.ui/message-link-pressed text])))})
:tag (fn [text {:keys [outgoing]}]
{:style {:color (if outgoing colors/white colors/blue)
:text-decoration-line :underline}
:on-press #(re-frame/dispatch [:chat.ui/start-public-chat (subs text 1) {:navigation-reset? true}])})})
(defn- lookup-props [text-chunk message kind]
(let [prop (get styling->prop (keyword kind))
prop-fn (get action->prop-fn (keyword kind))]
(if prop-fn (prop-fn text-chunk message) prop)))
(defn render-chunks [render-recipe message]
(vec (map-indexed (fn [idx [text-chunk kind]]
(if (= :text kind)
text-chunk
[(into {:key idx} (lookup-props text-chunk message kind))
text-chunk]))
render-recipe)))
(defn render-chunks-desktop [limit render-recipe message]
"This fn is only needed as a temporary hack
until rn-desktop supports text/number-of-lines property"
(->> render-recipe
(map vector (range))
(reduce (fn [[total-length acc] [idx [text-chunk kind]]]
(if (<= limit total-length)
(reduced [total-length acc])
(let [chunk-len (count text-chunk)
cut-chunk-len (min chunk-len (- limit total-length))
cut-chunk (if (= chunk-len cut-chunk-len)
text-chunk
(core-utils/truncate-str text-chunk cut-chunk-len))]
[(+ total-length cut-chunk-len)
(conj acc
(if (= :text kind)
cut-chunk
[react/text (into {:key idx} (lookup-props text-chunk message kind))
cut-chunk]))])))
[0 []])
second
seq))

View File

@ -1,37 +1,45 @@
(ns status-im.ui.screens.chat.views (ns status-im.ui.screens.chat.views
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.contact.db :as contact.db] [status-im.contact.db :as contact.db]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.multiaccounts.core :as multiaccounts] [status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.button :as button]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen] [status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.ui.components.connectivity.view :as connectivity] [status-im.ui.components.connectivity.view :as connectivity]
[status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.list.views :as list] [status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.toolbar.actions :as toolbar.actions]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.screens.chat.sheets :as sheets] [status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.screens.chat.input.input :as input] [status-im.ui.screens.chat.input.input :as input]
[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.message.message :as message]
[status-im.ui.screens.chat.stickers.views :as stickers] [status-im.ui.screens.chat.stickers.views :as stickers]
[status-im.ui.screens.chat.styles.main :as style] [status-im.ui.screens.chat.styles.main :as style]
[status-im.ui.screens.chat.toolbar-content :as toolbar-content] [status-im.ui.screens.chat.toolbar-content :as toolbar-content]
[status-im.ui.screens.profile.tribute-to-talk.views
:as
tribute-to-talk.views]
[status-im.ui.screens.chat.state :as state] [status-im.ui.screens.chat.state :as state]
[status-im.utils.debounce :as debounce] [status-im.utils.debounce :as debounce]
[status-im.utils.platform :as platform] [status-im.ui.screens.chat.extensions.views :as extensions]
[status-im.ui.screens.chat.extensions.views :as extensions]) [status-im.ui.components.topbar :as topbar]
[status-im.ui.screens.chat.group :as chat.group]
[status-im.ui.screens.chat.message.gap :as gap]
[status-im.ui.screens.chat.message.datemark :as message-datemark])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn topbar [current-chat]
[topbar/topbar
{:content [toolbar-content/toolbar-content-view]
:show-border? true
:navigation {:icon :main-icons/back
:accessibility-label :back-button
:handler
#(re-frame/dispatch [:navigate-to :home])}
:accessories [{:icon :main-icons/more
:accessibility-label :chat-menu-button
:handler
#(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[sheets/actions current-chat])
:height 256}])}]}])
(defn add-contact-bar (defn add-contact-bar
[public-key] [public-key]
[react/touchable-highlight [react/touchable-highlight
@ -43,250 +51,30 @@
{:color colors/blue}] {:color colors/blue}]
[react/i18n-text {:style style/add-contact-text :key :add-to-contacts}]]]) [react/i18n-text {:style style/add-contact-text :key :add-to-contacts}]]])
(defmulti message-row
(fn [{{:keys [type]} :row}] type))
(defmethod message-row :datemark
[{{:keys [value]} :row}]
[message-datemark/chat-datemark-mobile value])
(defmethod message-row :gap
[{:keys [row idx list-ref]}]
[gap/gap row idx list-ref])
(defmethod message-row :default
[{:keys [group-chat current-public-key modal? row]}]
[message/chat-message (assoc row
:group-chat group-chat
:modal? modal?
:current-public-key current-public-key)])
(def animation-duration 200)
(defview messages-view-animation [add-contact-bar message-view]
;; smooths out appearance of message-view
(letsubs [opacity (animation/create-value 0)]
{:component-did-mount (fn [_]
(animation/start
(animation/timing
opacity
{:toValue 1
:duration animation-duration
:useNativeDriver true})))}
(if platform/desktop?
message-view
[react/animated-view {:style (style/message-view-animated opacity)}
add-contact-bar
message-view])))
(defn tribute-to-talk-header
[name]
[react/nested-text {:style (assoc style/intro-header-description
:margin-bottom 32)}
(i18n/label :t/tribute-required-by-multiaccount {:multiaccount-name name})
[{:style {:color colors/blue}
:on-press #(re-frame/dispatch [:navigate-to :tribute-learn-more])}
(str " " (i18n/label :learn-more))]])
(defn intro-header (defn intro-header
[contact] [contact]
[react/text {:style (assoc style/intro-header-description [react/text {:style (assoc style/intro-header-description
:margin-bottom 32)} :margin-bottom 32)}
(str (i18n/label :t/empty-chat-description-one-to-one) (multiaccounts/displayed-name contact))]) (str
(i18n/label :t/empty-chat-description-one-to-one)
(defn join-chat-button [chat-id] (multiaccounts/displayed-name contact))])
[button/button
{:type :secondary
:on-press #(re-frame/dispatch [:group-chats.ui/join-pressed chat-id])
:label :t/join-group-chat}])
(defn decline-chat [chat-id]
[react/touchable-highlight
{:on-press
#(re-frame/dispatch [:group-chats.ui/remove-chat-confirmed chat-id])}
[react/text {:style style/decline-chat}
(i18n/label :t/group-chat-decline-invitation)]])
(defn group-chat-footer
[chat-id]
[react/view {:style style/group-chat-join-footer}
[react/view {:style style/group-chat-join-container}
[join-chat-button chat-id]
[decline-chat chat-id]]])
;; TODO this is now used only in Desktop - unnecessary for mobile
(defn group-chat-join-section
[inviter-name {:keys [name group-chat color chat-id]}]
[react/view style/empty-chat-container
[react/view {:style {:margin-bottom 170}}
[chat-icon.screen/profile-icon-view
nil name color false 100
{:default-chat-icon-text style/group-chat-icon}]]
[react/view {:style style/group-chat-join-footer}
[react/view {:style style/group-chat-join-container}
[react/view
[react/text {:style style/group-chat-join-name} name]]
[react/text {:style style/intro-header-description}
(i18n/label :t/join-group-chat-description {:username inviter-name
:group-name name})]
[join-chat-button chat-id]
[decline-chat chat-id]]]])
(defn 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 group-chat-description-container
[{:keys [group-chat name pending-invite-inviter-name
inviter-name color chat-id chat-name public?
contact universal-link range intro-status] :as chat}]
(let [{:keys [lowest-request-from highest-request-to]} range]
(case intro-status
:loading
[group-chat-description-loading]
:empty
(when public?
[react/nested-text {:style (merge style/intro-header-description
{:margin-bottom 36})}
(let [quiet-hours (quot (- highest-request-to lowest-request-from)
(* 60 60))
quiet-time (if (<= quiet-hours 24)
(i18n/label :t/quiet-hours
{:quiet-hours quiet-hours})
(i18n/label :t/quiet-days
{:quiet-days (quot quiet-hours 24)}))]
(i18n/label :t/empty-chat-description-public
{:quiet-hours quiet-time}))
[{:style {:color colors/blue}
:on-press #(list-selection/open-share
{:message
(i18n/label
:t/share-public-chat-text {:link universal-link})})}
(i18n/label :t/empty-chat-description-public-share-this)]])
:messages
(when (not public?)
(if pending-invite-inviter-name
[react/nested-text {:style style/intro-header-description}
[{:style {:color :black}} pending-invite-inviter-name]
(i18n/label :t/join-group-chat-description
{:username ""
:group-name chat-name})]
(if (not= inviter-name "Unknown")
[react/nested-text {:style style/intro-header-description}
(i18n/label :t/joined-group-chat-description
{:username ""
:group-name chat-name})
[{:style {:color :black}} inviter-name]]
[react/text {:style style/intro-header-description}
(i18n/label :t/created-group-chat-description
{:group-name chat-name})]))))))
(defn pay-to-chat-messages
[snt-amount chat-id tribute-status tribute-label
fiat-amount fiat-currency token]
[tribute-to-talk.views/pay-to-chat-message
{:snt-amount snt-amount
:public-key chat-id
:tribute-status tribute-status
:tribute-label tribute-label
:fiat-amount fiat-amount
:fiat-currency fiat-currency
:token token
:style {:margin-horizontal 8
:align-items :flex-start
:align-self (if snt-amount :flex-start :flex-end)}}])
(defn one-to-one-chat-description-container
[{:keys [chat-id name contact show-input? tribute-to-talk]
:tribute-to-talk/keys [my-message received? message tribute-status
tribute-label snt-amount on-share-my-profile
fiat-amount fiat-currency token]}]
(case tribute-status
:loading
[react/view (assoc (dissoc style/empty-chat-container :flex)
:justify-content :flex-end)
[react/view {:style {:align-items :center :justify-content :flex-end}}
[react/view {:style {:flex-direction :row :justify-content :center}}
[react/text {:style style/loading-text}
(i18n/label :t/loading)]
[react/activity-indicator {:color colors/gray
:animating true}]]]]
:required
[react/view
[tribute-to-talk-header name]
[pay-to-chat-messages snt-amount chat-id tribute-status tribute-label
fiat-amount fiat-currency token]
[react/view {:style style/are-you-friends-bubble}
[react/text {:style (assoc style/are-you-friends-text
:font-weight "500")}
(i18n/label :t/tribute-to-talk-are-you-friends)]
[react/text {:style style/are-you-friends-text}
(i18n/label :t/tribute-to-talk-ask-to-be-added)]
[react/text {:style style/share-my-profile
:on-press on-share-my-profile}
(i18n/label :t/share-my-profile)]]]
:pending
[react/view
[tribute-to-talk-header name]
[pay-to-chat-messages snt-amount chat-id tribute-status tribute-label
fiat-amount fiat-currency token]]
(:paid :none)
[react/view
[intro-header contact]
(when (= tribute-status :paid)
[pay-to-chat-messages snt-amount chat-id tribute-status tribute-label
fiat-amount fiat-currency token])
(when received?
[pay-to-chat-messages nil nil nil nil nil nil nil])
(when (or (= tribute-status :paid) received?)
[react/view {:style {:margin-top 16 :margin-horizontal 8}}
[react/nested-text {:style style/tribute-received-note}
(when received?
[{:style (assoc style/tribute-received-note :color colors/gray)}
(i18n/label :tribute-to-talk-tribute-received1)])
[{:style (assoc style/tribute-received-note :font-weight "500")}
name]
[{:style (assoc style/tribute-received-note :color colors/gray)}
(i18n/label (if received? :tribute-to-talk-tribute-received2
:tribute-to-talk-contact-received-your-tribute))]]])]
[intro-header contact]))
(defn chat-intro-header-container (defn chat-intro-header-container
[{:keys [group-chat name pending-invite-inviter-name [{:keys [group-chat name pending-invite-inviter-name color chat-id chat-name
inviter-name color chat-id chat-name public? public? contact intro-status] :as chat}
contact universal-link intro-status height input-height] :as chat}
no-messages] no-messages]
(let [icon-text (if public? chat-id name) (let [icon-text (if public? chat-id name)
intro-name (if public? chat-name (multiaccounts/displayed-name contact))] intro-name (if public? chat-name (multiaccounts/displayed-name contact))]
;; TODO This when check ought to be unnecessary but for now it prevents (when (or pending-invite-inviter-name
;; jerky motion when fresh chat is created, when input-height can be null
;; affecting the calculation of content-layout-height to be briefly adjusted
(when (or input-height
pending-invite-inviter-name
(not= (get-in contact [:tribute-to-talk :snt-amount]) 0)) (not= (get-in contact [:tribute-to-talk :snt-amount]) 0))
[react/touchable-without-feedback [react/touchable-without-feedback
{:style {:flex 1 {:style {:flex 1
:align-items :flex-start} :align-items :flex-start}
:on-press (fn [_] :on-press (fn [_]
(re-frame/dispatch (re-frame/dispatch
[:chat.ui/set-chat-ui-props {:messages-focused? true [:chat.ui/set-chat-ui-props {:input-bottom-sheet nil}])
:input-bottom-sheet nil}])
(react/dismiss-keyboard!))} (react/dismiss-keyboard!))}
[react/view (style/intro-header-container height intro-status no-messages) [react/view (style/intro-header-container intro-status no-messages)
;; Icon section ;; Icon section
[react/view {:style {:margin-top 42 [react/view {:style {:margin-top 42
:margin-bottom 24}} :margin-bottom 24}}
@ -296,11 +84,12 @@
:default-chat-icon-text style/intro-header-icon-text :default-chat-icon-text style/intro-header-icon-text
:size 120}]] :size 120}]]
;; Chat title section ;; Chat title section
[react/text {:style style/intro-header-chat-name} (if group-chat chat-name intro-name)] [react/text {:style style/intro-header-chat-name}
(if group-chat chat-name intro-name)]
;; Description section ;; Description section
(if group-chat (if group-chat
[group-chat-description-container chat] [chat.group/group-chat-description-container chat]
[one-to-one-chat-description-container chat])]]))) [intro-header contact])]])))
(defonce messages-list-ref (atom nil)) (defonce messages-list-ref (atom nil))
@ -320,149 +109,54 @@
(debounce/debounce-and-dispatch [:chat.ui/message-visibility-changed e] 5000)) (debounce/debounce-and-dispatch [:chat.ui/message-visibility-changed e] 5000))
(defview messages-view (defview messages-view
[{:keys [group-chat chat-id pending-invite-inviter-name contact] :as chat} [{:keys [group-chat chat-id pending-invite-inviter-name] :as chat}]
modal?]
(letsubs [messages [:chats/current-chat-messages-stream] (letsubs [messages [:chats/current-chat-messages-stream]
current-public-key [:multiaccount/public-key]] current-public-key [:multiaccount/public-key]]
{:component-did-update [list/flat-list
(fn [args] {:key-fn #(or (:message-id %) (:value %))
(re-frame/dispatch [:chat.ui/set-chat-ui-props
{:messages-focused? true
:input-focused? false}]))}
(let [no-messages (empty? messages)
flat-list-conf
{:data messages
:ref #(reset! messages-list-ref %) :ref #(reset! messages-list-ref %)
:footer [chat-intro-header-container chat no-messages] :header (when pending-invite-inviter-name
:key-fn #(or (:message-id %) (:value %)) [chat.group/group-chat-footer chat-id])
:render-fn (fn [message idx] :footer [chat-intro-header-container chat (empty? messages)]
[message-row :data messages
{:group-chat group-chat
:modal? modal?
:current-public-key current-public-key
:row message
:idx idx
:list-ref messages-list-ref}])
:inverted true :inverted true
:onViewableItemsChanged on-viewable-items-changed :render-fn (fn [{:keys [outgoing] :as message} idx]
:onEndReached #(re-frame/dispatch [:chat.ui/load-more-messages]) (let [type (:type message)]
:onScrollToIndexFailed #() (if (= type :datemark)
:keyboardShouldPersistTaps :handled} [message-datemark/chat-datemark (:value message)]
group-header {:header [group-chat-footer chat-id]}] (if (= type :gap)
(if pending-invite-inviter-name [gap/gap message idx messages-list-ref]
[list/flat-list (merge flat-list-conf group-header)] ; message content
[list/flat-list flat-list-conf])))) [message/chat-message
(assoc message
:incoming-group (and group-chat (not outgoing))
:group-chat group-chat
:current-public-key current-public-key)]))))
:on-viewable-items-changed on-viewable-items-changed
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages])
:on-scroll-to-index-failed #() ;;don't remove this
:keyboard-should-persist-taps :handled}]))
(def load-step 5) (defview bottom-sheet []
(letsubs [input-bottom-sheet [:chats/current-chat-ui-prop :input-bottom-sheet]]
(defn load-more [all-messages-count messages-to-load]
(let [next-count (min all-messages-count (+ @messages-to-load load-step))]
(reset! messages-to-load next-count)))
(defview messages-view-desktop [{:keys [chat-id group-chat pending-invite-inviter-name]}
modal?]
(letsubs [messages [:chats/current-chat-messages-stream]
current-public-key [:multiaccount/public-key]
messages-to-load (reagent/atom load-step)
chat-id* (reagent/atom nil)]
{:component-did-update #(if (:messages-initialized? (second (.-argv (.-props %1))))
(load-more (count messages) messages-to-load)
(re-frame/dispatch [:chat.ui/load-more-messages]))
:component-did-mount #(if (:messages-initialized? (second (.-argv (.-props %1))))
(load-more (count messages) messages-to-load)
(re-frame/dispatch [:chat.ui/load-more-messages]))}
(let [messages-list-ref (atom nil)
scroll-timer (atom nil)
scroll-height (atom nil)
_ (when (or (not @chat-id*) (not= @chat-id* chat-id))
(do
(reset! messages-to-load load-step)
(reset! chat-id* chat-id)))]
[react/view {:style style/chat-view}
[react/scroll-view {:scrollEventThrottle 16
:headerHeight style/messages-list-vertical-padding
:footerWidth style/messages-list-vertical-padding
:enableArrayScrollingOptimization true
:inverted true
:ref #(reset! messages-list-ref %)
:on-scroll (fn [e]
(let [ne (.-nativeEvent e)
y (.-y (.-contentOffset ne))]
(when (<= y 0)
(when @scroll-timer (js/clearTimeout @scroll-timer))
(reset! scroll-timer (js/setTimeout #(re-frame/dispatch [:chat.ui/load-more-messages]) 300)))
(reset! scroll-height (+ y (.-height (.-layoutMeasurement ne))))))}
[react/view
(doall
(for [{:keys [from content] :as message-obj} (take @messages-to-load messages)]
^{:key message-obj}
[message-row
{:group-chat group-chat
:modal? modal?
:current-public-key current-public-key
:row message-obj
:idx #(or (:message-id message-obj) (:value message-obj))
:list-ref messages-list-ref}]))]]
(if pending-invite-inviter-name
[group-chat-footer chat-id])])))
(defview chat-root [modal?]
(letsubs [{:keys [public? chat-id chat-name show-input? group-chat contact] :as current-chat}
[:chats/current-chat]
current-chat-id [:chats/current-chat-id]
input-bottom-sheet [:chats/current-chat-ui-prop :input-bottom-sheet]
two-pane-ui-enabled? [:two-pane-ui-enabled?]
anim-translate-y (animation/create-value
(if two-pane-ui-enabled? 0 connectivity/neg-connectivity-bar-height))]
[react/view {:style style/chat-view
:on-layout (fn [e]
(re-frame/dispatch [:set :layout-height (-> e .-nativeEvent .-layout .-height)]))}
[toolbar/toolbar
{:chat? true
:style {:z-index 2}}
(if modal?
[toolbar/nav-button
(toolbar.actions/close toolbar.actions/default-handler)]
toolbar/nav-back-home)
[toolbar-content/toolbar-content-view]
(when-not modal?
[toolbar/actions
[{:icon :main-icons/more
:icon-opts {:color :black
:accessibility-label :chat-menu-button}
:handler #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[sheets/actions current-chat])
:height 256}])}]])]
(when-not two-pane-ui-enabled?
[connectivity/connectivity-view anim-translate-y])
[connectivity/connectivity-animation-wrapper
{}
anim-translate-y
false
[messages-view-animation
(if (and (= chat-id current-chat-id) (not group-chat) (not (contact.db/added? contact)))
[add-contact-bar chat-id])
;;TODO(kozieiev) : When FlatList in react-native-desktop become viable it should be used instead of optimized ScrollView for chat
(if platform/desktop?
[messages-view-desktop current-chat modal?]
[messages-view current-chat modal?])]]
(when show-input?
[input/container])
(case input-bottom-sheet (case input-bottom-sheet
:stickers :stickers
[stickers/stickers-view] [stickers/stickers-view]
:extensions :extensions
[extensions/extensions-view] [extensions/extensions-view]
nil)])) nil)))
(defview chat [] (defview chat []
[chat-root false]) (letsubs [{:keys [chat-id show-input? group-chat contact] :as current-chat}
[:chats/current-chat]]
(defview chat-modal [] [react/view {:style {:flex 1}}
[chat-root true]) [connectivity/connectivity
[topbar current-chat]
(defview select-chat [] [react/view {:style {:flex 1}}
[react/view {:align-items :center :justify-content :center :flex 1} ;;TODO contact.db/added? looks weird here, move to events
[react/text style/select-chat (when (and (not group-chat) (not (contact.db/added? contact)))
(i18n/label :t/select-chat)]]) [add-contact-bar chat-id])
[messages-view current-chat]]]
(when show-input?
[input/container])
[bottom-sheet]]))

View File

@ -1,12 +1,8 @@
(ns status-im.ui.screens.home.sheet.views (ns status-im.ui.screens.home.sheet.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame] (:require [re-frame.core :as re-frame]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.list-selection :as list-selection] [status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.utils.universal-links.core :as universal-links]
[status-im.utils.platform :as platform]
[status-im.ui.components.list-item.views :as list-item] [status-im.ui.components.list-item.views :as list-item]
[status-im.utils.config :as config])) [status-im.utils.config :as config]))

View File

@ -1,22 +1,12 @@
(ns status-im.ui.screens.home.styles (ns status-im.ui.screens.home.styles
(:require [status-im.ui.components.colors :as colors] (:require [status-im.ui.components.colors :as colors]
[status-im.utils.styles :as styles] [status-im.utils.platform :as platform]
[status-im.ui.components.search-input.styles :as search-input.styles] [status-im.ui.components.tabbar.styles :as tabs.styles]))
[status-im.utils.platform :as platform]))
(defn toolbar []
{:background-color colors/white})
(def sync-wrapper
{:flex-direction :row})
(def sync-info
{:margin-horizontal 15})
(def last-message-container (def last-message-container
{:flex-shrink 1}) {:flex-shrink 1})
(styles/def last-message-text (def last-message-text
{:flex 1 {:flex 1
:align-self :stretch :align-self :stretch
:line-height 22 :line-height 22
@ -31,31 +21,6 @@
:width 12 :width 12
:height 12}) :height 12})
(def search-container
(merge
search-input.styles/search-container
(when platform/ios?
{:position :absolute
:top (- search-input.styles/search-input-height)
:width "100%"})))
(def filter-section-title
{:margin-left 16
:margin-top 14
:margin-bottom 4
:color colors/gray})
(def status-container
{:flex-direction :row
:top 16
:right 16})
(def status-image
{:opacity 0.6
:margin-right 4
:width 16
:height 16})
(def datetime-text (def datetime-text
{:color colors/text-gray {:color colors/text-gray
:font-size 10 :font-size 10
@ -64,34 +29,13 @@
:align-items :center :align-items :center
:line-height 12}) :line-height 12})
(styles/def new-messages-text
{:left 0
:font-size 12
:color colors/blue
:text-align :center
:android {:top 2}
:ios {:top 3}
:desktop {:top 3}})
(def group-icon
{:margin-top 8
:margin-right 6
:width 14
:height 9
:tint-color :white})
(def no-chats
{:flex 1
:padding-top 16
:padding-horizontal 16
:background-color :white})
(def chat-tooltip (def chat-tooltip
{:align-items :center {:align-items :center
:border-color colors/gray-lighter :border-color colors/gray-lighter
:border-width 1 :border-width 1
:border-radius 16 :border-radius 16
:margin 16}) :margin 16
:margin-bottom 68})
(def no-chats-text (def no-chats-text
{:margin-top 50 {:margin-top 50
@ -103,9 +47,6 @@
{:flex 1 {:flex 1
:justify-content :flex-end}) :justify-content :flex-end})
(def welcome-image-container
{:align-items :center})
(def welcome-text (def welcome-text
{:typography :header {:typography :header
:text-align :center}) :text-align :center})
@ -124,12 +65,12 @@
:margin-horizontal 40 :margin-horizontal 40
:color colors/gray}) :color colors/gray})
(defn action-button-container [home-width] (def action-button-container
{:position :absolute {:position :absolute
:z-index 2 :z-index 2
:align-items :center :align-items :center
:align-self :center
:bottom 16 :bottom 16
:left (- (/ home-width 2) 20)
:width 40 :width 40
:height 40}) :height 40})
@ -169,16 +110,6 @@
{:margin-top 10 {:margin-top 10
:margin-bottom 18}) :margin-bottom 18})
(def tag-text
{:font-size 13
:font-weight "500"
:line-height 20
:margin-left 10
:margin-right 10
:margin-top 6
:margin-bottom 6
:color colors/blue})
(def close-icon-container (def close-icon-container
{:width 24 {:width 24
:height 24 :height 24
@ -186,3 +117,10 @@
:background-color colors/gray :background-color colors/gray
:align-items :center :align-items :center
:justify-content :center}) :justify-content :center})
(def home-container
(merge
{:flex 1}
;;TODO move this to navigation layer
(when platform/ios?
{:margin-bottom tabs.styles/tabs-diff})))

View File

@ -7,29 +7,23 @@
[status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.components.icons.vector-icons :as icons]
[status-im.ui.components.list.views :as list] [status-im.ui.components.list.views :as list]
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.screens.home.styles :as styles] [status-im.ui.screens.home.styles :as styles]
[status-im.utils.platform :as platform]
[status-im.ui.components.tabbar.styles :as tabs.styles]
[status-im.ui.screens.home.views.inner-item :as inner-item] [status-im.ui.screens.home.views.inner-item :as inner-item]
[status-im.ui.components.common.common :as components.common] [status-im.ui.components.common.common :as components.common]
[status-im.ui.components.list-selection :as list-selection] [status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.animation :as animation]
[status-im.constants :as constants]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.ui.screens.add-new.new-public-chat.view :as new-public-chat] [status-im.ui.screens.add-new.new-public-chat.view :as new-public-chat]
[status-im.ui.components.button :as button] [status-im.ui.components.button :as button]
[status-im.ui.components.search-input.view :as search-input] [status-im.ui.components.search-input.view :as search-input]
[status-im.ui.components.search-input.styles :as search-input.styles]) [cljs-bean.core :as bean]
[status-im.ui.components.topbar :as topbar])
(:require-macros [status-im.utils.views :as views])) (:require-macros [status-im.utils.views :as views]))
(defonce search-active? (reagent/atom false))
(defn welcome-image-wrapper [] (defn welcome-image-wrapper []
(let [dimensions (reagent/atom {})] (let [dimensions (reagent/atom {})]
(fn [] (fn []
[react/view {:on-layout (fn [e] [react/view {:on-layout (fn [e]
(reset! dimensions (js->clj (-> e .-nativeEvent .-layout) :keywordize-keys true))) (reset! dimensions (bean/->clj (-> e .-nativeEvent .-layout))))
:style {:align-items :center :style {:align-items :center
:justify-content :center :justify-content :center
:flex 1}} :flex 1}}
@ -86,17 +80,11 @@
[react/view {:style {:flex 1 :flex-direction :row :align-items :center :justify-content :center}} [react/view {:style {:flex 1 :flex-direction :row :align-items :center :justify-content :center}}
[react/i18n-text {:style styles/welcome-blank-text :key :welcome-blank-message}]]) [react/i18n-text {:style styles/welcome-blank-text :key :welcome-blank-message}]])
(defn chat-list-footer [hide-home-tooltip?] (defonce search-active? (reagent/atom false))
(let [show-tooltip? (and (not hide-home-tooltip?) (not @search-active?))]
[react/view
(when show-tooltip?
[home-tooltip-view])
[react/view {:height 68 :flex 1}]]))
(defn search-input-wrapper [search-filter] (defn search-input-wrapper [search-filter]
[search-input/search-input [search-input/search-input
{:search-active? search-active? {:search-active? search-active?
:search-container-style styles/search-container
:search-filter search-filter :search-filter search-filter
:on-cancel #(re-frame/dispatch [:search/home-filter-changed nil]) :on-cancel #(re-frame/dispatch [:search/home-filter-changed nil])
:on-focus (fn [search-filter] :on-focus (fn [search-filter]
@ -105,102 +93,43 @@
:on-change (fn [text] :on-change (fn [text]
(re-frame/dispatch [:search/home-filter-changed text]))}]) (re-frame/dispatch [:search/home-filter-changed text]))}])
(defn section-footer [{:keys [title data]}] (views/defview chats-list []
(when (and @search-active? (empty? data)) (views/letsubs [loading? [:chats/loading?]
[list/big-list-item {:keys [chats all-home-items search-filter]} [:home-items]
{:text (i18n/label :t/no-result)
:text-color colors/gray
:hide-chevron? true
:action-fn #()
:icon (case title
"messages" :main-icons/one-on-one-chat
"browser" :main-icons/browser
"chats" :main-icons/message)
:icon-color colors/gray}]))
(views/defview home-filtered-items-list []
(views/letsubs
[{:keys [chats all-home-items search-filter]} [:home-items]
{:keys [hide-home-tooltip?]} [:multiaccount]] {:keys [hide-home-tooltip?]} [:multiaccount]]
(let [list-ref (reagent/atom nil)] (if loading?
[list/section-list [react/activity-indicator {:flex 1 :animating true}]
(merge (if (and (empty? all-home-items) hide-home-tooltip? (not @search-active?))
{:sections [{:title :t/chats [welcome-blank-page]
:data (if @search-active? chats all-home-items)}] (let [data (if @search-active? chats all-home-items)]
:key-fn first [list/flat-list
;; true by default on iOS {:key-fn first
:stickySectionHeadersEnabled false
:keyboard-should-persist-taps :always :keyboard-should-persist-taps :always
:ref #(reset! list-ref %) :data data
:footer [chat-list-footer hide-home-tooltip?] :render-fn inner-item/home-list-item
:contentInset {:top search-input.styles/search-input-height} :header (when (or (not-empty data) @search-active?)
:render-section-header-fn (fn [data] [react/view])
:render-section-footer-fn section-footer
:render-fn (fn [home-item]
[inner-item/home-list-item home-item])
:header (when (or @search-active? (not-empty all-home-items))
[search-input-wrapper search-filter]) [search-input-wrapper search-filter])
:on-scroll-end-drag :footer (if (and (not hide-home-tooltip?) (not @search-active?))
(fn [e] [home-tooltip-view]
(let [y (-> e .-nativeEvent .-contentOffset .-y) [react/view {:height 68}])}])))))
hide-searchbar? (cond
platform/ios? (and (neg? y) (> y (- (/ search-input.styles/search-input-height 2))))
platform/android? (and (< y search-input.styles/search-input-height) (> y (/ search-input.styles/search-input-height 2))))]
(if hide-searchbar?
(.scrollToLocation @list-ref #js {:sectionIndex 0 :itemIndex 0}))))})])))
(views/defview home-action-button [home-width] (views/defview plus-button []
(views/letsubs [logging-in? [:multiaccounts/login]] (views/letsubs [logging-in? [:multiaccounts/login]]
[react/view (styles/action-button-container home-width) [react/view styles/action-button-container
[react/touchable-highlight {:accessibility-label :new-chat-button [react/touchable-highlight
:on-press (when-not logging-in? #(re-frame/dispatch [:bottom-sheet/show-sheet :add-new {}]))} {:accessibility-label :new-chat-button
:on-press (when-not logging-in?
#(re-frame/dispatch [:bottom-sheet/show-sheet :add-new {}]))}
[react/view styles/action-button [react/view styles/action-button
(if logging-in? (if logging-in?
[react/activity-indicator {:color :white [react/activity-indicator {:color :white
:animating true}] :animating true}]
[icons/icon :main-icons/add {:color :white}])]]])) [icons/icon :main-icons/add {:color :white}])]]]))
(views/defview home [loading?] (defn home []
(views/letsubs [react/keyboard-avoiding-view {:style styles/home-container}
[anim-translate-y (animation/create-value connectivity/neg-connectivity-bar-height) [connectivity/connectivity
{:keys [all-home-items]} [:home-items] [topbar/topbar {:title :t/chat :navigation :none
{:keys [hide-home-tooltip?]} [:multiaccount] :show-border? true}]
window-width [:dimensions/window-width] [chats-list]]
two-pane-ui-enabled? [:two-pane-ui-enabled?]] [plus-button]])
(let [home-width (if (> window-width constants/two-pane-min-width)
(max constants/left-pane-min-width (/ window-width 3))
window-width)]
[react/view (merge {:flex 1
:width home-width}
(when platform/ios?
{:margin-bottom tabs.styles/tabs-diff})
(when two-pane-ui-enabled?
{:border-right-width 1 :border-right-color colors/gray-lighter}))
[react/keyboard-avoiding-view {:style {:flex 1}
:on-layout (fn [e]
(re-frame/dispatch
[:set-once :content-layout-height
(-> e .-nativeEvent .-layout .-height)]))}
[toolbar/toolbar {:style {:z-index 2}} nil [toolbar/content-title (i18n/label :t/chat)]]
;; toolbar, connectivity-view, cannectivity-animation-wrapper are expected
;; to be next to each other as siblings for them to work effctively.
;; les-debug-info being here could disrupt that. Assuming its purpose is
;; debug only, commenting it out for now.
;; [les-debug-info]
[connectivity/connectivity-view anim-translate-y]
[connectivity/connectivity-animation-wrapper
{}
anim-translate-y
true
(if loading?
[react/activity-indicator {:flex 1
:animating true}]
[react/view {:flex 1}
(if (and (empty? all-home-items) hide-home-tooltip? (not @search-active?))
[welcome-blank-page]
[home-filtered-items-list])])]
[home-action-button home-width]]])))
(views/defview home-wrapper []
(views/letsubs [loading? [:chats/loading?]]
[home loading?]))

View File

@ -4,7 +4,6 @@
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen] [status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.common.common :as components.common]
[status-im.ui.screens.chat.sheets :as sheets] [status-im.ui.screens.chat.sheets :as sheets]
[status-im.ui.components.list-item.views :as list-item] [status-im.ui.components.list-item.views :as list-item]
[status-im.ui.components.badge :as badge] [status-im.ui.components.badge :as badge]
@ -15,7 +14,7 @@
[status-im.utils.datetime :as time]) [status-im.utils.datetime :as time])
(:require-macros [status-im.utils.views :refer [defview letsubs]])) (:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn message-content-text [{:keys [content content-type] :as message}] (defn message-content-text [{:keys [content content-type]}]
[react/view styles/last-message-container [react/view styles/last-message-container
(cond (cond
@ -26,6 +25,7 @@
(= constants/content-type-sticker content-type) (= constants/content-type-sticker content-type)
[react/image {:style {:margin 1 :width 20 :height 20} [react/image {:style {:margin 1 :width 20 :height 20}
;;TODO (perf) move to event
:source {:uri (contenthash/url (-> content :sticker :hash))}}] :source {:uri (contenthash/url (-> content :sticker :hash))}}]
(string/blank? (:text content)) (string/blank? (:text content))
@ -37,31 +37,32 @@
:number-of-lines 1 :number-of-lines 1
:ellipsize-mode :tail :ellipsize-mode :tail
:accessibility-label :chat-message-text} :accessibility-label :chat-message-text}
(string/trim-newline (:text content))])]) ;;TODO (perf) move to event
(-> (:text content)
(subs 0 40)
(string/trim-newline))])])
(defn message-timestamp [timestamp] (defn message-timestamp [timestamp]
(when timestamp (when timestamp
[react/text {:style styles/datetime-text [react/text {:style styles/datetime-text
:accessibility-label :last-message-time-text} :accessibility-label :last-message-time-text}
;;TODO (perf) move to event
(string/upper-case (time/to-short-str timestamp))])) (string/upper-case (time/to-short-str timestamp))]))
(defview unviewed-indicator [chat-id] (defn unviewed-indicator [{:keys [unviewed-messages-count public?]}]
(letsubs [{:keys [unviewed-messages-count public?]} [:chats/chat chat-id]]
(when (pos? unviewed-messages-count) (when (pos? unviewed-messages-count)
(if public? (if public?
[react/view {:style styles/public-unread [react/view {:style styles/public-unread
:accessibility-label :unviewed-messages-public}] :accessibility-label :unviewed-messages-public}]
[badge/message-counter unviewed-messages-count])))) [badge/message-counter unviewed-messages-count])))
(defn home-list-item [[_ home-item]] (defn home-list-item [[_ home-item]]
(let [{:keys (let [{:keys [chat-id chat-name color online group-chat
[chat-id chat-name public? contact timestamp last-message]}
color online group-chat home-item
public? contact
timestamp
last-message]} home-item
private-group? (and group-chat (not public?)) private-group? (and group-chat (not public?))
public-group? (and group-chat public?) public-group? (and group-chat public?)
;;TODO (perf) move to event
truncated-chat-name (utils/truncate-str chat-name 30)] truncated-chat-name (utils/truncate-str chat-name 30)]
[list-item/list-item [list-item/list-item
{:icon [chat-icon.screen/chat-icon-view-chat-list {:icon [chat-icon.screen/chat-icon-view-chat-list
@ -81,7 +82,7 @@
[message-content-text {:content (:content last-message) [message-content-text {:content (:content last-message)
:content-type (:content-type last-message)}] :content-type (:content-type last-message)}]
tribute-label)) tribute-label))
:subtitle-row-accessory [unviewed-indicator chat-id] :subtitle-row-accessory [unviewed-indicator home-item]
:on-press #(do :on-press #(do
(re-frame/dispatch [:dismiss-keyboard]) (re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id]) (re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])

View File

@ -4,7 +4,6 @@
{:name :chat-stack {:name :chat-stack
:screens [:home :screens [:home
:chat :chat
:select-chat
:profile :profile
:take-picture :take-picture
:new-group :new-group
@ -13,5 +12,4 @@
:group-chat-profile :group-chat-profile
:stickers :stickers
:stickers-pack] :stickers-pack]
:config {:initialRouteName :home :config {:initialRouteName :home}})
:emptyRightPaneName :select-chat}})

View File

@ -1,8 +1,7 @@
(ns status-im.ui.screens.routing.modals) (ns status-im.ui.screens.routing.modals)
(def modal-screens (def modal-screens
[:chat-modal [:stickers-pack-modal
:stickers-pack-modal
:tribute-learn-more :tribute-learn-more
:selection-modal-screen :selection-modal-screen
:wallet-transactions-filter :wallet-transactions-filter

View File

@ -99,9 +99,8 @@
:keycard-unpaired keycard/unpaired :keycard-unpaired keycard/unpaired
:keycard-login-pin keycard/login-pin :keycard-login-pin keycard/login-pin
:not-keycard keycard/not-keycard :not-keycard keycard/not-keycard
:home home/home-wrapper :home home/home
:chat chat/chat :chat chat/chat
:select-chat chat/select-chat
:profile profile.contact/profile :profile profile.contact/profile
:new-chat [:modal new-chat/new-chat] :new-chat [:modal new-chat/new-chat]
:qr-scanner [:modal qr-scanner/qr-scanner] :qr-scanner [:modal qr-scanner/qr-scanner]
@ -116,7 +115,6 @@
:stickers-pack stickers/pack :stickers-pack stickers/pack
:stickers-pack-modal [:modal stickers/pack-modal] :stickers-pack-modal [:modal stickers/pack-modal]
:tribute-learn-more [:modal tr-to-talk/learn-more] :tribute-learn-more [:modal tr-to-talk/learn-more]
:chat-modal [:modal chat/chat-modal]
:wallet wallet.accounts/accounts-overview :wallet wallet.accounts/accounts-overview
:wallet-account wallet.account/account :wallet-account wallet.account/account
:collectibles-list collectibles/collectibles-list :collectibles-list collectibles/collectibles-list

View File

@ -55,12 +55,14 @@
hex/decode hex/decode
b58/encode))}))) b58/encode))})))
(defn url [hex] (defn url-fn [hex]
(let [{:keys [namespace hash]} (decode (ethereum/normalized-hex hex))] (let [{:keys [namespace hash]} (decode (ethereum/normalized-hex hex))]
(case namespace (case namespace
:ipfs (str "https://ipfs.infura.io/ipfs/" hash) :ipfs (str "https://ipfs.infura.io/ipfs/" hash)
""))) "")))
(def url (memoize url-fn))
(fx/defn cat (fx/defn cat
[cofx {:keys [contenthash on-success on-failure]}] [cofx {:keys [contenthash on-success on-failure]}]
(let [{:keys [namespace hash]} (decode contenthash)] (let [{:keys [namespace hash]} (decode contenthash)]