images
This commit is contained in:
parent
9a114128bc
commit
60d5815bde
|
@ -18,7 +18,9 @@
|
|||
[status-im.utils.platform :as platform]
|
||||
[status-im.utils.utils :as utils]
|
||||
[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]
|
||||
(get-in cofx [:db :chats chat-id]))
|
||||
|
@ -309,3 +311,31 @@
|
|||
{:events [:chat.ui/input-on-focus]}
|
||||
[{db :db}]
|
||||
{: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 %])})))
|
|
@ -121,6 +121,14 @@
|
|||
:pack pack}
|
||||
: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
|
||||
"Sends message from current chat input"
|
||||
[{{:keys [current-chat-id] :as db} :db :as cofx}]
|
||||
|
@ -133,6 +141,13 @@
|
|||
;;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
|
||||
|
||||
(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
|
||||
|
||||
(re-frame/reg-fx
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
(def content-type-emoji 4)
|
||||
(def content-type-command 5)
|
||||
(def content-type-system-text 6)
|
||||
(def content-type-image 7)
|
||||
|
||||
(def message-type-one-to-one 1)
|
||||
(def message-type-public-group 2)
|
||||
|
|
|
@ -1204,10 +1204,8 @@
|
|||
:http-post
|
||||
http-post)
|
||||
|
||||
(defn- http-raw-post [{:keys [url body response-validator success-event-creator failure-event-creator timeout-ms opts]}]
|
||||
(let [on-success #(re-frame/dispatch (success-event-creator %))
|
||||
on-error (when failure-event-creator #(re-frame/dispatch (failure-event-creator %)))
|
||||
all-opts (assoc opts
|
||||
(defn- http-raw-post [{:keys [url body response-validator on-success on-error timeout-ms opts]}]
|
||||
(let [all-opts (assoc opts
|
||||
:valid-response? response-validator
|
||||
:timeout-ms timeout-ms)]
|
||||
(http/raw-post url body on-success on-error all-opts)))
|
||||
|
|
|
@ -31,18 +31,18 @@
|
|||
|
||||
(fx/defn add
|
||||
"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.)
|
||||
;; the key is ignored so there is no need to provide one
|
||||
(.append "file" value))]
|
||||
{:http-raw-post (cond-> {:url ipfs-add-url
|
||||
:body formdata
|
||||
:timeout-ms 5000
|
||||
:success-event-creator
|
||||
(fn [{:keys [status body]}]
|
||||
(if (= 200 status)
|
||||
(on-success (parse-ipfs-add-response body))
|
||||
(when on-failure
|
||||
(on-failure status))))}
|
||||
on-failure
|
||||
(assoc :failure-event-creator on-failure))}))
|
||||
{:http-raw-post {:url ipfs-add-url
|
||||
:body formdata
|
||||
:opts opts
|
||||
:timeout-ms (or 25000 timeout-ms)
|
||||
:on-failure on-failure
|
||||
:on-success
|
||||
(fn [{:keys [status body]}]
|
||||
(if (= 200 status)
|
||||
(on-success (parse-ipfs-add-response body))
|
||||
(when on-failure
|
||||
(on-failure status))))}}))
|
|
@ -0,0 +1,3 @@
|
|||
(ns status-im.ui.screens.chat.image.styles)
|
||||
|
||||
(def image-panel-height 263)
|
|
@ -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])]])]))
|
|
@ -12,6 +12,7 @@
|
|||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.icons.vector-icons :as vector-icons]
|
||||
[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.extensions.views :as extensions]))
|
||||
|
||||
|
@ -67,6 +68,8 @@
|
|||
[reply-message-view]
|
||||
[react/view {:style style/input-container}
|
||||
[basic-text-input input-text cooldown-enabled?]
|
||||
(when input-text-empty?
|
||||
[image/button (= :images input-bottom-sheet)])
|
||||
(when (and input-text-empty? mainnet?)
|
||||
[stickers/button (= :stickers input-bottom-sheet)])
|
||||
(when (and one-to-one-chat? input-text-empty? (or config/commands-enabled? mainnet?))
|
||||
|
|
|
@ -295,4 +295,7 @@
|
|||
[react/image {:style {:margin 10 :width 140 :height 140}
|
||||
;;TODO (perf) move to event
|
||||
: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])))))]])))
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[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.chat.image.views :as image]
|
||||
[status-im.ui.screens.chat.state :as state]
|
||||
[status-im.utils.debounce :as debounce]
|
||||
[status-im.ui.screens.chat.extensions.views :as extensions]
|
||||
|
@ -147,6 +148,8 @@
|
|||
[stickers/stickers-view]
|
||||
:extensions
|
||||
[extensions/extensions-view]
|
||||
:images
|
||||
[image/image-view]
|
||||
[empty-bottom-sheet])))
|
||||
|
||||
(defview chat []
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
;; Default HTTP request timeout ms
|
||||
(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)))]
|
||||
(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 on-error]
|
||||
(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
|
||||
url
|
||||
(clj->js {:method "POST"
|
||||
:headers {"Cache-Control" "no-cache"}
|
||||
:headers (merge {"Cache-Control" "no-cache"} headers)
|
||||
:body body
|
||||
:timeout (or timeout-ms http-request-default-timeout-ms)}))
|
||||
(.then (fn [^js response]
|
||||
|
@ -29,7 +29,7 @@
|
|||
(.text response)
|
||||
(.then (fn [body]
|
||||
(on-success {:status (.-status response)
|
||||
:headers (headers response)
|
||||
:headers (response-headers response)
|
||||
:body body}))))))
|
||||
(.catch (or on-error
|
||||
(fn [error]
|
||||
|
@ -92,7 +92,7 @@
|
|||
(.text response)
|
||||
(.then (fn [body]
|
||||
(on-success {:status (.-status response)
|
||||
:headers (headers response)
|
||||
:headers (response-headers response)
|
||||
:body body}))))))
|
||||
(.catch (or on-error
|
||||
(fn [error]
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
(ns status-im.utils.image-processing
|
||||
(:require [goog.object :as object]
|
||||
[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)
|
||||
(.then on-resize)
|
||||
(.catch on-error)))
|
||||
|
|
Loading…
Reference in New Issue