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 (fx/merge cofx
modal? (navigation/navigate-to-cofx :chat {})
(fx/merge cofx (preload-chat-data chat-id)))
(navigation/navigate-to-cofx :chat-modal {})
(preload-chat-data chat-id))
:else
(fx/merge cofx
(navigation/navigate-to-cofx :chat {})
(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,24 +1,17 @@
(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
:font-size 14 :font-size 14
:top 8}) :top 8})

View File

@ -43,7 +43,7 @@
:background-color color}) :background-color color})
(views/defview loading-indicator [parent-width] (views/defview loading-indicator [parent-width]
(views/letsubs [blue-bar-left-margin (animation/create-value 0) (views/letsubs [blue-bar-left-margin (animation/create-value 0)
white-bar-left-margin (animation/create-value 0)] white-bar-left-margin (animation/create-value 0)]
{:component-did-mount {:component-did-mount
(fn [_] (fn [_]
@ -62,11 +62,11 @@
(animation/parallel (animation/parallel
[(animation/timing blue-bar-left-margin (easing :out 0)) [(animation/timing blue-bar-left-margin (easing :out 0))
(animation/timing white-bar-left-margin (easing :out 0))])]))))} (animation/timing white-bar-left-margin (easing :out 0))])]))))}
[react/view {:style {:width parent-width [react/view {:style {:width parent-width
:position :absolute :position :absolute
:top -3 :top -3
:z-index 3 :z-index 3
:height 3 :height 3
:background-color colors/white}} :background-color colors/white}}
[react/animated-view {:style (animated-bar-style blue-bar-left-margin [react/animated-view {:style (animated-bar-style blue-bar-left-margin
parent-width parent-width
@ -87,10 +87,10 @@ all connectivity views (we have at least one view in home and one in chat)"
(animation/start (animation/start
(animation/parallel (animation/parallel
[(animation/timing anim-opacity [(animation/timing anim-opacity
{:toValue 0 {:toValue 0
:delay 800 :delay 800
:duration 150 :duration 150
:easing (.-ease (animation/easing)) :easing (.-ease (animation/easing))
:useNativeDriver true}) :useNativeDriver true})
(animation/timing anim-y (animation/timing anim-y
{:toValue (if platform/desktop? 0 neg-connectivity-bar-height) {:toValue (if platform/desktop? 0 neg-connectivity-bar-height)
@ -111,14 +111,14 @@ all connectivity views (we have at least one view in home and one in chat)"
(animation/start (animation/start
(animation/parallel (animation/parallel
[(animation/timing anim-opacity [(animation/timing anim-opacity
{:toValue 1 {:toValue 1
:duration 150 :duration 150
:easing (.-ease (animation/easing)) :easing (.-ease (animation/easing))
:useNativeDriver true}) :useNativeDriver true})
(animation/timing anim-y (animation/timing anim-y
{:toValue (if platform/desktop? connectivity-bar-height 0) {:toValue (if platform/desktop? connectivity-bar-height 0)
:duration 150 :duration 150
:easing (.-ease (animation/easing)) :easing (.-ease (animation/easing))
:useNativeDriver true})]) :useNativeDriver true})])
;; second param of start() - a callback that fires when animation stops ;; second param of start() - a callback that fires when animation stops
#(do (reset! to-hide? true) (reset! status-hidden false)))) #(do (reset! to-hide? true) (reset! status-hidden false))))
@ -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,38 +147,35 @@ 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}]
[react/animated-view {:style (styles/text-wrapper (when-not @status-hidden
(assoc opts [react/animated-view {:style (styles/text-wrapper
:height (if platform/desktop? (assoc opts
anim-translate-y :height connectivity-bar-height
connectivity-bar-height) :background-color (if connected?
:background-color (if connected? colors/green
colors/green colors/gray)
colors/gray) :transform anim-translate-y
;;TODO how does this affect desktop? :opacity anim-opacity))
:transform anim-translate-y :accessibility-label :connection-status-text}
:opacity anim-opacity (when connecting?
:modal? (= view-id :chat-modal))) [react/activity-indicator {:color colors/white :margin-right 6}])
:accessibility-label :connection-status-text} (if (= message :mobile-network)
(when connecting? [react/nested-text {:style styles/text
[react/activity-indicator {:color colors/white :margin-right 6}]) :on-press (when on-press-event #(re-frame/dispatch [on-press-event]))}
(if (= message :mobile-network) (i18n/label :t/waiting-for-wifi) " "
[react/nested-text {:style styles/text [{:style {:text-decoration-line :underline}}
:on-press (when on-press-event #(re-frame/dispatch [on-press-event]))} (i18n/label :t/waiting-for-wifi-change)]]
(i18n/label :t/waiting-for-wifi) " " (when message
[{:style {:text-decoration-line :underline}} [react/text {:style styles/text
(i18n/label :t/waiting-for-wifi-change)]] :on-press (when on-press-event #(re-frame/dispatch [on-press-event]))}
(when message (i18n/label message)]))]))})))
[react/text {:style styles/text
:on-press (when on-press-event #(re-frame/dispatch [on-press-event]))}
(i18n/label message)]))])})))
;; timer updating the enqueued status ;; timer updating the enqueued status
(def timer (atom nil)) (def timer (atom nil))
;; connectivity status change going to be persisted to :connectivity/ui-status-properties ;; connectivity status change going to be persisted to :connectivity/ui-status-properties
(def enqueued-connectivity-status-properties (atom nil)) (def enqueued-connectivity-status-properties (atom nil))
(defn propagate-status (defn propagate-status
"Smoothly propagate from :connectivity/status-properties subscription to "Smoothly propagate from :connectivity/status-properties subscription to
@ -191,24 +188,29 @@ 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!
(reset! timer nil) timer
(when @enqueued-connectivity-status-properties (utils/set-timeout
(re-frame/dispatch [:set :connectivity/ui-status-properties @enqueued-connectivity-status-properties]) #(do
(reset! enqueued-connectivity-status-properties nil))) (reset! timer nil)
(when @enqueued-connectivity-status-properties
(re-frame/dispatch [:set
:connectivity/ui-status-properties
@enqueued-connectivity-status-properties])
(reset! enqueued-connectivity-status-properties nil)))
;; timeout choice: ;; timeout choice:
;; if the app is in foreground or logged-in for less than <timeframe>, ;; if the app is in foreground or logged-in for less than <timeframe>,
;; postpone state changes for <long> otherwise <short> ;; postpone state changes for <long> otherwise <short>
(let [ts (max (let [ts (max
(or logged-in-since 0) (or logged-in-since 0)
(or app-active-since 0)) (or app-active-since 0))
ts-diff (- (datetime/timestamp) ts) ts-diff (- (datetime/timestamp) ts)
timeout (if (< ts-diff timewindow-for-long-delay) timeout (if (< ts-diff timewindow-for-long-delay)
long-delay long-delay
standard-delay)] standard-delay)]
(log/debug "propagate-status set-timeout: " logged-in-since app-active-since ts-diff timeout) (log/debug "propagate-status set-timeout: " logged-in-since app-active-since ts-diff timeout)
timeout)))))) timeout))))))
(defn status-propagator-dummy-view (defn status-propagator-dummy-view
"this empty view is needed to react propagate status-properties to ui-status-properties" "this empty view is needed to react propagate status-properties to ui-status-properties"
@ -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))}
(when (and loading-indicator? @status-hidden) [react/view {:style {:z-index 2 :background-color :white}}
[loading-indicator @window-width]) header
;; This view below exists only to hide the connectivity-status bar when "connected". [react/view
;; Ideally connectivity-status bar would be hidden under "toolbar/toolbar", (when (and loading-indicator? @status-hidden)
;; but that has to be transparent(enven though it sits above the bar) [loading-indicator @window-width])]]
;; 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]
[status-propagator-dummy-view {:status-properties status-properties ;;TODO this is something weird, rework
:app-active-since app-active-since [status-propagator-dummy-view {:status-properties status-properties
:logged-in-since logged-in-since :app-active-since app-active-since
:ui-status-properties ui-status-properties}]]))) :logged-in-since logged-in-since
:ui-status-properties ui-status-properties}]
;; "push?" determines whether "content" gets pushed down when disconnected footer])))
;; 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,52 +1,44 @@
(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 [react/view {:style (or search-container-style styles/search-container)}
search-filter [react/view {:style styles/search-input-container}
auto-focus]}] [icons/icon :main-icons/search {:color colors/gray
(views/letsubs :container-style {:margin-left 6
[input-ref (reagent/atom nil)] :margin-right 2}}]
[react/view {:style (or search-container-style styles/search-container)} [react/text-input {:placeholder (i18n/label :t/search)
[react/view {:style styles/search-input-container} :blur-on-submit true
[icons/icon :main-icons/search {:color colors/gray :multiline false
:container-style {:margin-left 6 :ref #(reset! input-ref %)
:margin-right 2}}] :style styles/search-input
[react/text-input {:placeholder (i18n/label :t/search) :default-value search-filter
:blur-on-submit true :auto-focus auto-focus
:multiline false :auto-correct false
:ref #(reset! input-ref %) :auto-capitalize false
:style styles/search-input :on-focus #(do
:default-value search-filter (when on-focus
:auto-focus auto-focus (on-focus search-filter))
:auto-correct false (reset! search-active? true))
:auto-capitalize false :on-change (fn [e]
:on-focus #(do (let [native-event (.-nativeEvent e)
(when on-focus text (.-text native-event)]
(on-focus search-filter)) (when on-change
(reset! search-active? true)) (on-change text))))}]]
:on-change (fn [e] (when @search-active?
(let [native-event (.-nativeEvent e) [react/touchable-highlight
text (.-text native-event)] {:on-press (fn []
(when on-change (.clear @input-ref)
(on-change text))))}]] (.blur @input-ref)
(when @search-active? (when on-cancel (on-cancel))
[react/touchable-highlight (reset! search-active? false))
{:on-press (fn [] :style {:margin-left 16}}
(.clear @input-ref) [react/text {:style {:color colors/blue}}
(.blur @input-ref) (i18n/label :t/cancel)]])])))
(when on-cancel
(on-cancel))
(reset! search-active? false))
:style {:margin-left 16}}
[react/text {:style {:color colors/blue}}
(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,8 +49,12 @@
(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}
[react/text {:style {:typography :title-bold :text-align :center} :number-of-lines 2} [react/text {:style {:typography :title-bold :text-align :center} :number-of-lines 2}
(utils.label/stringify title)]])])))) (utils.label/stringify title)]])]))))

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}]
@ -183,4 +168,4 @@
(re-frame/dispatch [:chat.ui/send-current-message]) (re-frame/dispatch [:chat.ui/send-current-message])
(set-text ""))] (set-text ""))]
[send-button/send-button-view {:input-text input-text} [send-button/send-button-view {:input-text input-text}
#(re-frame/dispatch [:chat.ui/send-current-message])]))]]))) #(re-frame/dispatch [:chat.ui/send-current-message])]))]])))

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]))
@ -21,4 +20,4 @@
[vector-icons/icon :main-icons/arrow-up [vector-icons/icon :main-icons/arrow-up
{:container-style style/send-message-container {:container-style style/send-message-container
:accessibility-label :send-message-button :accessibility-label :send-message-button
:color :white}]]))) :color :white}]])))

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 :on-long-press #(cond (or (= content-type constants/content-type-text)
outgoing (= content-type constants/content-type-emoji))
modal? (re-frame/dispatch [:bottom-sheet/show-sheet
content] {:content (sheets/message-long-press message)
:as message} child] :height 192}])
[react/view (style/group-message-wrapper message) (and (= content-type constants/content-type-sticker)
from (not outgoing))
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (sheets/sticker-long-press message)
:height 64}]))}))
(defn message-content-wrapper
"Author, userpic and delivery wrapper"
[{:keys [alias first-in-group? display-photo? identicon display-username?
from outgoing]
:as message} content]
[react/view {:style (style/message-wrapper message)
:accessibility-label :chat-item}
[react/view (style/message-body message) [react/view (style/message-body message)
(when display-photo? (when display-photo?
[react/view (style/message-author outgoing) ; userpic
[react/view (style/message-author-userpic outgoing)
(when first-in-group? (when first-in-group?
[react/touchable-highlight {:on-press #(when-not modal? (re-frame/dispatch [:chat.ui/show-profile from]))} [react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
[react/view [photos/member-identicon identicon]])])
[photos/member-photo from identicon]]])]) ; username
[react/view (style/group-message-view outgoing display-photo?) [react/view (style/message-author-wrapper outgoing display-photo?)
(when display-username? (when display-username?
[react/touchable-opacity {:style style/message-author-touchable [react/touchable-opacity {:style style/message-author-touchable
:on-press #(re-frame/dispatch [:chat.ui/show-profile from])} :on-press #(re-frame/dispatch [:chat.ui/show-profile from])}
;;TODO (perf) move to event
[message-author-name from alias]]) [message-author-name from alias]])
[react/view {:style (style/timestamp-content-wrapper outgoing)} ;;MESSAGE CONTENT
child]]] content]]
; delivery status
[react/view (style/delivery-status outgoing) [react/view (style/delivery-status outgoing)
[message-delivery-status message]]]) [message-delivery-status message]]])
(defn chat-message (defn chat-message [{:keys [content content-type] :as message}]
[{:keys [outgoing from group-chat modal? current-public-key content-type content] :as message}] (if (= content-type constants/content-type-command)
(let [sticker (:sticker content)] [message.command/comand-content message-content-wrapper message]
[react/view [react/touchable-highlight (message-press-handlers message)
[react/touchable-highlight [message-content-wrapper
{:on-press (fn [arg] message
(if (and platform/desktop? (= "right" (.-button (.-nativeEvent arg)))) (if (= content-type constants/content-type-text)
(re-frame/dispatch [:bottom-sheet/show-sheet ; text message
{:content (sheets/message-long-press message) [text-message message]
:height 192}]) (if (= content-type constants/content-type-status)
(do [message-content-status message]
(when (and (= content-type constants/content-type-sticker) (:pack sticker)) (if (= content-type constants/content-type-emoji)
(re-frame/dispatch [:stickers/open-sticker-pack (:pack sticker)])) [emoji-message message]
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:messages-focused? true (if (= content-type constants/content-type-sticker)
:input-bottom-sheet nil}]) [react/image {:style {:margin 10 :width 140 :height 140}
(when-not platform/desktop? ;;TODO (perf) move to event
(react/dismiss-keyboard!))))) :source {:uri (contenthash/url (-> content :sticker :hash))}}]
:on-long-press #(cond (or (= content-type constants/content-type-text) [message-bubble-wrapper message
(= content-type constants/content-type-emoji)) [react/text (str "Unhandled content-type " content-type)]]))))]]))
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (sheets/message-long-press message)
:height 192}])
(and (= content-type constants/content-type-sticker)
from (not outgoing))
(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (sheets/sticker-long-press message)
:height 64}]))}
[react/view {:accessibility-label :chat-item}
(let [incoming-group (and group-chat (not outgoing))]
[message-content message-body (merge message
{:current-public-key current-public-key
:group-chat group-chat
:modal? modal?
:incoming-group incoming-group})])]]]))

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 324}
:height adjusted-height} {: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
@ -303,4 +164,4 @@
(def tribute-received-note (def tribute-received-note
{:font-size 13 {:font-size 13
:line-height 18 :line-height 18
:text-align :center}) :text-align :center})

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,9 +45,9 @@
(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}
[react/view {:margin-right 10} [react/view {:margin-right 10}

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)]
@ -37,60 +30,4 @@
(or (and (= from current-public-key) (or (and (= from current-public-key)
[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))
@ -308,11 +97,11 @@
(when @messages-list-ref (when @messages-list-ref
(reset! state/viewable-item (reset! state/viewable-item
(when-let [last-visible-element (aget (.-viewableItems e) (dec (.-length (.-viewableItems e))))] (when-let [last-visible-element (aget (.-viewableItems e) (dec (.-length (.-viewableItems e))))]
(let [index (.-index last-visible-element) (let [index (.-index last-visible-element)
;; Get first not visible element, if it's a datemark/gap ;; Get first not visible element, if it's a datemark/gap
;; we might unnecessarely add messages on receiving as ;; we might unnecessarely add messages on receiving as
;; they do not have a clock value, but most of the times ;; they do not have a clock value, but most of the times
;; it will be a message ;; it will be a message
first-not-visible (aget (.-data (.-props @messages-list-ref)) (inc index))] first-not-visible (aget (.-data (.-props @messages-list-ref)) (inc index))]
(when (and first-not-visible (when (and first-not-visible
(= :message (:type first-not-visible))) (= :message (:type first-not-visible)))
@ -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 :ref #(reset! messages-list-ref %)
{:messages-focused? true :header (when pending-invite-inviter-name
:input-focused? false}]))} [chat.group/group-chat-footer chat-id])
(let [no-messages (empty? messages) :footer [chat-intro-header-container chat (empty? messages)]
flat-list-conf :data messages
{:data messages :inverted true
:ref #(reset! messages-list-ref %) :render-fn (fn [{:keys [outgoing] :as message} idx]
:footer [chat-intro-header-container chat no-messages] (let [type (:type message)]
:key-fn #(or (:message-id %) (:value %)) (if (= type :datemark)
:render-fn (fn [message idx] [message-datemark/chat-datemark (:value message)]
[message-row (if (= type :gap)
{:group-chat group-chat [gap/gap message idx messages-list-ref]
:modal? modal? ; message content
:current-public-key current-public-key [message/chat-message
:row message (assoc message
:idx idx :incoming-group (and group-chat (not outgoing))
:list-ref messages-list-ref}]) :group-chat group-chat
:inverted true :current-public-key current-public-key)]))))
:onViewableItemsChanged on-viewable-items-changed :on-viewable-items-changed on-viewable-items-changed
:onEndReached #(re-frame/dispatch [:chat.ui/load-more-messages]) :on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages])
:onScrollToIndexFailed #() :on-scroll-to-index-failed #() ;;don't remove this
:keyboardShouldPersistTaps :handled} :keyboard-should-persist-taps :handled}]))
group-header {:header [group-chat-footer chat-id]}]
(if pending-invite-inviter-name
[list/flat-list (merge flat-list-conf group-header)]
[list/flat-list flat-list-conf]))))
(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] (case input-bottom-sheet
(let [next-count (min all-messages-count (+ @messages-to-load load-step))] :stickers
(reset! messages-to-load next-count))) [stickers/stickers-view]
:extensions
(defview messages-view-desktop [{:keys [chat-id group-chat pending-invite-inviter-name]} [extensions/extensions-view]
modal?] nil)))
(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
:stickers
[stickers/stickers-view]
:extensions
[extensions/extensions-view]
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]))
@ -45,4 +41,4 @@
(list-selection/open-share {:message (i18n/label :t/get-status-at)}))}]]) (list-selection/open-share {:message (i18n/label :t/get-status-at)}))}]])
(def add-new (def add-new
{:content add-new-view}) {:content add-new-view})

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,37 +7,31 @@
[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}}
(let [padding 0 (let [padding 0
image-size (- (min (:width @dimensions) (:height @dimensions)) padding)] image-size (- (min (:width @dimensions) (:height @dimensions)) padding)]
[react/image {:source (resources/get-image :welcome) [react/image {:source (resources/get-image :welcome)
:resize-mode :contain :resize-mode :contain
:style {:width image-size :height image-size}}])]))) :style {:width image-size :height image-size}}])])))
(defn welcome [] (defn welcome []
[react/view {:style styles/welcome-view} [react/view {:style styles/welcome-view}
@ -47,9 +41,9 @@
[react/i18n-text {:style styles/welcome-text-description [react/i18n-text {:style styles/welcome-text-description
:key :welcome-to-status-description}]] :key :welcome-to-status-description}]]
[react/view {:align-items :center :margin-bottom 50} [react/view {:align-items :center :margin-bottom 50}
[components.common/button {:on-press #(re-frame/dispatch [:navigate-back]) [components.common/button {:on-press #(re-frame/dispatch [:navigate-back])
:accessibility-label :lets-go-button :accessibility-label :lets-go-button
:label (i18n/label :t/lets-go)}]]]) :label (i18n/label :t/lets-go)}]]])
(defn home-tooltip-view [] (defn home-tooltip-view []
[react/view styles/chat-tooltip [react/view styles/chat-tooltip
@ -86,121 +80,56 @@
[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] (when-not search-filter
(when-not search-filter (re-frame/dispatch [:search/home-filter-changed ""])))
(re-frame/dispatch [:search/home-filter-changed ""]))) :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) {:keys [hide-home-tooltip?]} [:multiaccount]]
:text-color colors/gray (if loading?
:hide-chevron? true [react/activity-indicator {:flex 1 :animating true}]
:action-fn #() (if (and (empty? all-home-items) hide-home-tooltip? (not @search-active?))
:icon (case title [welcome-blank-page]
"messages" :main-icons/one-on-one-chat (let [data (if @search-active? chats all-home-items)]
"browser" :main-icons/browser [list/flat-list
"chats" :main-icons/message) {:key-fn first
:icon-color colors/gray}])) :keyboard-should-persist-taps :always
:data data
:render-fn inner-item/home-list-item
:header (when (or (not-empty data) @search-active?)
[search-input-wrapper search-filter])
:footer (if (and (not hide-home-tooltip?) (not @search-active?))
[home-tooltip-view]
[react/view {:height 68}])}])))))
(views/defview home-filtered-items-list [] (views/defview plus-button []
(views/letsubs
[{:keys [chats all-home-items search-filter]} [:home-items]
{:keys [hide-home-tooltip?]} [:multiaccount]]
(let [list-ref (reagent/atom nil)]
[list/section-list
(merge
{:sections [{:title :t/chats
:data (if @search-active? chats all-home-items)}]
:key-fn first
;; true by default on iOS
:stickySectionHeadersEnabled false
:keyboard-should-persist-taps :always
:ref #(reset! list-ref %)
:footer [chat-list-footer hide-home-tooltip?]
:contentInset {:top search-input.styles/search-input-height}
: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])
:on-scroll-end-drag
(fn [e]
(let [y (-> e .-nativeEvent .-contentOffset .-y)
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/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,39 +37,40 @@
: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
contact group-chat truncated-chat-name color online false] contact group-chat truncated-chat-name color online false]
:title-prefix (cond :title-prefix (cond
private-group? :main-icons/tiny-group private-group? :main-icons/tiny-group
public-group? :main-icons/tiny-public public-group? :main-icons/tiny-public
:else nil) :else nil)
:title truncated-chat-name :title truncated-chat-name
:title-accessibility-label :chat-name-text :title-accessibility-label :chat-name-text
:title-row-accessory [message-timestamp (if (pos? (:whisper-timestamp last-message)) :title-row-accessory [message-timestamp (if (pos? (:whisper-timestamp last-message))
@ -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])
@ -91,4 +92,4 @@
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet :on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn [] {:content (fn []
[sheets/actions home-item]) [sheets/actions home-item])
:height 256}])}])) :height 256}])}]))

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)]