Add pick multiple images

Add inner border to image message

Add max batch size of 5

Disable camera roll more images

Use interop instead of aget

Add border as a separate layer

Signed-off-by: Gheorghe Pinzaru <feross95@gmail.com>
This commit is contained in:
Gheorghe Pinzaru 2020-10-19 16:02:19 +03:00
parent fe5b5ab4bc
commit cf4bef8268
No known key found for this signature in database
GPG Key ID: C9A094959935A952
13 changed files with 212 additions and 112 deletions

View File

@ -259,8 +259,8 @@ PODS:
- React - React
- react-native-splash-screen (3.2.0): - react-native-splash-screen (3.2.0):
- React - React
- react-native-webview (10.3.1): - react-native-webview (10.9.2):
- React - React-Core
- React-RCTActionSheet (0.62.2): - React-RCTActionSheet (0.62.2):
- React-Core/RCTActionSheetHeaders (= 0.62.2) - React-Core/RCTActionSheetHeaders (= 0.62.2)
- React-RCTAnimation (0.62.2): - React-RCTAnimation (0.62.2):
@ -630,7 +630,7 @@ SPEC CHECKSUMS:
react-native-shake: de052eaa3eadc4a326b8ddd7ac80c06e8d84528c react-native-shake: de052eaa3eadc4a326b8ddd7ac80c06e8d84528c
react-native-slider: 12bd76d3d568c9c5500825db54123d44b48e4ad4 react-native-slider: 12bd76d3d568c9c5500825db54123d44b48e4ad4
react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865 react-native-splash-screen: 200d11d188e2e78cea3ad319964f6142b6384865
react-native-webview: 40bbeb6d011226f34cb83f845aeb0fdf515cfc5f react-native-webview: 4e96d493f9f90ba4f03b28933f30b2964df07e39
React-RCTActionSheet: f41ea8a811aac770e0cc6e0ad6b270c644ea8b7c React-RCTActionSheet: f41ea8a811aac770e0cc6e0ad6b270c644ea8b7c
React-RCTAnimation: 49ab98b1c1ff4445148b72a3d61554138565bad0 React-RCTAnimation: 49ab98b1c1ff4445148b72a3d61554138565bad0
React-RCTBlob: a332773f0ebc413a0ce85942a55b064471587a71 React-RCTBlob: a332773f0ebc413a0ce85942a55b064471587a71

View File

@ -46,7 +46,8 @@
:shadow-01 "rgba(0,9,26,0.12)" ; Main shadow color :shadow-01 "rgba(0,9,26,0.12)" ; Main shadow color
:backdrop "rgba(0,0,0,0.4)" ; Backdrop for modals and bottom sheet :backdrop "rgba(0,0,0,0.4)" ; Backdrop for modals and bottom sheet
:border-01 "rgba(238,242,245,1)" :border-01 "rgba(238,242,245,1)"
:border-02 "rgba(67, 96, 223, 0.1)"}) :border-02 "rgba(67, 96, 223, 0.1)"
:highlight "rgba(67,96,223,0.4)"})
(def dark-theme (def dark-theme
{:positive-01 "rgba(68,208,88,1)" {:positive-01 "rgba(68,208,88,1)"
@ -75,7 +76,8 @@
:shadow-01 "rgba(0,0,0,0.75)" :shadow-01 "rgba(0,0,0,0.75)"
:backdrop "rgba(0,0,0,0.4)" :backdrop "rgba(0,0,0,0.4)"
:border-01 "rgba(37,37,40,1)" :border-01 "rgba(37,37,40,1)"
:border-02 "rgba(97,119,229,0.1)"}) :border-02 "rgba(97,119,229,0.1)"
:highlight "rgba(67,96,223,0.4)"})
(def theme (reagent/atom light-theme)) (def theme (reagent/atom light-theme))

View File

@ -11,6 +11,7 @@
[status-im.utils.platform :as platform])) [status-im.utils.platform :as platform]))
(def maximum-image-size-px 2000) (def maximum-image-size-px 2000)
(def max-images-batch 5)
(defn- resize-and-call [uri cb] (defn- resize-and-call [uri cb]
(react/image-get-size (react/image-get-size
@ -22,12 +23,17 @@
(if resize? maximum-image-size-px width) (if resize? maximum-image-size-px width)
(if resize? maximum-image-size-px height) (if resize? maximum-image-size-px height)
60 60
(fn [resized-image] (fn [^js resized-image]
(let [path (aget resized-image "path") (let [path (.-path resized-image)
path (if (string/starts-with? path "file") path (str "file://" path))] path (if (string/starts-with? path "file") path (str "file://" path))]
(cb path))) (cb path)))
#(log/error "could not resize image" %)))))) #(log/error "could not resize image" %))))))
(defn result->id [^js result]
(if platform/ios?
(.-localIdentifier result)
(.-path result)))
(re-frame/reg-fx (re-frame/reg-fx
::save-image-to-gallery ::save-image-to-gallery
(fn [base64-uri] (fn [base64-uri]
@ -41,8 +47,8 @@
width width
height height
100 100
(fn [resized-image] (fn [^js resized-image]
(let [path (aget resized-image "path") (let [path (.-path resized-image)
path (if (string/starts-with? path "file") path (str "file://" path))] path (if (string/starts-with? path "file") path (str "file://" path))]
(.saveToCameraRoll CameraRoll path))) (.saveToCameraRoll CameraRoll path)))
#(log/error "could not resize image" %))))))) #(log/error "could not resize image" %)))))))
@ -51,18 +57,27 @@
::chat-open-image-picker ::chat-open-image-picker
(fn [] (fn []
(react/show-image-picker (react/show-image-picker
(fn [result] (fn [^js images]
(resize-and-call ;; NOTE(Ferossgp): Because we can't highlight the already selected images inside
(aget result "path") ;; gallery, we just clean previous state and set all newly picked images
#(re-frame/dispatch [:chat.ui/image-selected %]))) (when (and platform/ios? (pos? (count images)))
"photo"))) (re-frame/dispatch [:chat.ui/clear-sending-images]))
(doseq [^js result (if platform/ios?
(take max-images-batch images)
[images])]
(resize-and-call (.-path result)
#(re-frame/dispatch [:chat.ui/image-selected (result->id result) %]))))
;; NOTE(Ferossgp): On android you cannot set max limit on images, when a user
;; selects too many images the app crashes.
{:media-type "photo"
:multiple platform/ios?})))
(re-frame/reg-fx (re-frame/reg-fx
::image-selected ::image-selected
(fn [uri] (fn [uri]
(resize-and-call (resize-and-call
uri uri
#(re-frame/dispatch [:chat.ui/image-selected %])))) #(re-frame/dispatch [:chat.ui/image-selected uri %]))))
(re-frame/reg-fx (re-frame/reg-fx
::camera-roll-get-photos ::camera-roll-get-photos
@ -72,7 +87,7 @@
:on-allowed (fn [] :on-allowed (fn []
(-> (.getPhotos CameraRoll #js {:first num :assetType "Photos" :groupTypes "All"}) (-> (.getPhotos CameraRoll #js {:first num :assetType "Photos" :groupTypes "All"})
(.then #(re-frame/dispatch [:on-camera-roll-get-photos (:edges (types/js->clj %))])) (.then #(re-frame/dispatch [:on-camera-roll-get-photos (:edges (types/js->clj %))]))
(.catch #(log/error "could not get cameraroll photos"))))}))) (.catch #(log/warn "could not get cameraroll photos"))))})))
(fx/defn image-captured (fx/defn image-captured
{:events [:chat.ui/image-captured]} {:events [:chat.ui/image-captured]}
@ -89,27 +104,46 @@
[{db :db} photos] [{db :db} photos]
{:db (assoc db :camera-roll-photos (mapv #(get-in % [:node :image :uri]) photos))}) {:db (assoc db :camera-roll-photos (mapv #(get-in % [:node :image :uri]) photos))})
(fx/defn cancel-sending-image (fx/defn clear-sending-images
{:events [:chat.ui/cancel-sending-image]} {:events [:chat.ui/clear-sending-images]}
[{:keys [db]}] [{:keys [db]}]
(let [current-chat-id (:current-chat-id db)] (let [current-chat-id (:current-chat-id db)]
{:db (update-in db [:chats current-chat-id :metadata] dissoc :sending-image)})) {:db (update-in db [:chats current-chat-id :metadata] assoc :sending-image {})}))
(fx/defn cancel-sending-image
{:events [:chat.ui/cancel-sending-image]}
[cofx]
(clear-sending-images cofx))
(fx/defn image-selected (fx/defn image-selected
{:events [:chat.ui/image-selected]} {:events [:chat.ui/image-selected]}
[{:keys [db]} uri] [{:keys [db]} original uri]
(let [current-chat-id (:current-chat-id db)] (let [current-chat-id (:current-chat-id db)]
{:db (assoc-in db [:chats current-chat-id :metadata :sending-image :uri] uri)})) {:db (update-in db [:chats current-chat-id :metadata :sending-image original] merge {:uri uri})}))
(fx/defn image-unselected
{:events [:chat.ui/image-unselected]}
[{:keys [db]} original]
(let [current-chat-id (:current-chat-id db)]
{:db (update-in db [:chats current-chat-id :metadata :sending-image] dissoc original)}))
(fx/defn chat-open-image-picker (fx/defn chat-open-image-picker
{:events [:chat.ui/open-image-picker]} {:events [:chat.ui/open-image-picker]}
[_] [{:keys [db]}]
{::chat-open-image-picker nil}) (let [current-chat-id (:current-chat-id db)
images (get-in db [:chats current-chat-id :metadata :sending-image])]
(when (< (count images) max-images-batch)
{::chat-open-image-picker nil})))
(fx/defn camera-roll-pick (fx/defn camera-roll-pick
{:events [:chat.ui/camera-roll-pick]} {:events [:chat.ui/camera-roll-pick]}
[_ uri] [{:keys [db]} uri]
{::image-selected uri}) (let [current-chat-id (:current-chat-id db)
images (get-in db [:chats current-chat-id :metadata :sending-image])]
(when (and (< (count images) max-images-batch)
(not (get images uri)))
{:db (update-in db [:chats current-chat-id :metadata :sending-image] assoc uri {:uri uri})
::image-selected uri})))
(fx/defn save-image-to-gallery (fx/defn save-image-to-gallery
{:events [:chat.ui/save-image-to-gallery]} {:events [:chat.ui/save-image-to-gallery]}

View File

@ -133,14 +133,17 @@
(fx/defn send-image (fx/defn send-image
[{{:keys [current-chat-id] :as db} :db :as cofx}] [{{:keys [current-chat-id] :as db} :db :as cofx}]
(let [image-path (get-in db [:chats current-chat-id :metadata :sending-image :uri])] (let [images (get-in db [:chats current-chat-id :metadata :sending-image])]
(fx/merge cofx (fx/merge cofx
;; NOTE(Ferossgp): Ideally here and for all other types of message we should dissoc on success only
{:db (update-in db [:chats current-chat-id :metadata] dissoc :sending-image)} {:db (update-in db [:chats current-chat-id :metadata] dissoc :sending-image)}
(when-not (string/blank? image-path) (chat.message/send-messages
(chat.message/send-message {:chat-id current-chat-id (map (fn [[_ {:keys [uri]}]]
:content-type constants/content-type-image {:chat-id current-chat-id
:image-path (utils/safe-replace image-path #"file://" "") :content-type constants/content-type-image
:text (i18n/label :t/update-to-see-image)}))))) :image-path (utils/safe-replace uri #"file://" "")
:text (i18n/label :t/update-to-see-image)})
images)))))
(fx/defn send-my-status-message (fx/defn send-my-status-message
"when not empty, proceed by sending text message with public key topic" "when not empty, proceed by sending text message with public key topic"

View File

@ -233,8 +233,12 @@
(rebuild-message-list chat-id))) (rebuild-message-list chat-id)))
(fx/defn send-message (fx/defn send-message
[{:keys [db now] :as cofx} {:keys [chat-id] :as message}] [{:keys [db now] :as cofx} message]
(protocol/send-chat-message cofx message)) (protocol/send-chat-messages cofx [message]))
(fx/defn send-messages
[{:keys [db now] :as cofx} messages]
(protocol/send-chat-messages cofx messages))
(fx/defn toggle-expand-message (fx/defn toggle-expand-message
[{:keys [db]} chat-id message-id] [{:keys [db]} chat-id message-id]

View File

@ -5,31 +5,31 @@
[status-im.utils.fx :as fx] [status-im.utils.fx :as fx]
[taoensso.timbre :as log])) [taoensso.timbre :as log]))
(fx/defn send-chat-message [cofx {:keys [chat-id (defn build-message [{:keys [chat-id
text text
response-to response-to
ens-name ens-name
image-path image-path
audio-path audio-path
audio-duration-ms audio-duration-ms
message-type sticker
sticker content-type]}]
content-type] {:method (json-rpc/call-ext-method "sendChatMessage")
:as message}] :params [{:chatId chat-id
{::json-rpc/call [{:method (json-rpc/call-ext-method :text text
"sendChatMessage") :responseTo response-to
:params [{:chatId chat-id :ensName ens-name
:text text :imagePath image-path
:responseTo response-to :audioPath audio-path
:ensName ens-name :audioDurationMs audio-duration-ms
:imagePath image-path :sticker sticker
:audioPath audio-path :contentType content-type}]
:audioDurationMs audio-duration-ms :on-success
:sticker sticker #(re-frame/dispatch [:transport/message-sent % 1])
:contentType content-type}] :on-failure #(log/error "failed to send a message" %)})
:on-success
#(re-frame/dispatch [:transport/message-sent % 1]) (fx/defn send-chat-messages [cofx messages]
:on-failure #(log/error "failed to send a message" %)}]}) {::json-rpc/call (mapv build-message messages)})
(fx/defn send-reaction [cofx {:keys [message-id chat-id emoji-id]}] (fx/defn send-reaction [cofx {:keys [message-id chat-id emoji-id]}]
{::json-rpc/call [{:method (json-rpc/call-ext-method {::json-rpc/call [{:method (json-rpc/call-ext-method

View File

@ -6,13 +6,14 @@
{:read-external-storage (cond {:read-external-storage (cond
platform/android? (.-READ_EXTERNAL_STORAGE (.-ANDROID PERMISSIONS))) platform/android? (.-READ_EXTERNAL_STORAGE (.-ANDROID PERMISSIONS)))
:write-external-storage (cond :write-external-storage (cond
platform/android? (.-WRITE_EXTERNAL_STORAGE (.-ANDROID PERMISSIONS))) platform/android? (.-WRITE_EXTERNAL_STORAGE (.-ANDROID PERMISSIONS))
platform/ios? (.-PHOTO_LIBRARY (.-IOS PERMISSIONS)))
:camera (cond :camera (cond
platform/android? (.-CAMERA (.-ANDROID PERMISSIONS)) platform/android? (.-CAMERA (.-ANDROID PERMISSIONS))
platform/ios? (.-CAMERA (.-IOS PERMISSIONS))) platform/ios? (.-CAMERA (.-IOS PERMISSIONS)))
:record-audio (cond :record-audio (cond
platform/android? (.-RECORD_AUDIO (.-ANDROID PERMISSIONS)) platform/android? (.-RECORD_AUDIO (.-ANDROID PERMISSIONS))
platform/ios? (.-MICROPHONE (.-IOS PERMISSIONS)))}) platform/ios? (.-MICROPHONE (.-IOS PERMISSIONS)))})
(defn all-granted? [permissions] (defn all-granted? [permissions]
(let [permission-vals (distinct (vals permissions))] (let [permission-vals (distinct (vals permissions))]

View File

@ -192,9 +192,9 @@
(defn show-image-picker (defn show-image-picker
([images-fn] ([images-fn]
(show-image-picker images-fn nil)) (show-image-picker images-fn nil))
([images-fn media-type] ([images-fn {:keys [multiple media-type]}]
(-> ^js image-picker (-> ^js image-picker
(.openPicker (clj->js {:multiple false :mediaType (or media-type "any")})) (.openPicker (clj->js {:multiple multiple :mediaType (or media-type "any")}))
(.then images-fn) (.then images-fn)
(.catch show-access-error)))) (.catch show-access-error))))

View File

@ -287,7 +287,7 @@
{:style (styles/input-container)} {:style (styles/input-container)}
(when reply (when reply
[reply/reply-message reply]) [reply/reply-message reply])
(when sending-image (when (seq sending-image)
[reply/send-image sending-image]) [reply/send-image sending-image])
[rn/view {:style (styles/input-row)} [rn/view {:style (styles/input-row)}
[text-input props] [text-input props]

View File

@ -71,13 +71,17 @@
[icons/icon :main-icons/close-circle {:container-style (styles/close-button) [icons/icon :main-icons/close-circle {:container-style (styles/close-button)
:color (:icon-01 @colors/theme)}]]]])) :color (:icon-01 @colors/theme)}]]]]))
(defn send-image [{:keys [uri]}] (defn send-image [images]
[rn/view {:style (styles/reply-container true)} [rn/view {:style (styles/reply-container true)}
[rn/view {:style (styles/reply-content)} [rn/scroll-view {:horizontal true
[rn/image {:source {:uri uri} :style (styles/reply-content)}
:style {:width 56 (for [{:keys [uri]} (vals images)]
:height 56 ^{:key uri}
:border-radius 4}}]] [rn/image {:source {:uri uri}
:style {:width 56
:height 56
:border-radius 4
:margin-right 4}}])]
[rn/view [rn/view
[pressable/pressable {:on-press #(re-frame/dispatch [:chat.ui/cancel-sending-image]) [pressable/pressable {:on-press #(re-frame/dispatch [:chat.ui/cancel-sending-image])
:accessibility-label :cancel-send-image} :accessibility-label :cancel-send-image}

View File

@ -2,13 +2,24 @@
(:require-macros [status-im.utils.views :refer [defview letsubs]]) (:require-macros [status-im.utils.views :refer [defview letsubs]])
(:require [status-im.ui.components.react :as react] (:require [status-im.ui.components.react :as react]
[status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.components.icons.vector-icons :as icons]
[status-im.ui.components.permissions :as permissions]
[reagent.core :as reagent] [reagent.core :as reagent]
[quo.components.animated.pressable :as pressable] [quo.components.animated.pressable :as pressable]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.ui.components.colors :as colors])) [quo.design-system.colors :as colors]
[status-im.chat.models.images :as images]
[quo.core :as quo]))
(defn take-picture [] (defn take-picture []
(react/show-image-picker-camera #(re-frame/dispatch [:chat.ui/image-captured (.-path %)]) {})) (permissions/request-permissions
{:permissions [:camera]
:on-allowed (fn []
(react/show-image-picker-camera #(re-frame/dispatch [:chat.ui/image-captured (.-path %)]) {}))}))
(defn show-image-picker []
(permissions/request-permissions
{:permissions [:read-external-storage :write-external-storage]
:on-allowed #(re-frame/dispatch [:chat.ui/open-image-picker])}))
(defn buttons [] (defn buttons []
[react/view [react/view
@ -18,26 +29,48 @@
[react/view {:style {:padding 10}} [react/view {:style {:padding 10}}
[icons/icon :main-icons/camera]]] [icons/icon :main-icons/camera]]]
[react/view {:style {:padding-top 8}} [react/view {:style {:padding-top 8}}
[pressable/pressable {:on-press #(re-frame/dispatch [:chat.ui/open-image-picker]) [pressable/pressable {:on-press show-image-picker
:accessibility-label :open-gallery :accessibility-label :open-gallery
:type :scale} :type :scale}
[react/view {:style {:padding 10}} [react/view {:style {:padding 10}}
[icons/icon :main-icons/gallery]]]]]) [icons/icon :main-icons/gallery]]]]])
(defn image-preview [uri first? panel-height] (defn image-preview [uri all-selected first? panel-height]
(let [wh (/ (- panel-height 8) 2)] (let [wh (/ (- panel-height 8) 2)
[react/touchable-highlight {:on-press #(re-frame/dispatch [:chat.ui/camera-roll-pick uri])} selected (get all-selected uri)
[react/image {:style (merge {:width wh max-selected (>= (count all-selected) images/max-images-batch)]
:height wh [react/touchable-highlight {:on-press #(if selected
:background-color :black (re-frame/dispatch [:chat.ui/image-unselected uri])
:resize-mode :cover (re-frame/dispatch [:chat.ui/camera-roll-pick uri]))
:border-radius 4} :disabled (and max-selected (not selected))}
(when first? [react/view {:style (merge {:width wh
{:margin-bottom 8})) :height wh
:source {:uri uri}}]])) :border-radius 4
:overflow :hidden}
(when (and (not selected) max-selected)
{:opacity 0.5})
(when first?
{:margin-bottom 4}))}
[react/image {:style (merge {:width wh
:height wh
:background-color :black
:resize-mode :cover
:border-radius 4})
:source {:uri uri}}]
(when selected
[react/view {:style {:position :absolute
:top 0
:bottom 0
:left 0
:right 0
:padding 10
:background-color (:highlight @colors/theme)
:align-items :flex-end}}
[quo/radio {:value true}]])]]))
(defview photos [] (defview photos []
(letsubs [camera-roll-photos [:camera-roll-photos] (letsubs [camera-roll-photos [:camera-roll-photos]
selected [:chats/sending-image]
panel-height (reagent/atom nil)] panel-height (reagent/atom nil)]
[react/view {:style {:flex 1 [react/view {:style {:flex 1
:flex-direction :row} :flex-direction :row}
@ -45,16 +78,18 @@
(let [height @panel-height] (let [height @panel-height]
(for [[first-img second-img] (partition 2 camera-roll-photos)] (for [[first-img second-img] (partition 2 camera-roll-photos)]
^{:key (str "image" first-img)} ^{:key (str "image" first-img)}
[react/view {:margin-left 8} [react/view {:margin-left 4}
(when first-img (when first-img
[image-preview first-img true height]) [image-preview first-img selected true height])
(when second-img (when second-img
[image-preview second-img false height])]))])) [image-preview second-img selected false height])]))]))
(defview image-view [] (defview image-view []
{:component-did-mount (fn [] {:component-did-mount (fn []
(re-frame/dispatch [:chat.ui/camera-roll-get-photos 20]))} (permissions/request-permissions
[react/animated-view {:style {:background-color colors/white {:permissions [:read-external-storage :write-external-storage]
:on-allowed #(re-frame/dispatch [:chat.ui/camera-roll-get-photos 20])}))}
[react/animated-view {:style {:background-color (:ui-background @colors/theme)
:flex 1}} :flex 1}}
[react/scroll-view {:horizontal true :style {:flex 1}} [react/scroll-view {:horizontal true :style {:flex 1}}
[react/view {:flex 1 :flex-direction :row :margin-horizontal 4} [react/view {:flex 1 :flex-direction :row :margin-horizontal 4}

View File

@ -218,19 +218,29 @@
[react/view (style/delivery-status outgoing) [react/view (style/delivery-status outgoing)
[message-delivery-status message]]]) [message-delivery-status message]]])
(defn message-content-image [{:keys [content outgoing]}] (defn message-content-image [{:keys [content outgoing] :as message} {:keys [on-long-press]}]
(let [dimensions (reagent/atom [260 260]) (let [dimensions (reagent/atom [260 260])
uri (:image content)] uri (:image content)]
(react/image-get-size (react/image-get-size
uri uri
(fn [width height] (fn [width height]
(let [k (/ (max width height) 260)] (reset! dimensions [width height])))
(reset! dimensions [(/ width k) (/ height k)]))))
(fn [] (fn []
[react/view {:style (style/image-content outgoing)} (let [k (/ (max (first @dimensions) (second @dimensions)) 260)
[react/image {:style {:width (first @dimensions) :height (last @dimensions)} style-opts {:outgoing outgoing
:resize-mode :contain :width (/ (first @dimensions) k)
:source {:uri uri}}]]))) :height (/ (second @dimensions) k)}]
[react/touchable-highlight {:on-press (fn []
(when (:image content)
(re-frame/dispatch [:navigate-to :image-preview message]))
(react/dismiss-keyboard!))
:on-long-press on-long-press}
[react/view {:style (style/image-message style-opts)}
[react/image {:style {:width (/ (first @dimensions) k)
:height (/ (second @dimensions) k)}
:resize-mode :contain
:source {:uri uri}}]
[react/view {:style (style/image-message-border style-opts)}]]]))))
(defmulti ->message :content-type) (defmulti ->message :content-type)
@ -372,18 +382,13 @@
(defmethod ->message constants/content-type-image [{:keys [content] :as message} {:keys [on-long-press modal] (defmethod ->message constants/content-type-image [{:keys [content] :as message} {:keys [on-long-press modal]
:as reaction-picker}] :as reaction-picker}]
[message-content-wrapper message [message-content-wrapper message
[react/touchable-highlight (when-not modal [message-content-image message {:modal modal
{:on-press (fn [_] :on-long-press (fn []
(when (:image content) (on-long-press
(re-frame/dispatch [:navigate-to :image-preview message])) [{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
(react/dismiss-keyboard!)) :label (i18n/label :t/message-reply)}
:on-long-press (fn [] {:on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)])
(on-long-press :label (i18n/label :t/save)}]))}]
[{:on-press #(re-frame/dispatch [:chat.ui/reply-to-message message])
:label (i18n/label :t/message-reply)}
{:on-press #(re-frame/dispatch [:chat.ui/save-image-to-gallery (:image content)])
:label (i18n/label :t/save)}]))})
[message-content-image message]]
reaction-picker]) reaction-picker])
(defmethod ->message constants/content-type-audio [message {:keys [on-long-press modal] (defmethod ->message constants/content-type-audio [message {:keys [on-long-press modal]

View File

@ -301,9 +301,21 @@
(outgoing-blockquote-text-style) (outgoing-blockquote-text-style)
(default-blockquote-text-style))) (default-blockquote-text-style)))
(defn image-content [outgoing] (defn image-message
{:overflow :hidden [{:keys [outgoing width height]}]
{:overflow "hidden"
:border-top-left-radius 16 :border-top-left-radius 16
:border-top-right-radius 16 :border-top-right-radius 16
:border-bottom-left-radius (if outgoing 16 4) :border-bottom-left-radius (if outgoing 16 4)
:border-bottom-right-radius (if outgoing 4 16)}) :border-bottom-right-radius (if outgoing 4 16)
:width width
:height height})
(defn image-message-border [opts]
(merge (image-message opts)
{:position :absolute
:top 0
:left 0
:background-color "transparent"
:border-width 1
:border-color colors/black-transparent}))