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]
[(create-main-screen-view current-view)
styles/flex
[(if (= current-view :chat-modal)
view
keyboard-avoiding-view)
[keyboard-avoiding-view
(merge {:flex 1 :flex-direction :column}
(when platform/android?
{:background-color :white}))

View File

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

View File

@ -248,40 +248,33 @@
(fx/defn navigate-to-chat
"Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data"
[cofx chat-id {:keys [modal? navigation-reset?]}]
(cond
modal?
(fx/merge cofx
(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))))
[cofx chat-id]
(fx/merge cofx
(navigation/navigate-to-cofx :chat {})
(preload-chat-data chat-id)))
(fx/defn start-chat
"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
(when (not= (multiaccounts.model/current-public-key cofx) chat-id)
(fx/merge cofx
(upsert-chat {:chat-id chat-id
:is-active true})
(transport.filters/load-chat chat-id)
(navigate-to-chat chat-id opts))))
(navigate-to-chat chat-id))))
(fx/defn start-public-chat
"Starts a new public chat"
[cofx topic {:keys [dont-navigate?] :as opts}]
[cofx topic {:keys [dont-navigate?]}]
(if (active-chat? cofx topic)
(when-not dont-navigate?
(navigate-to-chat cofx topic opts))
(navigate-to-chat cofx topic))
(fx/merge cofx
(add-public-chat topic)
(transport.filters/load-chat topic)
#(when-not dont-navigate?
(navigate-to-chat % topic opts)))))
(navigate-to-chat % topic)))))
(fx/defn disable-chat-cooldown
"Turns off chat cooldown (protection against message spamming)"

View File

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

View File

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

View File

@ -65,7 +65,7 @@
(data-store.messages/<-rpc %))
messages)
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 {}))]
(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-max-height :keyboard-max-height)
(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 :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 :networks/current-network :networks/current-network)
(reg-root-key-sub :networks/networks :networks/networks)
@ -566,32 +564,6 @@
(fn [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
:chats/current-chat-ui-props
:<- [::chat-ui-props]
@ -703,11 +675,8 @@
(assoc :show-input? true))))
(defn enrich-current-chat
[{:keys [messages chat-id might-have-join-time-messages?] :as chat}
ranges height input-height]
[{:keys [messages chat-id might-have-join-time-messages?] :as chat} ranges]
(assoc chat
:height height
:input-height input-height
:range
(get ranges chat-id)
:intro-status
@ -735,12 +704,10 @@
:<- [:chats/current-raw-chat]
:<- [:multiaccount/public-key]
:<- [:mailserver/ranges]
:<- [:chats/content-layout-height]
:<- [:chats/current-chat-ui-prop :input-height]
(fn [[{:keys [group-chat chat-id contact messages] :as current-chat}
my-public-key ranges height input-height]]
(fn [[{:keys [group-chat chat-id messages] :as current-chat}
my-public-key ranges]]
(when current-chat
(cond-> (enrich-current-chat current-chat ranges height input-height)
(cond-> (enrich-current-chat current-chat ranges)
(empty? messages)
(assoc :universal-link
(links/generate-link :public-chat :external chat-id))
@ -830,6 +797,7 @@
:<- [:chats/all-loaded?]
:<- [:chats/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)
(chat.db/add-datemarks)
(hydrate-messages messages)
@ -851,7 +819,7 @@
:<- [:contacts/contacts]
:<- [:multiaccount]
(fn [[contacts multiaccount] [_ id]]
(multiaccounts/displayed-photo (or (contacts id)
(multiaccounts/displayed-photo (or (get contacts id)
(when (= id (:public-key multiaccount))
multiaccount)
(contact.db/public-key->new-contact id)))))

View File

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

View File

@ -43,7 +43,7 @@
:background-color color})
(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)]
{:component-did-mount
(fn [_]
@ -62,11 +62,11 @@
(animation/parallel
[(animation/timing blue-bar-left-margin (easing :out 0))
(animation/timing white-bar-left-margin (easing :out 0))])]))))}
[react/view {:style {:width parent-width
:position :absolute
:top -3
:z-index 3
:height 3
[react/view {:style {:width parent-width
:position :absolute
:top -3
:z-index 3
:height 3
:background-color colors/white}}
[react/animated-view {:style (animated-bar-style blue-bar-left-margin
parent-width
@ -87,10 +87,10 @@ all connectivity views (we have at least one view in home and one in chat)"
(animation/start
(animation/parallel
[(animation/timing anim-opacity
{:toValue 0
:delay 800
:duration 150
:easing (.-ease (animation/easing))
{:toValue 0
:delay 800
:duration 150
:easing (.-ease (animation/easing))
:useNativeDriver true})
(animation/timing anim-y
{: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/parallel
[(animation/timing anim-opacity
{:toValue 1
:duration 150
:easing (.-ease (animation/easing))
{:toValue 1
:duration 150
:easing (.-ease (animation/easing))
:useNativeDriver true})
(animation/timing anim-y
{:toValue (if platform/desktop? connectivity-bar-height 0)
:duration 150
:easing (.-ease (animation/easing))
{:toValue (if platform/desktop? connectivity-bar-height 0)
:duration 150
:easing (.-ease (animation/easing))
:useNativeDriver true})])
;; second param of start() - a callback that fires when animation stops
#(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)))))
(defn connectivity-status
[{:keys [connected?]} anim-translate-y status-hidden]
(let [anim-translate-y (or anim-translate-y (animation/create-value 0))
[{:keys [connected?]} status-hidden]
(let [anim-translate-y (animation/create-value neg-connectivity-bar-height)
anim-opacity (animation/create-value 0)]
(reagent/create-class
{: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
anim-opacity anim-translate-y status-hidden))
:reagent-render
(fn [{:keys [view-id message on-press-event connected? connecting?] :as opts}]
[react/animated-view {:style (styles/text-wrapper
(assoc opts
:height (if platform/desktop?
anim-translate-y
connectivity-bar-height)
:background-color (if connected?
colors/green
colors/gray)
;;TODO how does this affect desktop?
:transform anim-translate-y
:opacity anim-opacity
:modal? (= view-id :chat-modal)))
:accessibility-label :connection-status-text}
(when connecting?
[react/activity-indicator {:color colors/white :margin-right 6}])
(if (= message :mobile-network)
[react/nested-text {:style styles/text
:on-press (when on-press-event #(re-frame/dispatch [on-press-event]))}
(i18n/label :t/waiting-for-wifi) " "
[{:style {:text-decoration-line :underline}}
(i18n/label :t/waiting-for-wifi-change)]]
(when message
[react/text {:style styles/text
:on-press (when on-press-event #(re-frame/dispatch [on-press-event]))}
(i18n/label message)]))])})))
(fn [{:keys [message on-press-event connected? connecting?] :as opts}]
(when-not @status-hidden
[react/animated-view {:style (styles/text-wrapper
(assoc opts
:height connectivity-bar-height
:background-color (if connected?
colors/green
colors/gray)
:transform anim-translate-y
:opacity anim-opacity))
:accessibility-label :connection-status-text}
(when connecting?
[react/activity-indicator {:color colors/white :margin-right 6}])
(if (= message :mobile-network)
[react/nested-text {:style styles/text
:on-press (when on-press-event #(re-frame/dispatch [on-press-event]))}
(i18n/label :t/waiting-for-wifi) " "
[{:style {:text-decoration-line :underline}}
(i18n/label :t/waiting-for-wifi-change)]]
(when 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
(def timer (atom nil))
(def timer (atom nil))
;; 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
"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! enqueued-connectivity-status-properties status-properties)
(when-not @timer
(reset! timer (utils/set-timeout #(do
(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)))
(reset!
timer
(utils/set-timeout
#(do
(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:
;; if the app is in foreground or logged-in for less than <timeframe>,
;; postpone state changes for <long> otherwise <short>
(let [ts (max
(or logged-in-since 0)
(or app-active-since 0))
ts-diff (- (datetime/timestamp) ts)
timeout (if (< ts-diff timewindow-for-long-delay)
long-delay
standard-delay)]
(log/debug "propagate-status set-timeout: " logged-in-since app-active-since ts-diff timeout)
timeout))))))
;; timeout choice:
;; if the app is in foreground or logged-in for less than <timeframe>,
;; postpone state changes for <long> otherwise <short>
(let [ts (max
(or logged-in-since 0)
(or app-active-since 0))
ts-diff (- (datetime/timestamp) ts)
timeout (if (< ts-diff timewindow-for-long-delay)
long-delay
standard-delay)]
(log/debug "propagate-status set-timeout: " logged-in-since app-active-since ts-diff timeout)
timeout))))))
(defn status-propagator-dummy-view
"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
#()}))
(defview connectivity-view [anim-translate-y]
(letsubs [status-properties [:connectivity/status-properties]
app-active-since [:app-active-since]
logged-in-since [:logged-in-since]
ui-status-properties [:connectivity/ui-status-properties]
status-hidden (reagent/atom true)
view-id [:view-id]
window-width (reagent/atom 0)]
(defview connectivity [header footer]
(letsubs [status-properties [:connectivity/status-properties]
app-active-since [:app-active-since]
logged-in-since [:logged-in-since]
ui-status-properties [:connectivity/ui-status-properties]
status-hidden (reagent/atom true)
window-width (reagent/atom 0)]
(let [loading-indicator? (:loading-indicator? ui-status-properties)]
[react/view {:style {:align-items :stretch
:z-index 1}
[react/view {:style {:flex 1}
:on-layout #(reset! window-width (-> % .-nativeEvent .-layout .-width))}
(when (and loading-indicator? @status-hidden)
[loading-indicator @window-width])
;; This view below exists only to hide the connectivity-status bar when "connected".
;; Ideally connectivity-status bar would be hidden under "toolbar/toolbar",
;; but that has to be transparent(enven though it sits above the bar)
;; to show through the "loading-indicator"
;; TODO consider making the height the same height as the "toolbar/toolbar"
[react/view {:position :absolute
:top neg-connectivity-bar-height
:width @window-width
:z-index 2
:height connectivity-bar-height
:background-color colors/white}]
[react/view {:style {:z-index 2 :background-color :white}}
header
[react/view
(when (and loading-indicator? @status-hidden)
[loading-indicator @window-width])]]
[connectivity-status
;on startup default connected
(merge (or ui-status-properties
{:connected? true :message :t/connected})
{:view-id view-id
:window-width @window-width})
anim-translate-y
{:window-width @window-width})
status-hidden]
[status-propagator-dummy-view {:status-properties status-properties
:app-active-since app-active-since
:logged-in-since logged-in-since
:ui-status-properties ui-status-properties}]])))
;; "push?" determines whether "content" gets pushed down when disconnected
;; like in :home view, or stays put like in :chat view
;; TODO determine-how-this-affects/fix desktop
(defn connectivity-animation-wrapper [style anim-value push? & content]
(vec (concat
(if platform/desktop?
[react/view {:style {:flex 1}}]
[react/animated-view
{:style (merge {:flex 1
:margin-bottom neg-connectivity-bar-height}
;; A translated view (connectivity-view in this case)
;; prevents touch interaction to component below
;; them. If we don't bring this view on the same level
;; or above as the translated view, the top
;; portion(same height as connectivity-view) of
;; "content" (which now occupies translated view's
;; natural[untranslated] position) becomes
;; unresponsive to touch
(when-not @to-hide?
{:z-index 1})
(if push?
{:transform [{:translateY anim-value}]}
{:transform [{:translateY neg-connectivity-bar-height}]})
style)}])
content)))
;;TODO this is something weird, rework
[status-propagator-dummy-view {:status-properties status-properties
:app-active-since app-active-since
:logged-in-since logged-in-since
:ui-status-properties ui-status-properties}]
footer])))

View File

@ -199,28 +199,6 @@
(when header {:ListHeaderComponent (reagent/as-element header)})
(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
"A wrapper for FlatList.
See https://facebook.github.io/react-native/docs/flatlist.html"
@ -232,7 +210,7 @@
[class
(merge (base-list-props props)
props
{:data (wrap-data data)})])))
{:data (to-array data)})])))
(defn flat-list-generic-render-fn
"A generic status-react specific `render-fn` for `list-item`.
@ -262,7 +240,7 @@
(if-let [f (:render-fn props)]
(assoc (dissoc props :render-fn) :renderItem (wrap-render-fn f))
props)
:data wrap-data))
:data to-array))
;;TODO DEPRECATED, use status-im.ui.components.list-item.views
(defn section-list
"A wrapper for SectionList.

View File

@ -1,52 +1,44 @@
(ns status-im.ui.components.search-input.view
(:require-macros [status-im.utils.views :as views])
(:require [reagent.core :as reagent]
[status-im.i18n :as i18n]
(:require [status-im.i18n :as i18n]
[status-im.ui.components.react :as react]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.search-input.styles :as styles]
[status-im.ui.components.icons.vector-icons :as icons]))
(views/defview search-input [{:keys [on-cancel
on-focus
on-change
search-active?
search-container-style
search-filter
auto-focus]}]
(views/letsubs
[input-ref (reagent/atom nil)]
[react/view {:style (or search-container-style styles/search-container)}
[react/view {:style styles/search-input-container}
[icons/icon :main-icons/search {:color colors/gray
:container-style {:margin-left 6
:margin-right 2}}]
[react/text-input {:placeholder (i18n/label :t/search)
:blur-on-submit true
:multiline false
:ref #(reset! input-ref %)
:style styles/search-input
:default-value search-filter
:auto-focus auto-focus
:auto-correct false
:auto-capitalize false
:on-focus #(do
(when on-focus
(on-focus search-filter))
(reset! search-active? true))
:on-change (fn [e]
(let [native-event (.-nativeEvent e)
text (.-text native-event)]
(when on-change
(on-change text))))}]]
(when @search-active?
[react/touchable-highlight
{:on-press (fn []
(.clear @input-ref)
(.blur @input-ref)
(when on-cancel
(on-cancel))
(reset! search-active? false))
:style {:margin-left 16}}
[react/text {:style {:color colors/blue}}
(i18n/label :t/cancel)]])]))
(defn search-input [_]
(let [input-ref (atom nil)]
(fn [{:keys [on-cancel on-focus on-change search-active?
search-container-style search-filter auto-focus]}]
[react/view {:style (or search-container-style styles/search-container)}
[react/view {:style styles/search-input-container}
[icons/icon :main-icons/search {:color colors/gray
:container-style {:margin-left 6
:margin-right 2}}]
[react/text-input {:placeholder (i18n/label :t/search)
:blur-on-submit true
:multiline false
:ref #(reset! input-ref %)
:style styles/search-input
:default-value search-filter
:auto-focus auto-focus
:auto-correct false
:auto-capitalize false
:on-focus #(do
(when on-focus
(on-focus search-filter))
(reset! search-active? true))
:on-change (fn [e]
(let [native-event (.-nativeEvent e)
text (.-text native-event)]
(when on-change
(on-change text))))}]]
(when @search-active?
[react/touchable-highlight
{:on-press (fn []
(.clear @input-ref)
(.blur @input-ref)
(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 [_]
(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?))]
[react/view (cond-> {:height 56 :align-items :center :flex-direction :row}
show-border?
@ -49,8 +49,12 @@
(for [value accessories]
^{:key value}
[button value false])])
(when content
[react/view {:position :absolute :left @title-padding :right @title-padding
:top 0 :bottom 0}
content])
(when title
[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}
(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.utils :as chat-utils]
[status-im.i18n :as i18n]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[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]
[taoensso.timbre :as log]
[status-im.ui.screens.chat.stickers.views :as stickers]
[status-im.ui.screens.chat.extensions.views :as extensions]))
@ -32,10 +28,7 @@
:default-value (or input-text "")
:editable (not cooldown-enabled?)
:blur-on-submit false
:on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? true
:input-bottom-sheet nil
:messages-focused? false}])
:on-blur #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? false}])
:on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-bottom-sheet nil}])
:on-submit-editing #(when single-line-input?
(re-frame/dispatch [:chat.ui/send-current-message]))
:on-layout #(set-container-width-fn (.-width (.-layout (.-nativeEvent %))))
@ -63,10 +56,7 @@
:default-value @state-text
:editable (not cooldown-enabled?)
:blur-on-submit false
:on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? true
:input-bottom-sheet nil
:messages-focused? false}])
:on-blur #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-focused? false}])
:on-focus #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:input-bottom-sheet nil}])
:submit-shortcut {:key "Enter"}
:on-submit-editing #(do
(.clear @inp-ref)
@ -162,12 +152,7 @@
input-text-empty? (if platform/desktop?
(string/blank? state-text)
(string/blank? input-text))]
[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}])))}
[react/view {:style (style/root margin)}
[reply-message-view]
[react/view {:style style/input-container}
[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])
(set-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
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(: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.components.react :as react]
[status-im.ui.components.icons.vector-icons :as vector-icons]))
@ -21,4 +20,4 @@
[vector-icons/icon :main-icons/arrow-up
{:container-style style/send-message-container
: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]))
(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
{:on-press (fn [_]
(re-frame/dispatch
[:chat.ui/set-chat-ui-props {:messages-focused? true
:input-bottom-sheet nil}])
[:chat.ui/set-chat-ui-props {:input-bottom-sheet nil}])
(react/dismiss-keyboard!))}
[react/view style/datemark-mobile
[react/text {:style style/datemark-text}

View File

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

View File

@ -1,15 +1,11 @@
(ns status-im.ui.screens.chat.photos
(:require [clojure.string :as string]
[status-im.multiaccounts.core :as multiaccounts]
[status-im.ui.components.react :as react]
(:require [status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.photos :as style]
[status-im.ui.screens.profile.db :as profile.db]
[status-im.utils.identicon :as identicon]
[status-im.utils.image :as utils.image])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn photo [photo-path {:keys [size
accessibility-label]}]
(defn photo [photo-path {:keys [size accessibility-label]}]
(let [identicon? (when photo-path (profile.db/base64-png? photo-path))]
[react/view {:style (style/photo-container size)}
[react/image {:source (utils.image/source photo-path)
@ -24,3 +20,12 @@
(photo (or photo-path identicon)
{:accessibility-label :member-photo
: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
(:require [status-im.ui.components.colors :as colors]))
(def chat-view
{:flex 1})
(def toolbar-container
{:flex 1
:align-items :center
: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
{:flex 1
:justify-content :center})
@ -44,12 +15,6 @@
:font-size 15
:line-height 22})
(def group-icon
{:margin-top 4
:margin-bottom 2.7
:width 14
:height 9})
(def toolbar-subtitle
{:typography :caption
:line-height 16
@ -60,57 +25,6 @@
:margin-top 4
: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
{:flex 1})
@ -134,31 +48,6 @@
:right 16
: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
{:flex-direction :row
:align-items :center
@ -171,14 +60,6 @@
{:margin-left 4
: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
{:flex 1
:justify-content :center
@ -187,18 +68,17 @@
:margin-right 6})
(defn intro-header-container
[height status no-messages]
(let [adjusted-height (if (< height 280) 324 height)]
(if (or no-messages (= status (or :loading :empty)))
{:flex 1
:flex-direction :column
:justify-content :center
:align-items :center
:height adjusted-height}
{:flex 1
:flex-direction :column
:justify-content :center
:align-items :center})))
[status no-messages]
(if (or no-messages (= status (or :loading :empty)))
{:flex 1
:flex-direction :column
:justify-content :center
:align-items :center
:height 324}
{:flex 1
:flex-direction :column
:justify-content :center
:align-items :center}))
(defn intro-header-icon [diameter color]
{:width diameter
@ -238,20 +118,12 @@
:margin-right 4
:text-align :center})
(def empty-chat-text-name
{:margin-bottom 5})
(def intro-header-description
{:color colors/gray
:line-height 22
:text-align :center
:margin-horizontal 32})
(def group-chat-icon
{:color colors/white
:font-size 40
:font-weight "700"})
(def group-chat-join-footer
{:flex 1
:justify-content :center})
@ -261,21 +133,10 @@
:align-items :center
:justify-content :center})
(def group-chat-join-name
{:typography :header})
(def join-button
{:margin-bottom 15})
(def decline-chat
{:color colors/blue
:margin-bottom 40})
(def select-chat
{:color colors/gray})
(def messages-list-vertical-padding 46)
(def are-you-friends-bubble
{:border-radius 8
:border-width 1
@ -303,4 +164,4 @@
(def tribute-received-note
{:font-size 13
:line-height 18
:text-align :center})
:text-align :center})

View File

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

View File

@ -1,10 +1,8 @@
(ns status-im.ui.screens.chat.toolbar-content
(:require [cljs-time.core :as t]
[status-im.i18n :as i18n]
(:require [status-im.i18n :as i18n]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.react :as react]
[status-im.ui.screens.chat.styles.main :as st]
[status-im.utils.datetime :as time])
[status-im.ui.screens.chat.styles.main :as st])
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
(defn- in-progress-text [{:keys [highestBlock currentBlock startBlock]}]
@ -47,9 +45,9 @@
(i18n/label :chat-is-not-a-contact))]])
(defview toolbar-content-view []
(letsubs [{:keys [group-chat color online contacts chat-name contact
public? chat-id] :as chat} [:chats/current-chat]
sync-state [:sync-state]]
(letsubs [{:keys [group-chat color online contacts chat-name contact public?]}
[:chats/current-chat]
sync-state [:sync-state]]
(let [has-subtitle? (or group-chat (not= :done sync-state))]
[react/view {:style st/toolbar-container}
[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
(:require [re-frame.core :as re-frame]
[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]
(:require [status-im.ethereum.stateofus :as stateofus]
[status-im.i18n :as i18n]
[status-im.utils.core :as core-utils]
[status-im.ui.components.react :as react]
[status-im.ui.components.colors :as colors]
[status-im.utils.http :as http]))
[status-im.ui.components.colors :as colors]))
(defn format-author [alias style name]
(let [additional-styles (style false)]
@ -37,60 +30,4 @@
(or (and (= from current-public-key)
[react/text {:style (style true)}
(str reply-symbol (i18n/label :t/You))])
(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))
(format-author (subs reply-name 0 80) style false))))

View File

@ -1,37 +1,45 @@
(ns status-im.ui.screens.chat.views
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.contact.db :as contact.db]
[status-im.i18n :as i18n]
[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.colors :as colors]
[status-im.ui.components.connectivity.view :as connectivity]
[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.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.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.stickers.views :as stickers]
[status-im.ui.screens.chat.styles.main :as style]
[status-im.ui.screens.chat.toolbar-content :as toolbar-content]
[status-im.ui.screens.profile.tribute-to-talk.views
:as
tribute-to-talk.views]
[status-im.ui.screens.chat.state :as state]
[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]]))
(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
[public-key]
[react/touchable-highlight
@ -43,250 +51,30 @@
{:color colors/blue}]
[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
[contact]
[react/text {:style (assoc style/intro-header-description
:margin-bottom 32)}
(str (i18n/label :t/empty-chat-description-one-to-one) (multiaccounts/displayed-name contact))])
(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]]])
;; 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]))
(str
(i18n/label :t/empty-chat-description-one-to-one)
(multiaccounts/displayed-name contact))])
(defn chat-intro-header-container
[{:keys [group-chat name pending-invite-inviter-name
inviter-name color chat-id chat-name public?
contact universal-link intro-status height input-height] :as chat}
[{:keys [group-chat name pending-invite-inviter-name color chat-id chat-name
public? contact intro-status] :as chat}
no-messages]
(let [icon-text (if public? chat-id name)
intro-name (if public? chat-name (multiaccounts/displayed-name contact))]
;; TODO This when check ought to be unnecessary but for now it prevents
;; 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
(when (or pending-invite-inviter-name
(not= (get-in contact [:tribute-to-talk :snt-amount]) 0))
[react/touchable-without-feedback
{:style {:flex 1
:align-items :flex-start}
:on-press (fn [_]
(re-frame/dispatch
[:chat.ui/set-chat-ui-props {:messages-focused? true
:input-bottom-sheet nil}])
[:chat.ui/set-chat-ui-props {:input-bottom-sheet nil}])
(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
[react/view {:style {:margin-top 42
:margin-bottom 24}}
@ -296,11 +84,12 @@
:default-chat-icon-text style/intro-header-icon-text
:size 120}]]
;; 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
(if group-chat
[group-chat-description-container chat]
[one-to-one-chat-description-container chat])]])))
[chat.group/group-chat-description-container chat]
[intro-header contact])]])))
(defonce messages-list-ref (atom nil))
@ -308,11 +97,11 @@
(when @messages-list-ref
(reset! state/viewable-item
(when-let [last-visible-element (aget (.-viewableItems e) (dec (.-length (.-viewableItems e))))]
(let [index (.-index last-visible-element)
;; Get first not visible element, if it's a datemark/gap
;; we might unnecessarely add messages on receiving as
;; they do not have a clock value, but most of the times
;; it will be a message
(let [index (.-index last-visible-element)
;; Get first not visible element, if it's a datemark/gap
;; we might unnecessarely add messages on receiving as
;; they do not have a clock value, but most of the times
;; it will be a message
first-not-visible (aget (.-data (.-props @messages-list-ref)) (inc index))]
(when (and first-not-visible
(= :message (:type first-not-visible)))
@ -320,149 +109,54 @@
(debounce/debounce-and-dispatch [:chat.ui/message-visibility-changed e] 5000))
(defview messages-view
[{:keys [group-chat chat-id pending-invite-inviter-name contact] :as chat}
modal?]
[{:keys [group-chat chat-id pending-invite-inviter-name] :as chat}]
(letsubs [messages [:chats/current-chat-messages-stream]
current-public-key [:multiaccount/public-key]]
{:component-did-update
(fn [args]
(re-frame/dispatch [:chat.ui/set-chat-ui-props
{:messages-focused? true
:input-focused? false}]))}
(let [no-messages (empty? messages)
flat-list-conf
{:data messages
:ref #(reset! messages-list-ref %)
:footer [chat-intro-header-container chat no-messages]
:key-fn #(or (:message-id %) (:value %))
:render-fn (fn [message idx]
[message-row
{:group-chat group-chat
:modal? modal?
:current-public-key current-public-key
:row message
:idx idx
:list-ref messages-list-ref}])
:inverted true
:onViewableItemsChanged on-viewable-items-changed
:onEndReached #(re-frame/dispatch [:chat.ui/load-more-messages])
:onScrollToIndexFailed #()
:keyboardShouldPersistTaps :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]))))
[list/flat-list
{:key-fn #(or (:message-id %) (:value %))
:ref #(reset! messages-list-ref %)
:header (when pending-invite-inviter-name
[chat.group/group-chat-footer chat-id])
:footer [chat-intro-header-container chat (empty? messages)]
:data messages
:inverted true
:render-fn (fn [{:keys [outgoing] :as message} idx]
(let [type (:type message)]
(if (= type :datemark)
[message-datemark/chat-datemark (:value message)]
(if (= type :gap)
[gap/gap message idx messages-list-ref]
; message content
[message/chat-message
(assoc message
:incoming-group (and group-chat (not outgoing))
:group-chat group-chat
:current-public-key current-public-key)]))))
:on-viewable-items-changed on-viewable-items-changed
:on-end-reached #(re-frame/dispatch [:chat.ui/load-more-messages])
:on-scroll-to-index-failed #() ;;don't remove this
:keyboard-should-persist-taps :handled}]))
(def load-step 5)
(defn load-more [all-messages-count messages-to-load]
(let [next-count (min all-messages-count (+ @messages-to-load load-step))]
(reset! messages-to-load next-count)))
(defview messages-view-desktop [{:keys [chat-id group-chat pending-invite-inviter-name]}
modal?]
(letsubs [messages [:chats/current-chat-messages-stream]
current-public-key [:multiaccount/public-key]
messages-to-load (reagent/atom load-step)
chat-id* (reagent/atom nil)]
{:component-did-update #(if (:messages-initialized? (second (.-argv (.-props %1))))
(load-more (count messages) messages-to-load)
(re-frame/dispatch [:chat.ui/load-more-messages]))
:component-did-mount #(if (:messages-initialized? (second (.-argv (.-props %1))))
(load-more (count messages) messages-to-load)
(re-frame/dispatch [:chat.ui/load-more-messages]))}
(let [messages-list-ref (atom nil)
scroll-timer (atom nil)
scroll-height (atom nil)
_ (when (or (not @chat-id*) (not= @chat-id* chat-id))
(do
(reset! messages-to-load load-step)
(reset! chat-id* chat-id)))]
[react/view {:style style/chat-view}
[react/scroll-view {:scrollEventThrottle 16
:headerHeight style/messages-list-vertical-padding
:footerWidth style/messages-list-vertical-padding
:enableArrayScrollingOptimization true
:inverted true
:ref #(reset! messages-list-ref %)
:on-scroll (fn [e]
(let [ne (.-nativeEvent e)
y (.-y (.-contentOffset ne))]
(when (<= y 0)
(when @scroll-timer (js/clearTimeout @scroll-timer))
(reset! scroll-timer (js/setTimeout #(re-frame/dispatch [:chat.ui/load-more-messages]) 300)))
(reset! scroll-height (+ y (.-height (.-layoutMeasurement ne))))))}
[react/view
(doall
(for [{:keys [from content] :as message-obj} (take @messages-to-load messages)]
^{:key message-obj}
[message-row
{:group-chat group-chat
:modal? modal?
:current-public-key current-public-key
:row message-obj
:idx #(or (:message-id message-obj) (:value message-obj))
:list-ref messages-list-ref}]))]]
(if pending-invite-inviter-name
[group-chat-footer chat-id])])))
(defview chat-root [modal?]
(letsubs [{:keys [public? chat-id chat-name show-input? group-chat contact] :as current-chat}
[:chats/current-chat]
current-chat-id [:chats/current-chat-id]
input-bottom-sheet [:chats/current-chat-ui-prop :input-bottom-sheet]
two-pane-ui-enabled? [:two-pane-ui-enabled?]
anim-translate-y (animation/create-value
(if two-pane-ui-enabled? 0 connectivity/neg-connectivity-bar-height))]
[react/view {:style style/chat-view
:on-layout (fn [e]
(re-frame/dispatch [:set :layout-height (-> e .-nativeEvent .-layout .-height)]))}
[toolbar/toolbar
{:chat? true
:style {:z-index 2}}
(if modal?
[toolbar/nav-button
(toolbar.actions/close toolbar.actions/default-handler)]
toolbar/nav-back-home)
[toolbar-content/toolbar-content-view]
(when-not modal?
[toolbar/actions
[{:icon :main-icons/more
:icon-opts {:color :black
:accessibility-label :chat-menu-button}
:handler #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[sheets/actions current-chat])
:height 256}])}]])]
(when-not two-pane-ui-enabled?
[connectivity/connectivity-view anim-translate-y])
[connectivity/connectivity-animation-wrapper
{}
anim-translate-y
false
[messages-view-animation
(if (and (= chat-id current-chat-id) (not group-chat) (not (contact.db/added? contact)))
[add-contact-bar chat-id])
;;TODO(kozieiev) : When FlatList in react-native-desktop become viable it should be used instead of optimized ScrollView for chat
(if platform/desktop?
[messages-view-desktop current-chat modal?]
[messages-view current-chat modal?])]]
(when show-input?
[input/container])
(case input-bottom-sheet
:stickers
[stickers/stickers-view]
:extensions
[extensions/extensions-view]
nil)]))
(defview bottom-sheet []
(letsubs [input-bottom-sheet [:chats/current-chat-ui-prop :input-bottom-sheet]]
(case input-bottom-sheet
:stickers
[stickers/stickers-view]
:extensions
[extensions/extensions-view]
nil)))
(defview chat []
[chat-root false])
(defview chat-modal []
[chat-root true])
(defview select-chat []
[react/view {:align-items :center :justify-content :center :flex 1}
[react/text style/select-chat
(i18n/label :t/select-chat)]])
(letsubs [{:keys [chat-id show-input? group-chat contact] :as current-chat}
[:chats/current-chat]]
[react/view {:style {:flex 1}}
[connectivity/connectivity
[topbar current-chat]
[react/view {:style {:flex 1}}
;;TODO contact.db/added? looks weird here, move to events
(when (and (not group-chat) (not (contact.db/added? contact)))
[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
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [re-frame.core :as re-frame]
[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.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.utils.config :as config]))
@ -45,4 +41,4 @@
(list-selection/open-share {:message (i18n/label :t/get-status-at)}))}]])
(def add-new
{:content add-new-view})
{:content add-new-view})

View File

@ -1,22 +1,12 @@
(ns status-im.ui.screens.home.styles
(:require [status-im.ui.components.colors :as colors]
[status-im.utils.styles :as styles]
[status-im.ui.components.search-input.styles :as search-input.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})
[status-im.utils.platform :as platform]
[status-im.ui.components.tabbar.styles :as tabs.styles]))
(def last-message-container
{:flex-shrink 1})
(styles/def last-message-text
(def last-message-text
{:flex 1
:align-self :stretch
:line-height 22
@ -31,31 +21,6 @@
:width 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
{:color colors/text-gray
:font-size 10
@ -64,34 +29,13 @@
:align-items :center
: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
{:align-items :center
:border-color colors/gray-lighter
:border-width 1
:border-radius 16
:margin 16})
:margin 16
:margin-bottom 68})
(def no-chats-text
{:margin-top 50
@ -103,9 +47,6 @@
{:flex 1
:justify-content :flex-end})
(def welcome-image-container
{:align-items :center})
(def welcome-text
{:typography :header
:text-align :center})
@ -124,12 +65,12 @@
:margin-horizontal 40
:color colors/gray})
(defn action-button-container [home-width]
(def action-button-container
{:position :absolute
:z-index 2
:align-items :center
:align-self :center
:bottom 16
:left (- (/ home-width 2) 20)
:width 40
:height 40})
@ -169,16 +110,6 @@
{:margin-top 10
: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
{:width 24
:height 24
@ -186,3 +117,10 @@
:background-color colors/gray
:align-items :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.list.views :as list]
[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.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.components.common.common :as components.common]
[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.screens.add-new.new-public-chat.view :as new-public-chat]
[status-im.ui.components.button :as button]
[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]))
(defonce search-active? (reagent/atom false))
(defn welcome-image-wrapper []
(let [dimensions (reagent/atom {})]
(fn []
[react/view {:on-layout (fn [e]
(reset! dimensions (js->clj (-> e .-nativeEvent .-layout) :keywordize-keys true)))
:style {:align-items :center
:justify-content :center
:flex 1}}
[react/view {:on-layout (fn [e]
(reset! dimensions (bean/->clj (-> e .-nativeEvent .-layout))))
:style {:align-items :center
:justify-content :center
:flex 1}}
(let [padding 0
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
:style {:width image-size :height image-size}}])])))
:style {:width image-size :height image-size}}])])))
(defn welcome []
[react/view {:style styles/welcome-view}
@ -47,9 +41,9 @@
[react/i18n-text {:style styles/welcome-text-description
:key :welcome-to-status-description}]]
[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
:label (i18n/label :t/lets-go)}]]])
:label (i18n/label :t/lets-go)}]]])
(defn home-tooltip-view []
[react/view styles/chat-tooltip
@ -86,121 +80,56 @@
[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}]])
(defn chat-list-footer [hide-home-tooltip?]
(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}]]))
(defonce search-active? (reagent/atom false))
(defn search-input-wrapper [search-filter]
[search-input/search-input
{:search-active? search-active?
:search-container-style styles/search-container
:search-filter search-filter
:on-cancel #(re-frame/dispatch [:search/home-filter-changed nil])
:on-focus (fn [search-filter]
(when-not search-filter
(re-frame/dispatch [:search/home-filter-changed ""])))
:on-change (fn [text]
(re-frame/dispatch [:search/home-filter-changed text]))}])
{:search-active? search-active?
:search-filter search-filter
:on-cancel #(re-frame/dispatch [:search/home-filter-changed nil])
:on-focus (fn [search-filter]
(when-not search-filter
(re-frame/dispatch [:search/home-filter-changed ""])))
:on-change (fn [text]
(re-frame/dispatch [:search/home-filter-changed text]))}])
(defn section-footer [{:keys [title data]}]
(when (and @search-active? (empty? data))
[list/big-list-item
{:text (i18n/label :t/no-result)
:text-color colors/gray
:hide-chevron? true
:action-fn #()
:icon (case title
"messages" :main-icons/one-on-one-chat
"browser" :main-icons/browser
"chats" :main-icons/message)
:icon-color colors/gray}]))
(views/defview chats-list []
(views/letsubs [loading? [:chats/loading?]
{:keys [chats all-home-items search-filter]} [:home-items]
{:keys [hide-home-tooltip?]} [:multiaccount]]
(if loading?
[react/activity-indicator {:flex 1 :animating true}]
(if (and (empty? all-home-items) hide-home-tooltip? (not @search-active?))
[welcome-blank-page]
(let [data (if @search-active? chats all-home-items)]
[list/flat-list
{:key-fn first
: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/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/defview plus-button []
(views/letsubs [logging-in? [:multiaccounts/login]]
[react/view (styles/action-button-container home-width)
[react/touchable-highlight {: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-container
[react/touchable-highlight
{: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
(if logging-in?
[react/activity-indicator {:color :white
:animating true}]
[icons/icon :main-icons/add {:color :white}])]]]))
(views/defview home [loading?]
(views/letsubs
[anim-translate-y (animation/create-value connectivity/neg-connectivity-bar-height)
{:keys [all-home-items]} [:home-items]
{:keys [hide-home-tooltip?]} [:multiaccount]
window-width [:dimensions/window-width]
two-pane-ui-enabled? [:two-pane-ui-enabled?]]
(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?]))
(defn home []
[react/keyboard-avoiding-view {:style styles/home-container}
[connectivity/connectivity
[topbar/topbar {:title :t/chat :navigation :none
:show-border? true}]
[chats-list]]
[plus-button]])

View File

@ -4,7 +4,6 @@
[status-im.constants :as constants]
[status-im.i18n :as i18n]
[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.components.list-item.views :as list-item]
[status-im.ui.components.badge :as badge]
@ -15,7 +14,7 @@
[status-im.utils.datetime :as time])
(: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
(cond
@ -26,6 +25,7 @@
(= constants/content-type-sticker content-type)
[react/image {:style {:margin 1 :width 20 :height 20}
;;TODO (perf) move to event
:source {:uri (contenthash/url (-> content :sticker :hash))}}]
(string/blank? (:text content))
@ -37,39 +37,40 @@
:number-of-lines 1
:ellipsize-mode :tail
: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]
(when timestamp
[react/text {:style styles/datetime-text
:accessibility-label :last-message-time-text}
;;TODO (perf) move to event
(string/upper-case (time/to-short-str timestamp))]))
(defview unviewed-indicator [chat-id]
(letsubs [{:keys [unviewed-messages-count public?]} [:chats/chat chat-id]]
(when (pos? unviewed-messages-count)
(if public?
[react/view {:style styles/public-unread
:accessibility-label :unviewed-messages-public}]
[badge/message-counter unviewed-messages-count]))))
(defn unviewed-indicator [{:keys [unviewed-messages-count public?]}]
(when (pos? unviewed-messages-count)
(if public?
[react/view {:style styles/public-unread
:accessibility-label :unviewed-messages-public}]
[badge/message-counter unviewed-messages-count])))
(defn home-list-item [[_ home-item]]
(let [{:keys
[chat-id chat-name
color online group-chat
public? contact
timestamp
last-message]} home-item
(let [{:keys [chat-id chat-name color online group-chat
public? contact timestamp last-message]}
home-item
private-group? (and group-chat (not public?))
public-group? (and group-chat public?)
;;TODO (perf) move to event
truncated-chat-name (utils/truncate-str chat-name 30)]
[list-item/list-item
{:icon [chat-icon.screen/chat-icon-view-chat-list
contact group-chat truncated-chat-name color online false]
:title-prefix (cond
private-group? :main-icons/tiny-group
public-group? :main-icons/tiny-public
:else nil)
public-group? :main-icons/tiny-public
:else nil)
:title truncated-chat-name
:title-accessibility-label :chat-name-text
:title-row-accessory [message-timestamp (if (pos? (:whisper-timestamp last-message))
@ -81,7 +82,7 @@
[message-content-text {:content (:content last-message)
:content-type (:content-type last-message)}]
tribute-label))
:subtitle-row-accessory [unviewed-indicator chat-id]
:subtitle-row-accessory [unviewed-indicator home-item]
:on-press #(do
(re-frame/dispatch [:dismiss-keyboard])
(re-frame/dispatch [:chat.ui/navigate-to-chat chat-id])
@ -91,4 +92,4 @@
:on-long-press #(re-frame/dispatch [:bottom-sheet/show-sheet
{:content (fn []
[sheets/actions home-item])
:height 256}])}]))
:height 256}])}]))

View File

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

View File

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

View File

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

View File

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