This commit is contained in:
Andrey Shovkoplyas 2019-09-18 14:30:36 +02:00 committed by Andrea Maria Piana
parent 9a114128bc
commit 60d5815bde
No known key found for this signature in database
GPG Key ID: AA6CCA6DE0E06424
14 changed files with 164 additions and 25 deletions

View File

@ -18,7 +18,9 @@
[status-im.utils.platform :as platform] [status-im.utils.platform :as platform]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]
[status-im.chat.models.message-seen :as message-seen] [status-im.chat.models.message-seen :as message-seen]
[status-im.chat.models.loading :as loading])) [status-im.chat.models.loading :as loading]
[status-im.utils.image-processing :as image-processing]
[status-im.ipfs.core :as ipfs]))
(defn- get-chat [cofx chat-id] (defn- get-chat [cofx chat-id]
(get-in cofx [:db :chats chat-id])) (get-in cofx [:db :chats chat-id]))
@ -309,3 +311,31 @@
{:events [:chat.ui/input-on-focus]} {:events [:chat.ui/input-on-focus]}
[{db :db}] [{db :db}]
{:db (set-chat-ui-props db {:input-bottom-sheet nil})}) {:db (set-chat-ui-props db {:input-bottom-sheet nil})})
(re-frame/reg-fx
:chat-open-image-picker
(fn []
(react/show-image-picker
(fn [image]
(image-processing/resize
(aget image "path")
400 400
(fn [resized-image]
(re-frame/dispatch [:chat.ui/set-chat-ui-props {:send-image (aget resized-image "path")}]))
#()))
"any")))
(fx/defn chat-open-image-picker
{:events [:chat.ui/open-image-picker]}
[cofx]
{:chat-open-image-picker nil})
(fx/defn send-image
{:events [:chat.ui/send-image]}
[{:keys [db] :as cofx} send-image]
(fx/merge cofx
{:db (set-chat-ui-props db {:send-image-loading? true})}
(ipfs/add {:value #js {:uri send-image :name "image"}
:opts {:headers {"Content-Type" "multipart/form-data"}}
:on-failure #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:send-image-loading? false}])
:on-success #(re-frame/dispatch [:chat-image-added-to-ipfs %])})))

View File

@ -121,6 +121,14 @@
:pack pack} :pack pack}
:text "Update to latest version to see a nice sticker here!"}))) :text "Update to latest version to see a nice sticker here!"})))
(fx/defn send-image
[{:keys [db] :as cofx} hash]
(when-not (string/blank? hash)
(chat.message/send-message cofx {:chat-id (:current-chat-id db)
:content-type constants/content-type-image
:content {:chat-id (:current-chat-id db)
:hash hash}})))
(fx/defn send-current-message (fx/defn send-current-message
"Sends message from current chat input" "Sends message from current chat input"
[{{:keys [current-chat-id] :as db} :db :as cofx}] [{{:keys [current-chat-id] :as db} :db :as cofx}]
@ -133,6 +141,13 @@
;;TODO: should be implemented on status-go side ;;TODO: should be implemented on status-go side
;;see https://github.com/status-im/team-core/blob/6c3d67d8e8bd8500abe52dab06a59e976ec942d2/rfc-001.md#status-gostatus-react-interface ;;see https://github.com/status-im/team-core/blob/6c3d67d8e8bd8500abe52dab06a59e976ec942d2/rfc-001.md#status-gostatus-react-interface
(fx/defn chat-image-added-to-ipfs
{:events [:chat-image-added-to-ipfs]}
[{:keys [db] :as cofx} {:keys [hash]}]
(fx/merge cofx
{:db (chat/set-chat-ui-props db {:send-image-loading? false :show-image? nil :send-image nil})}
(send-image hash)))
;; effects ;; effects
(re-frame/reg-fx (re-frame/reg-fx

View File

@ -13,6 +13,7 @@
(def content-type-emoji 4) (def content-type-emoji 4)
(def content-type-command 5) (def content-type-command 5)
(def content-type-system-text 6) (def content-type-system-text 6)
(def content-type-image 7)
(def message-type-one-to-one 1) (def message-type-one-to-one 1)
(def message-type-public-group 2) (def message-type-public-group 2)

View File

@ -1204,10 +1204,8 @@
:http-post :http-post
http-post) http-post)
(defn- http-raw-post [{:keys [url body response-validator success-event-creator failure-event-creator timeout-ms opts]}] (defn- http-raw-post [{:keys [url body response-validator on-success on-error timeout-ms opts]}]
(let [on-success #(re-frame/dispatch (success-event-creator %)) (let [all-opts (assoc opts
on-error (when failure-event-creator #(re-frame/dispatch (failure-event-creator %)))
all-opts (assoc opts
:valid-response? response-validator :valid-response? response-validator
:timeout-ms timeout-ms)] :timeout-ms timeout-ms)]
(http/raw-post url body on-success on-error all-opts))) (http/raw-post url body on-success on-error all-opts)))

View File

@ -31,18 +31,18 @@
(fx/defn add (fx/defn add
"Add `value` on ipfs, and returns its b58 encoded CID" "Add `value` on ipfs, and returns its b58 encoded CID"
[cofx {:keys [value on-success on-failure]}] [cofx {:keys [value on-success on-failure opts timeout-ms]}]
(let [formdata (doto (js/FormData.) (let [formdata (doto (js/FormData.)
;; the key is ignored so there is no need to provide one ;; the key is ignored so there is no need to provide one
(.append "file" value))] (.append "file" value))]
{:http-raw-post (cond-> {:url ipfs-add-url {:http-raw-post {:url ipfs-add-url
:body formdata :body formdata
:timeout-ms 5000 :opts opts
:success-event-creator :timeout-ms (or 25000 timeout-ms)
(fn [{:keys [status body]}] :on-failure on-failure
(if (= 200 status) :on-success
(on-success (parse-ipfs-add-response body)) (fn [{:keys [status body]}]
(when on-failure (if (= 200 status)
(on-failure status))))} (on-success (parse-ipfs-add-response body))
on-failure (when on-failure
(assoc :failure-event-creator on-failure))})) (on-failure status))))}}))

View File

View File

@ -0,0 +1,3 @@
(ns status-im.ui.screens.chat.image.styles)
(def image-panel-height 263)

View File

@ -0,0 +1,83 @@
(ns status-im.ui.screens.chat.image.views
(:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.ui.components.react :as react]
[status-im.utils.platform :as platform]
[re-frame.core :as re-frame]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.animation :as anim]
[status-im.ui.screens.chat.image.styles :as styles]
[status-im.utils.utils :as utils]
[status-im.i18n :as i18n]
[status-im.ui.components.icons.vector-icons :as icons]))
(defn button [images-showing?]
[react/touchable-highlight
{:on-press
(fn [_]
(re-frame/dispatch [:chat.ui/set-chat-ui-props
{:input-bottom-sheet (when-not images-showing? :images)}])
(when-not platform/desktop? (js/setTimeout #(react/dismiss-keyboard!) 100)))
:accessibility-label :show-photo-icon}
[icons/icon
:main-icons/photo
{:container-style {:margin 14 :margin-right 6}
:color (if images-showing? colors/blue colors/gray)}]])
(defn show-panel-anim
[bottom-anim-value alpha-value]
(anim/start
(anim/parallel
[(anim/spring bottom-anim-value {:toValue 0
:useNativeDriver true})
(anim/timing alpha-value {:toValue 1
:duration 500
:useNativeDriver true})])))
(defn select-button [title icon on-press]
[react/touchable-highlight {:on-press on-press :style {:flex 1}}
[react/view {:background-color colors/black :max-height 223 :flex 1 :border-radius 16
:align-items :center :justify-content :center}
[react/view {:height 48 :width 48 :align-items :center :justify-content :center :border-radius 24
:border-width 2 :border-color colors/gray}
[icons/icon icon {:color :white}]]
[react/text {:style {:margin-top 9 :typography :caption :color colors/gray}} title]]])
(defn take-picture []
(re-frame/dispatch [:request-permissions
{:permissions [:camera]
:on-allowed #(re-frame/dispatch [:navigate-to :profile-photo-capture])
:on-denied (fn []
(utils/set-timeout
#(utils/show-popup (i18n/label :t/error)
(i18n/label :t/camera-access-error))
50))}]))
(defn round-button [on-press cancel? loading?]
[react/touchable-highlight {:on-press (when-not loading? on-press)}
[react/view {:width 32 :height 32 :border-radius 16 :background-color (if cancel? colors/gray colors/blue)
:align-items :center :justify-content :center}
(if loading?
[react/activity-indicator {:color :white
:animating true}]
[icons/icon (if cancel? :main-icons/close :main-icons/arrow-up) {:color :white}])]])
(defview image-view []
(letsubs [send-image [:chats/current-chat-ui-prop :send-image]
loading? [:chats/current-chat-ui-prop :send-image-loading?]
bottom-anim-value (anim/create-value styles/image-panel-height)
alpha-value (anim/create-value 0)]
{:component-did-mount #(show-panel-anim bottom-anim-value alpha-value)}
[react/animated-view {:style {:background-color :white
:height styles/image-panel-height
:transform [{:translateY bottom-anim-value}]
:opacity alpha-value}}
(if send-image
[react/view {:align-items :center :flex-direction :row :flex 1 :justify-content :space-between
:padding-horizontal 20}
[round-button #(re-frame/dispatch [:chat.ui/set-chat-ui-props {:send-image nil}]) true false]
[react/image {:source {:uri send-image} :style {:width 150 :height 150 :border-radius 8}}]
[round-button #(re-frame/dispatch [:chat.ui/send-image send-image]) false loading?]]
[react/view {:flex-direction :row :padding-horizontal 16 :padding-top 12 :flex 1}
[select-button "Take a picture" :main-icons/camera take-picture]
[react/view {:width 16}]
[select-button "Choose photo" :main-icons/photo #(re-frame/dispatch [:chat.ui/open-image-picker])]])]))

View File

@ -12,6 +12,7 @@
[status-im.ui.components.react :as react] [status-im.ui.components.react :as react]
[status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.utils.config :as config] [status-im.utils.config :as config]
[status-im.ui.screens.chat.image.views :as image]
[status-im.ui.screens.chat.stickers.views :as stickers] [status-im.ui.screens.chat.stickers.views :as stickers]
[status-im.ui.screens.chat.extensions.views :as extensions])) [status-im.ui.screens.chat.extensions.views :as extensions]))
@ -67,6 +68,8 @@
[reply-message-view] [reply-message-view]
[react/view {:style style/input-container} [react/view {:style style/input-container}
[basic-text-input input-text cooldown-enabled?] [basic-text-input input-text cooldown-enabled?]
(when input-text-empty?
[image/button (= :images input-bottom-sheet)])
(when (and input-text-empty? mainnet?) (when (and input-text-empty? mainnet?)
[stickers/button (= :stickers input-bottom-sheet)]) [stickers/button (= :stickers input-bottom-sheet)])
(when (and one-to-one-chat? input-text-empty? (or config/commands-enabled? mainnet?)) (when (and one-to-one-chat? input-text-empty? (or config/commands-enabled? mainnet?))

View File

@ -295,4 +295,7 @@
[react/image {:style {:margin 10 :width 140 :height 140} [react/image {:style {:margin 10 :width 140 :height 140}
;;TODO (perf) move to event ;;TODO (perf) move to event
:source {:uri (contenthash/url (-> content :sticker :hash))}}] :source {:uri (contenthash/url (-> content :sticker :hash))}}]
[unknown-content-type message]))))]]))) (if (= content-type constants/content-type-image)
[react/image {:style {:margin-vertical 10 :width 140 :height 140 :border-radius 8}
:source {:uri (str "https://ipfs.infura.io/ipfs/" (:hash content))}}]
[unknown-content-type message])))))]])))

View File

@ -15,6 +15,7 @@
[status-im.ui.screens.chat.stickers.views :as stickers] [status-im.ui.screens.chat.stickers.views :as stickers]
[status-im.ui.screens.chat.styles.main :as style] [status-im.ui.screens.chat.styles.main :as style]
[status-im.ui.screens.chat.toolbar-content :as toolbar-content] [status-im.ui.screens.chat.toolbar-content :as toolbar-content]
[status-im.ui.screens.chat.image.views :as image]
[status-im.ui.screens.chat.state :as state] [status-im.ui.screens.chat.state :as state]
[status-im.utils.debounce :as debounce] [status-im.utils.debounce :as debounce]
[status-im.ui.screens.chat.extensions.views :as extensions] [status-im.ui.screens.chat.extensions.views :as extensions]
@ -147,6 +148,8 @@
[stickers/stickers-view] [stickers/stickers-view]
:extensions :extensions
[extensions/extensions-view] [extensions/extensions-view]
:images
[image/image-view]
[empty-bottom-sheet]))) [empty-bottom-sheet])))
(defview chat [] (defview chat []

View File

View File

@ -8,7 +8,7 @@
;; Default HTTP request timeout ms ;; Default HTTP request timeout ms
(def http-request-default-timeout-ms 3000) (def http-request-default-timeout-ms 3000)
(defn- headers [^js response] (defn- response-headers [^js response]
(let [entries (es6-iterator-seq (.entries ^js (.-headers response)))] (let [entries (es6-iterator-seq (.entries ^js (.-headers response)))]
(reduce #(assoc %1 (string/trim (string/lower-case (first %2))) (string/trim (second %2))) {} entries))) (reduce #(assoc %1 (string/trim (string/lower-case (first %2))) (string/trim (second %2))) {} entries)))
@ -17,11 +17,11 @@
([url body on-success] (raw-post url body on-success nil)) ([url body on-success] (raw-post url body on-success nil))
([url body on-success on-error] ([url body on-success on-error]
(raw-post url body on-success on-error nil)) (raw-post url body on-success on-error nil))
([url body on-success on-error {:keys [timeout-ms]}] ([url body on-success on-error {:keys [timeout-ms headers]}]
(-> (fetch (-> (fetch
url url
(clj->js {:method "POST" (clj->js {:method "POST"
:headers {"Cache-Control" "no-cache"} :headers (merge {"Cache-Control" "no-cache"} headers)
:body body :body body
:timeout (or timeout-ms http-request-default-timeout-ms)})) :timeout (or timeout-ms http-request-default-timeout-ms)}))
(.then (fn [^js response] (.then (fn [^js response]
@ -29,7 +29,7 @@
(.text response) (.text response)
(.then (fn [body] (.then (fn [body]
(on-success {:status (.-status response) (on-success {:status (.-status response)
:headers (headers response) :headers (response-headers response)
:body body})))))) :body body}))))))
(.catch (or on-error (.catch (or on-error
(fn [error] (fn [error]
@ -92,7 +92,7 @@
(.text response) (.text response)
(.then (fn [body] (.then (fn [body]
(on-success {:status (.-status response) (on-success {:status (.-status response)
:headers (headers response) :headers (response-headers response)
:body body})))))) :body body}))))))
(.catch (or on-error (.catch (or on-error
(fn [error] (fn [error]

View File

@ -1,9 +1,9 @@
(ns status-im.utils.image-processing (ns status-im.utils.image-processing
(:require [goog.object :as object] (:require [goog.object :as object]
[status-im.utils.fs :as fs] [status-im.utils.fs :as fs]
["react-native-image-resizer" :as image-resizer])) ["react-native-image-resizer" :default image-resizer]))
(defn- resize [path max-width max-height on-resize on-error] (defn resize [path max-width max-height on-resize on-error]
(-> (.createResizedImage image-resizer path max-width max-height "JPEG" 75 0 nil) (-> (.createResizedImage image-resizer path max-width max-height "JPEG" 75 0 nil)
(.then on-resize) (.then on-resize)
(.catch on-error))) (.catch on-error)))