Show timestamp inside bubble for text messages

Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
Andrea Maria Piana 2018-04-23 17:46:27 +01:00
parent 3b4d7a6a62
commit 4aaecf792b
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
6 changed files with 83 additions and 65 deletions

View File

@ -1,10 +1,10 @@
(ns status-im.chat.styles.message.message (ns status-im.chat.styles.message.message
(:require-macros [status-im.utils.styles :refer [defstyle defnstyle]]) (:require-macros [status-im.utils.styles :refer [defstyle defnstyle]])
(:require [status-im.ui.components.styles :as styles] (:require [status-im.ui.components.styles :as styles]
[status-im.chat.styles.photos :as photos]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.constants :as constants])) [status-im.constants :as constants]))
(def photo-size 36)
(defstyle style-message-text (defstyle style-message-text
{:font-size 15 {:font-size 15
@ -48,13 +48,18 @@
:align-self align :align-self align
:align-items align}))) :align-items align})))
(def message-timestamp (defn message-timestamp [justify-timestamp?]
{:margin-left 5 (merge {:color colors/gray
:margin-right 5 :font-size 10
:margin-bottom -2 :align-self :flex-end
:color colors/gray :opacity 0.5}
:opacity 0.5 (when justify-timestamp? {:position :absolute
:align-self :flex-end}) :bottom 10
:right 12})))
(def message-timestamp-placeholder
(assoc (message-timestamp false)
:color styles/color-white))
(def selected-message (def selected-message
{:margin-top 18 {:margin-top 18
@ -83,14 +88,9 @@
:padding-right 22}) :padding-right 22})
(def message-author (def message-author
{:width photo-size {:width photos/photo-size
:align-self :flex-end}) :align-self :flex-end})
(def photo
{:border-radius (/ photo-size 2)
:width photo-size
:height photo-size})
(def delivery-view (def delivery-view
{:flex-direction :row {:flex-direction :row
:margin-top 2 :margin-top 2
@ -136,7 +136,9 @@
(defn message-view (defn message-view
[{:keys [content-type outgoing group-chat selected]}] [{:keys [content-type outgoing group-chat selected]}]
(merge {:padding 12 (merge {:padding-top 6
:padding-horizontal 12
:padding-bottom 8
:border-radius 8} :border-radius 8}
(when-not (= content-type constants/content-type-emoji) (when-not (= content-type constants/content-type-emoji)
{:background-color styles/color-white}) {:background-color styles/color-white})

View File

@ -0,0 +1,8 @@
(ns status-im.chat.styles.photos)
(def photo-size 36)
(def photo
{:border-radius (/ photo-size 2)
:width photo-size
:height photo-size})

View File

@ -12,7 +12,8 @@
:background-color component.styles/chat-background}) :background-color component.styles/chat-background})
(def toolbar-container (def toolbar-container
{}) {:flex 1
:flex-direction :row})
(def messages-container (def messages-container
{:flex 1 {:flex 1

View File

@ -13,6 +13,7 @@
[status-im.chat.styles.message.message :as style] [status-im.chat.styles.message.message :as style]
[status-im.chat.styles.message.command-pill :as pill-style] [status-im.chat.styles.message.command-pill :as pill-style]
[status-im.chat.views.message.request-message :as request-message] [status-im.chat.views.message.request-message :as request-message]
[status-im.chat.views.photos :as photos]
[status-im.constants :as constants] [status-im.constants :as constants]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen] [status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.utils.core :as utils] [status-im.utils.core :as utils]
@ -22,8 +23,7 @@
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[clojure.string :as string] [clojure.string :as string]
[status-im.chat.events.console :as console] [status-im.chat.events.console :as console]))
[status-im.react-native.resources :as resources]))
(def window-width (:width (react/get-dimensions "window"))) (def window-width (:width (react/get-dimensions "window")))
@ -81,10 +81,14 @@
:font :default} :font :default}
(or preview (str params))])]))) (or preview (str params))])])))
(defview message-timestamp [t justify-timestamp?]
[react/text {:style (style/message-timestamp justify-timestamp?)} t])
(defn message-view (defn message-view
[{:keys [group-chat] :as message} content] [{:keys [timestamp-str] :as message} content {:keys [justify-timestamp?]}]
[react/view (style/message-view message) [react/view (style/message-view message)
content]) content
[message-timestamp timestamp-str justify-timestamp?]])
(def replacements (def replacements
{"\\*[^*]+\\*" {:font-weight :bold} {"\\*[^*]+\\*" {:font-weight :bold}
@ -162,18 +166,22 @@
(autolink string event-on-press))) (autolink string event-on-press)))
text-seq)))) text-seq))))
; We can't use CSS as nested Text element don't accept margins nor padding
; so we pad the invisible placeholder with some spaces to avoid having too
; close to the text.
(defn timestamp-with-padding [t]
(str " " t))
(def cached-parse-text (memoize parse-text)) (def cached-parse-text (memoize parse-text))
(defn text-message (defn text-message
[{:keys [content] :as message}] [{:keys [content timestamp-str] :as message}]
[message-view message [message-view message
(let [parsed-text (cached-parse-text content :browse-link-from-message)] (let [parsed-text (cached-parse-text content :browse-link-from-message)]
[react/text {:style (style/text-message message)} parsed-text])]) [react/text {:style (style/text-message message)}
parsed-text
(defn placeholder-message [react/text {:style style/message-timestamp-placeholder} (timestamp-with-padding timestamp-str)]])
[{:keys [content] :as message}] {:justify-timestamp? true}])
[message-view message
[react/text {:style (style/text-message message)} content]])
(defn emoji-message (defn emoji-message
[{:keys [content] :as message}] [{:keys [content] :as message}]
@ -204,10 +212,6 @@
[wrapper message [wrapper message
[message-view message [message-content-command message]]]) [message-view message [message-content-command message]]])
(defmethod message-content constants/content-type-placeholder
[wrapper message]
[wrapper message [placeholder-message message]])
(defmethod message-content constants/content-type-emoji (defmethod message-content constants/content-type-emoji
[wrapper message] [wrapper message]
[wrapper message [emoji-message message]]) [wrapper message [emoji-message message]])
@ -293,29 +297,6 @@
(when outgoing (when outgoing
[text-status status])))))) [text-status status]))))))
(defn- photo [from photo-path]
[react/view
[react/image {:source (if (and (not (string/blank? photo-path))
(string/starts-with? photo-path "contacts://"))
(->> (string/replace photo-path #"contacts://" "")
(keyword)
(get resources/contacts))
{:uri photo-path})
:style style/photo}]])
(defview member-photo [from]
(letsubs [photo-path [:get-photo-path from]]
(photo from (if (string/blank? photo-path)
(identicon/identicon from)
photo-path))))
(defview my-photo [from]
(letsubs [{:keys [photo-path]} [:get-current-account]]
(photo from photo-path)))
(defview message-timestamp [t]
[react/text {:style style/message-timestamp} t])
(defview message-author-name [from message-username] (defview message-author-name [from message-username]
(letsubs [username [:get-contact-name-by-identity from]] (letsubs [username [:get-contact-name-by-identity from]]
[react/text {:style style/message-author-name} (or username [react/text {:style style/message-author-name} (or username
@ -323,21 +304,21 @@
(gfycat/generate-gfy from))])) ; TODO: We defensively generate the name for now, to be revisited when new protocol is defined (gfycat/generate-gfy from))])) ; TODO: We defensively generate the name for now, to be revisited when new protocol is defined
(defn message-body (defn message-body
[{:keys [timestamp-str last-in-group? first-in-group? from outgoing username] :as message} content] [{:keys [last-in-group? first-in-group? group-chat from outgoing username] :as message} content]
[react/view (style/group-message-wrapper message) [react/view (style/group-message-wrapper message)
[react/view (style/message-body message) [react/view (style/message-body message)
(when (not outgoing) (when (and (not outgoing)
group-chat)
[react/view style/message-author [react/view style/message-author
(when last-in-group? (when last-in-group?
[react/touchable-highlight {:on-press #(re-frame/dispatch [:show-profile from])} [react/touchable-highlight {:on-press #(re-frame/dispatch [:show-profile from])}
[react/view [react/view
[member-photo from]]])]) [photos/member-photo from]]])])
[react/view (style/group-message-view outgoing) [react/view (style/group-message-view outgoing)
(when first-in-group? (when first-in-group?
[message-author-name from username]) [message-author-name from username])
[react/view {:style (style/timestamp-content-wrapper message)} [react/view {:style (style/timestamp-content-wrapper message)}
content content]]]
[message-timestamp timestamp-str]]]]
[react/view style/delivery-status [react/view style/delivery-status
[message-delivery-status message]]]) [message-delivery-status message]]])

View File

@ -0,0 +1,23 @@
(ns status-im.chat.views.photos
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.ui.components.react :as react]
[status-im.chat.styles.photos :as style]
[status-im.utils.identicon :as identicon]
[clojure.string :as string]
[status-im.react-native.resources :as resources]))
(defn- photo [from photo-path]
[react/view
[react/image {:source (if (and (not (string/blank? photo-path))
(string/starts-with? photo-path "contacts://"))
(->> (string/replace photo-path #"contacts://" "")
(keyword)
(get resources/contacts))
{:uri photo-path})
:style style/photo}]])
(defview member-photo [from]
(letsubs [photo-path [:get-photo-path from]]
(photo from (if (string/blank? photo-path)
(identicon/identicon from)
photo-path))))

View File

@ -5,6 +5,7 @@
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.chat.views.photos :as photos]
[status-im.chat.styles.screen :as st] [status-im.chat.styles.screen :as st]
[status-im.utils.datetime :as time] [status-im.utils.datetime :as time]
[status-im.utils.platform :refer [platform-specific]] [status-im.utils.platform :refer [platform-specific]]
@ -60,17 +61,19 @@
(i18n/label-pluralize cnt :t/members-active)))]]))) (i18n/label-pluralize cnt :t/members-active)))]])))
(defview toolbar-content-view [] (defview toolbar-content-view []
(letsubs [{:keys [group-chat name chat-id (letsubs [{:keys [group-chat name contacts
contacts public? public-key]} [:get-current-chat] public? chat-id]} [:get-current-chat]
show-actions? [:get-current-chat-ui-prop :show-actions?] show-actions? [:get-current-chat-ui-prop :show-actions?]
accounts [:get-accounts] accounts [:get-accounts]
contact [:get-current-chat-contact] contact [:get-current-chat-contact]
sync-state [:sync-state]] sync-state [:sync-state]]
[react/view common.styles/flex [react/view {:style st/toolbar-container}
[react/view (when-not group-chat [photos/member-photo chat-id])]
[react/view (st/chat-name-view (or (empty? accounts) [react/view (st/chat-name-view (or (empty? accounts)
show-actions?)) show-actions?))
(let [chat-name (if (string/blank? name) (let [chat-name (if (string/blank? name)
(generate-gfy public-key) (generate-gfy chat-id)
(or (i18n/get-contact-translated chat-id :name name) (or (i18n/get-contact-translated chat-id :name name)
(i18n/label :t/chat-name)))] (i18n/label :t/chat-name)))]
[react/text {:style st/chat-name-text [react/text {:style st/chat-name-text