Add resend and delete message actions

* Set message status based on signal from Go
* Set status as "not-sent" on app start if no signal received

Signed-off-by: Dmitry Novotochinov <trybeee@gmail.com>
This commit is contained in:
Dmitry Novotochinov 2018-04-23 12:58:56 +03:00
parent e1f6292833
commit a8a03067a1
No known key found for this signature in database
GPG Key ID: 267674DCC86628D9
18 changed files with 303 additions and 65 deletions

2
package-lock.json generated
View File

@ -8921,7 +8921,7 @@
}
},
"web3": {
"version": "github:status-im/web3.js#aca66029d7ffac8ed2803b2fc7f0fec01e335ca3",
"version": "github:status-im/web3.js#aed8fa4baf4f8f9d5702bd82fac21c21f2ef6351",
"requires": {
"bignumber.js": "github:status-im/bignumber.js#cc066a0a3d6bfe0c436c9957f4ea8344bf963c89",
"crypto-js": "3.1.8",

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 17 17">
<path fill="#F93D3D" fill-rule="evenodd" d="M8.5 17a8.5 8.5 0 1 1 0-17 8.5 8.5 0 0 1 0 17zm.668-7.36V5H8v4.64h1.168zm0 2.36v-1.125H8V12h1.168z"/>
</svg>

After

Width:  |  Height:  |  Size: 241 B

View File

@ -5,6 +5,7 @@
[status-im.constants :as constants]
[status-im.i18n :as i18n]
[status-im.chat.models :as models]
[status-im.chat.models.message :as models.message]
[status-im.chat.console :as console]
[status-im.chat.constants :as chat.constants]
[status-im.commands.events.loading :as events.loading]
@ -55,6 +56,13 @@
(models/set-chat-ui-props db {:show-bottom-info? true
:bottom-info details})))
(handlers/register-handler-db
:show-message-options
[re-frame/trim-v]
(fn [db [options]]
(models/set-chat-ui-props db {:show-message-options? true
:message-options options})))
(def index-messages (partial into {} (map (juxt :message-id identity))))
(handlers/register-handler-fx
@ -85,6 +93,41 @@
{:db new-db
:data-store/update-message (-> (get-in new-db msg-path) (select-keys [:message-id :user-statuses]))})))
(handlers/register-handler-fx
:transport/set-message-envelope-hash
[re-frame/trim-v]
(fn [{:keys [db]} [chat-id message-id envelope-hash]]
{:db (assoc-in db [:transport/message-envelopes envelope-hash] {:chat-id chat-id
:message-id message-id})}))
(handlers/register-handler-fx
:signals/envelope-status
[re-frame/trim-v]
(fn [{:keys [db] :as cofx} [envelope-hash status]]
(let [{:keys [chat-id message-id]} (get-in db [:transport/message-envelopes envelope-hash])
message (get-in db [:chats chat-id :messages message-id])]
(models.message/update-message-status message status cofx))))
;; Change status of messages which are still in "sending" status to "not-sent"
;; (If signal from status-go has not been received)
(handlers/register-handler-fx
:process-pending-messages
[re-frame/trim-v]
(fn [{:keys [db]} []]
(let [pending-messages (->> db
:chats
vals
(mapcat (comp vals :messages))
(filter (fn [{:keys [from user-statuses]}] (= :sending (get user-statuses from)))))
updated-messages (map (fn [{:keys [from] :as message}]
(assoc-in message [:user-statuses from] :not-sent))
pending-messages)]
{:data-store/update-messages updated-messages
:db (reduce (fn [m {:keys [chat-id message-id from]}]
(assoc-in m [:chats chat-id :messages message-id :user-statuses from] :not-sent))
db
pending-messages)})))
(defn init-console-chat
[{:keys [db] :as cofx}]
(when-not (get-in db [:chats constants/console-chat-id])
@ -349,3 +392,15 @@
(handlers-macro/merge-fx cofx
{:db (assoc db :contacts/identity identity)}
(navigation/navigate-forget :profile))))
(handlers/register-handler-fx
:resend-message
[re-frame/trim-v]
(fn [cofx [chat-id message-id]]
(models.message/resend-message chat-id message-id cofx)))
(handlers/register-handler-fx
:delete-message
[re-frame/trim-v]
(fn [cofx [chat-id message-id]]
(models.message/delete-message chat-id message-id cofx)))

View File

@ -170,17 +170,20 @@
:from current-account-id}}})))
(defn- send
[chat-id send-record {{:contacts/keys [contacts]} :db :as cofx}]
[chat-id message-id send-record {{:contacts/keys [contacts] :keys [network-status current-public-key]} :db :as cofx}]
(let [{:keys [dapp? fcm-token]} (get contacts chat-id)]
(if dapp?
(send-dapp-message! cofx chat-id send-record)
(if fcm-token
(handlers-macro/merge-fx cofx
{:send-notification {:message "message"
:payload {:title "Status" :body "You have a new message"}
:tokens [fcm-token]}}
(transport/send send-record chat-id))
(transport/send send-record chat-id cofx)))))
(if (= network-status :offline)
{:dispatch-later [{:ms 10000
:dispatch [:update-message-status chat-id message-id current-public-key :not-sent]}]}
(if fcm-token
(handlers-macro/merge-fx cofx
{:send-notification {:message "message"
:payload {:title "Status" :body "You have a new message"}
:tokens [fcm-token]}}
(transport/send send-record chat-id))
(transport/send send-record chat-id cofx))))))
(defn add-message-type [message {:keys [chat-id group-chat public?]}]
(cond-> message
@ -200,19 +203,40 @@
:outgoing true
:timestamp now
:clock-value (utils.clocks/send last-clock-value)
:show? true}
:show? true
:user-statuses {identity :sending}}
chat))
(def ^:private transport-keys [:content :content-type :message-type :clock-value :timestamp])
(defn- upsert-and-send [{:keys [chat-id] :as message} {:keys [now] :as cofx}]
(let [send-record (protocol/map->Message (select-keys message transport-keys))
message-with-id (assoc message :message-id (transport.utils/message-id send-record))]
message-id (transport.utils/message-id send-record)
message-with-id (assoc message :message-id message-id)]
(handlers-macro/merge-fx cofx
(chat-model/upsert-chat {:chat-id chat-id
:timestamp now})
(add-message chat-id message-with-id true)
(send chat-id send-record))))
(send chat-id message-id send-record))))
(defn update-message-status [{:keys [chat-id message-id from] :as message} status {:keys [db]}]
(let [updated-message (assoc-in message [:user-statuses from] status)]
{:db (assoc-in db [:chats chat-id :messages message-id] updated-message)
:data-store/update-message updated-message}))
(defn resend-message [chat-id message-id cofx]
(let [message (get-in cofx [:db :chats chat-id :messages message-id])
send-record (-> message
(select-keys transport-keys)
(update :message-type keyword)
protocol/map->Message)]
(handlers-macro/merge-fx cofx
(send chat-id message-id send-record)
(update-message-status message :sending))))
(defn delete-message [chat-id message-id {:keys [db]}]
{:db (update-in db [:chats chat-id :messages] dissoc message-id)
:data-store/delete-message message-id})
(defn send-message [{:keys [db now random-id] :as cofx} {:keys [chat-id] :as params}]
(upsert-and-send (prepare-plain-message chat-id params (get-in db [:chats chat-id]) now) cofx))

View File

@ -12,6 +12,7 @@
[status-im.chat.views.input.input :as input]
[status-im.chat.views.actions :as actions]
[status-im.chat.views.bottom-info :as bottom-info]
[status-im.chat.views.message.options :as message-options]
[status-im.chat.views.message.datemark :as message-datemark]
[status-im.chat.views.message.message :as message]
[status-im.chat.views.input.input :as input]
@ -123,6 +124,7 @@
(defview chat []
(letsubs [{:keys [group-chat public? input-text]} [:get-current-chat]
show-bottom-info? [:get-current-chat-ui-prop :show-bottom-info?]
show-message-options? [:get-current-chat-ui-prop :show-message-options?]
current-view [:get :view-id]]
;; this scroll-view is a hack that allows us to use on-blur and on-focus on Android
;; more details here: https://github.com/facebook/react-native/issues/11071
@ -140,4 +142,6 @@
[input/container {:text-empty? (string/blank? input-text)}]
(when show-bottom-info?
[bottom-info/bottom-info-view])
(when show-message-options?
[message-options/view])
[connectivity/error-view {:top (get platform/platform-specific :status-bar-default-height)}]]]))

View File

@ -102,6 +102,25 @@
:android {:font-size 13}
:ios {:font-size 14}})
(def not-sent-view
(assoc delivery-view :opacity 1
:margin-bottom 2
:padding-top 2))
(def not-sent-text
(assoc delivery-text :color styles/color-red
:opacity 1
:font-size 12
:text-align :right
:padding-top 4))
(def not-sent-icon
{:padding-top 3
:padding-left 3})
(def message-activity-indicator
{:padding-top 4})
(defn text-message
[{:keys [outgoing group-chat incoming-group]}]
(merge style-message-text

View File

@ -0,0 +1,37 @@
(ns status-im.chat.styles.message.options
(:require-macros [status-im.utils.styles :refer [defstyle defnstyle]])
(:require [status-im.ui.components.styles :as styles]
[status-im.ui.components.colors :as colors]
[status-im.constants :as constants]))
(defstyle row
{:flex-direction :row
:background-color :white
:align-items :center
:padding-horizontal 16
:ios {:height 36}
:android {:height 36}})
(def title
{:padding-horizontal 16
:padding-top 10
:padding-bottom 10})
(def title-text
{:font-weight :bold
:font-size 14
:letter-spacing -0.2
:line-height 20})
(def label
{:padding-horizontal 16})
(def label-text
{:font-size 12})
(def icon
{:width 40
:height 40
:border-radius 20
:align-items :center
:justify-content :center})

View File

@ -18,14 +18,14 @@
:friction 6
:tension 40}))))
(defn- overlay [{:keys [on-click-outside]} items]
(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 & _]
(defn container [height & _]
(let [anim-value (anim/create-value 1)
context {:to-value height
:val anim-value}

View File

@ -5,6 +5,8 @@
[status-im.ui.components.react :as react]
[status-im.ui.components.animation :as animation]
[status-im.ui.components.list-selection :as list-selection]
[status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.action-sheet :as action-sheet]
[status-im.commands.utils :as commands.utils]
[status-im.chat.models.commands :as models.commands]
[status-im.chat.models.message :as models.message]
@ -251,9 +253,30 @@
:font :default}
(str "+ " (- delivery-statuses-count 3))])]]))))
(defn message-activity-indicator []
[react/view style/message-activity-indicator
[react/activity-indicator {:animating true}]])
(defn message-not-sent-text [chat-id message-id]
[react/touchable-highlight {:on-press (fn [] (if platform/ios?
(action-sheet/show {:title (i18n/label :message-not-sent)
:options [{:label (i18n/label :resend-message)
:action #(re-frame/dispatch [:resend-message chat-id message-id])}
{:label (i18n/label :delete-message)
:destructive? true
:action #(re-frame/dispatch [:delete-message chat-id message-id])}]})
(re-frame/dispatch
[:show-message-options {:chat-id chat-id
:message-id message-id}])))}
[react/view style/not-sent-view
[react/text {:style style/not-sent-text}
(i18n/message-status-label :not-sent)]
[react/view style/not-sent-icon
[vector-icons/icon :icons/warning {:color colors/red}]]]])
(defn message-delivery-status
[{:keys [chat-id current-public-key user-statuses content]}]
(let [outgoing-status (or (get user-statuses current-public-key) :sending)
[{:keys [chat-id message-id current-public-key user-statuses content last-outgoing? outgoing message-type] :as message}]
(let [outgoing-status (or (get user-statuses current-public-key) :not-sent)
delivery-status (get user-statuses chat-id)
status (cond (and (= constants/console-chat-id chat-id)
(not (console/commands-with-delivery-status (:command content))))
@ -261,7 +284,14 @@
:else
(or delivery-status outgoing-status))]
[text-status status]))
(case outgoing-status
:sending [message-activity-indicator]
:not-sent [message-not-sent-text chat-id message-id]
(when last-outgoing?
(if (= message-type :group-user-message)
[group-message-delivery-status message]
(when outgoing
[text-status status]))))))
(defn- photo [from photo-path]
[react/view
@ -293,7 +323,7 @@
(gfycat/generate-gfy from))])) ; TODO: We defensively generate the name for now, to be revisited when new protocol is defined
(defn message-body
[{:keys [timestamp-str last-outgoing? last-in-group? message-type first-in-group? from outgoing group-chat username] :as message} content]
[{:keys [timestamp-str last-in-group? first-in-group? from outgoing username] :as message} content]
[react/view (style/group-message-wrapper message)
[react/view (style/message-body message)
(when (not outgoing)
@ -308,11 +338,8 @@
[react/view {:style (style/timestamp-content-wrapper message)}
content
[message-timestamp timestamp-str]]]]
(when last-outgoing?
[react/view style/delivery-status
(if (= message-type :group-user-message)
[group-message-delivery-status message]
[message-delivery-status message])])])
[react/view style/delivery-status
[message-delivery-status message]]])
(defn message-container-animation-logic [{:keys [to-value val callback]}]
(fn [_]

View File

@ -0,0 +1,44 @@
(ns status-im.chat.views.message.options
(:require-macros [status-im.utils.views :refer [defview]])
(:require [re-frame.core :as re-frame]
[reagent.core :as reagent]
[clojure.string :as string]
[status-im.ui.components.react :as react]
[status-im.ui.components.colors :as colors]
[status-im.chat.views.bottom-info :as bottom-info]
[status-im.chat.styles.screen :as styles]
[status-im.chat.styles.message.options :as options.styles]
[status-im.i18n :as i18n]
[status-im.ui.components.animation :as anim]
[status-im.ui.components.list.views :as list]
[status-im.utils.core :as utils]
[status-im.utils.identicon :as identicon]
[status-im.ui.components.icons.vector-icons :as vector-icons]))
(defn action-item [{:keys [label icon style on-press]}]
[react/touchable-highlight {:on-press on-press}
[react/view options.styles/row
[react/view
[vector-icons/icon icon style]]
[react/view (merge options.styles/label style)
[react/text {:style (merge options.styles/label-text style)} (i18n/label label)]]]])
(defn view []
(let [{:keys [chat-id message-id]} @(re-frame/subscribe [:get-current-chat-ui-prop :message-options])
close-message-options-fn #(re-frame/dispatch [:set-chat-ui-props {:show-message-options? false}])]
[bottom-info/overlay {:on-click-outside close-message-options-fn}
[bottom-info/container (* styles/item-height 2)
[react/view
[react/view options.styles/title
[react/text {:style options.styles/title-text} (i18n/label :message-not-sent)]]
[action-item {:label :resend-message
:icon :icons/refresh
:on-press #(do
(close-message-options-fn)
(re-frame/dispatch [:resend-message chat-id message-id]))}]
[action-item {:label :delete-message
:icon :icons/delete
:style {:color colors/red}
:on-press #(do
(close-message-options-fn)
(re-frame/dispatch [:delete-message chat-id message-id]))}]]]]))

View File

@ -91,12 +91,21 @@
message
{:from (or from "anonymous")
:received-timestamp (datetime/timestamp)})))))
(defn delete
[message-id]
(when (data-store/exists? message-id)
(data-store/delete message-id)))
(re-frame/reg-fx
:data-store/save-message
(fn [message]
(async/go (async/>! core/realm-queue #(save message)))))
(re-frame/reg-fx
:data-store/delete-message
(fn [message-id]
(async/go (async/>! core/realm-queue #(delete message-id)))))
(defn update-message
[{:keys [message-id] :as message}]
(when-let [{:keys [chat-id]} (data-store/get-by-id message-id)]

View File

@ -70,6 +70,12 @@
[message]
(realm/save @realm/account-realm :message message true))
(defn delete
[message-id]
(let [current-realm @realm/account-realm]
(when-let [message (realm/get-by-field current-realm :message :message-id message-id)]
(realm/delete current-realm message))))
(defn delete-by-chat-id
[chat-id]
(let [current-realm @realm/account-realm]

View File

@ -99,6 +99,10 @@
:status-seen "Seen"
:status-delivered "Delivered"
:status-failed "Failed"
:status-not-sent "Not sent. Tap for options"
:message-not-sent "Message not sent"
:resend-message "Resend"
:delete-message "Delete message"
;;datetime
:datetime-ago-format "{{number}} {{time-intervals}} {{ago}}"

View File

@ -94,11 +94,9 @@
(send [this chat-id cofx]
(send {:chat-id chat-id
:payload this
:success-event [:update-message-status
:success-event [:transport/set-message-envelope-hash
chat-id
(transport.utils/message-id this)
(get-in cofx [:db :current-public-key])
:sent]}
(transport.utils/message-id this)]}
cofx))
(receive [this chat-id signature cofx]
{:dispatch [:chat-received-message/add (assoc (into {} this)

View File

@ -53,22 +53,22 @@
[{:keys [web3 whisper-message on-success on-error]}]
(.. web3
-shh
(post (clj->js whisper-message) (fn [err resp]
(if-not err
(on-success resp)
(on-error err))))))
(extPost (clj->js whisper-message) (fn [err resp]
(if-not err
(on-success resp)
(on-error err))))))
(re-frame/reg-fx
:shh/post
(fn [{:keys [web3 message success-event error-event]
:or {error-event :protocol/send-status-message-error}}]
(post-message {:web3 web3
(post-message {:web3 web3
:whisper-message (update message :payload (comp transport.utils/from-utf8
transit/serialize))
:on-success (if success-event
#(re-frame/dispatch success-event)
#(log/debug :shh/post-success))
:on-error #(re-frame/dispatch [error-event %])})))
:on-success (if success-event
#(re-frame/dispatch (conj success-event %))
#(log/debug :shh/post-success))
:on-error #(re-frame/dispatch [error-event %])})))
;; This event params contain a recipients key because it's a vector of map with public-key and topic keys.
;; the :shh/post event has public-key and topic keys at the top level of the args map.

View File

@ -82,7 +82,8 @@
:icons/refresh (components.svg/slurp-svg "./resources/icons/refresh.svg")
:icons/newchat (components.svg/slurp-svg "./resources/icons/newchat.svg")
:icons/logo (components.svg/slurp-svg "./resources/icons/logo.svg")
:icons/camera (components.svg/slurp-svg "./resources/icons/camera.svg")})
:icons/camera (components.svg/slurp-svg "./resources/icons/camera.svg")
:icons/warning (components.svg/slurp-svg "./resources/icons/warning.svg")})
(defn normalize-property-name [n]
(if (= n :icons/options)

View File

@ -18,34 +18,35 @@
status-im.ui.screens.add-new.new-public-chat.db))
;; initial state of app-db
(def app-db {:current-public-key nil
:status-module-initialized? (or platform/ios? js/goog.DEBUG)
:keyboard-height 0
:accounts/accounts {}
:navigation-stack '()
:contacts/contacts {}
:qr-codes {}
:group/contact-groups {}
:group/selected-contacts #{}
:chats {}
:current-chat-id nil
:selected-participants #{}
:discoveries {}
:discover-search-tags #{}
:discover-current-dapp {}
:tags []
:sync-state :done
:wallet.transactions constants/default-wallet-transactions
:wallet-selected-asset {}
:prices {}
:notifications {}
:network constants/default-network
:networks/networks constants/default-networks
:inbox/wnodes constants/default-wnodes
:inbox/password constants/inbox-password
:my-profile/editing? false
:transport/chats {}
:desktop/desktop {:tab-view-id :home}})
(def app-db {:current-public-key nil
:status-module-initialized? (or platform/ios? js/goog.DEBUG)
:keyboard-height 0
:accounts/accounts {}
:navigation-stack '()
:contacts/contacts {}
:qr-codes {}
:group/contact-groups {}
:group/selected-contacts #{}
:chats {}
:current-chat-id nil
:selected-participants #{}
:discoveries {}
:discover-search-tags #{}
:discover-current-dapp {}
:tags []
:sync-state :done
:wallet.transactions constants/default-wallet-transactions
:wallet-selected-asset {}
:prices {}
:notifications {}
:network constants/default-network
:networks/networks constants/default-networks
:inbox/wnodes constants/default-wnodes
:inbox/password constants/inbox-password
:my-profile/editing? false
:transport/chats {}
:transport/message-envelopes {}
:desktop/desktop {:tab-view-id :home}})
;;;;GLOBAL
@ -128,6 +129,8 @@
(spec/def :node/after-start (spec/nilable vector?))
(spec/def :node/after-stop (spec/nilable vector?))
(spec/def ::message-envelopes (spec/nilable map?))
(spec/def ::db (allowed-keys
:opt
[:contacts/contacts
@ -166,6 +169,7 @@
:browser/options
:new/open-dapp
:navigation/screen-params
:transport/message-envelopes
:transport/chats
:transport/discovery-filter
:desktop/desktop]

View File

@ -277,6 +277,7 @@
[:initialize-browsers]
[:initialize-debugging {:address address}]
[:send-account-update-if-needed]
[:process-pending-messages]
[:update-wallet]
[:update-transactions]
[:get-fcm-token]
@ -356,6 +357,8 @@
"node.stopped" [:status-node-stopped]
"module.initialized" [:status-module-initialized]
"jail.signal" (handle-jail-signal event)
"envelope.sent" [:signals/envelope-status (:hash event) :sent]
"envelope.expired" [:signals/envelope-status (:hash event) :not-sent]
(log/debug "Event " type " not handled"))]
(when to-dispatch
{:dispatch to-dispatch}))))