parent
6ab6e3f041
commit
51f6a173df
|
@ -165,12 +165,15 @@
|
|||
(utils/show-popup (i18n/label :t/error)
|
||||
(i18n/label :t/photos-access-error))))
|
||||
|
||||
(defn show-image-picker [images-fn]
|
||||
(defn show-image-picker
|
||||
([images-fn]
|
||||
(show-image-picker images-fn nil))
|
||||
([images-fn media-type]
|
||||
(let [image-picker (.-default image-picker-class)]
|
||||
(-> image-picker
|
||||
(.openPicker (clj->js {:multiple false}))
|
||||
(.openPicker (clj->js {:multiple false :mediaType (or media-type "any")}))
|
||||
(.then images-fn)
|
||||
(.catch show-access-error))))
|
||||
(.catch show-access-error)))))
|
||||
|
||||
;; Clipboard
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
(ns status-im.extensions.camera
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.utils.fx :as fx]
|
||||
[status-im.extensions.views :as ext-views]
|
||||
[status-im.ui.components.list-selection :as list-selection]
|
||||
[status-im.qr-scanner.core :as qr-scanner]
|
||||
[status-im.ui.screens.navigation :as navigation]
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
;; Common
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:extensions/camera-cancel
|
||||
(fn [_ [_ _ {{:keys [on-failure]} :data}]]
|
||||
(when on-failure
|
||||
(re-frame/dispatch (on-failure {:result "user cancelled"})))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:extensions/camera-denied
|
||||
(fn [_ [_ {{:keys [on-failure]} :data}]]
|
||||
(when on-failure
|
||||
(re-frame/dispatch (on-failure {:result "user denied access to camera"})))))
|
||||
|
||||
;; Photo taker\picker
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:extensions/camera-error
|
||||
(fn [cofx [_ error {:keys [on-failure]}]]
|
||||
(when on-failure
|
||||
{:dispatch (on-failure {:result error})})))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:extensions/camera-picture-taken
|
||||
(fn [cofx [_ base64 {{:keys [on-success]} :data}]]
|
||||
(fx/merge cofx
|
||||
{:dispatch (on-success {:result base64})}
|
||||
(navigation/navigate-back))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:extensions/camera-picture
|
||||
(fn [_ [_ _ params]]
|
||||
(list-selection/show (ext-views/pick-or-take-picture-list-selection {:data params}))
|
||||
{}))
|
||||
|
||||
;; QR code scanner
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:extensions/camera-qr-code-scanned
|
||||
(fn [cofx [_ _ qr-code {{:keys [on-success]} :data}]]
|
||||
(fx/merge cofx
|
||||
{:dispatch (on-success {:result qr-code})}
|
||||
(navigation/navigate-back))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:extensions/camera-qr-code
|
||||
(fn [{:keys [db] :as cofx} [_ _ {:keys [on-success on-failure]}]]
|
||||
(qr-scanner/scan-qr-code cofx {:modal? false
|
||||
:deny-handler :extensions/camera-denied}
|
||||
{:handler :extensions/camera-qr-code-scanned
|
||||
:cancel-handler :extensions/camera-cancel
|
||||
:data {:on-success on-success
|
||||
:on-failure on-failure}})))
|
|
@ -20,6 +20,7 @@
|
|||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.fx :as fx]
|
||||
status-im.extensions.ethereum
|
||||
status-im.extensions.camera
|
||||
[status-im.utils.ethereum.tokens :as tokens]
|
||||
[status-im.utils.ethereum.core :as ethereum]
|
||||
[status-im.chat.commands.sending :as commands-sending]))
|
||||
|
@ -464,6 +465,16 @@
|
|||
:arguments {:values :vector
|
||||
:operation {:one-of #{:plus :minus :times :divide}}
|
||||
:on-result :event}}
|
||||
'camera/picture
|
||||
{:permissions [:read]
|
||||
:value :extensions/camera-picture
|
||||
:arguments {:on-success :event
|
||||
:on-failure? :event}}
|
||||
'camera/qr-code
|
||||
{:permissions [:read]
|
||||
:value :extensions/camera-qr-code
|
||||
:arguments {:on-success :event
|
||||
:on-failure? :event}}
|
||||
'schedule/start
|
||||
{:permissions [:read]
|
||||
:value :extensions/schedule-start
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
(ns status-im.extensions.views
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[reagent.core :as reagent]
|
||||
[status-im.ui.components.camera :as camera]
|
||||
[status-im.ui.components.react :as react]
|
||||
[status-im.ui.components.status-bar.view :as status-bar]
|
||||
[status-im.ui.components.toolbar.view :as toolbar]
|
||||
[status-im.ui.components.toolbar.actions :as actions]
|
||||
[status-im.ui.components.icons.vector-icons :as icons]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.ui.screens.profile.photo-capture.styles :as styles]
|
||||
[status-im.utils.image-processing :as image-processing]))
|
||||
|
||||
;; ensure photo taken or picked are not too big
|
||||
(def max-width 1024)
|
||||
(def max-height 1024)
|
||||
|
||||
(defn- process-image-and-finish [path context]
|
||||
(let [on-success (fn [base64]
|
||||
(re-frame/dispatch [:extensions/camera-picture-taken base64 context]))
|
||||
on-error (fn [type error]
|
||||
(re-frame/dispatch [:extensions/camera-error error context]))]
|
||||
(image-processing/img->base64 path on-success on-error max-width max-height)))
|
||||
|
||||
(defview take-picture []
|
||||
(letsubs [context [:get-screen-params]
|
||||
camera-ref (reagent/atom false)]
|
||||
[react/view styles/container
|
||||
[status-bar/status-bar]
|
||||
[toolbar/toolbar nil
|
||||
[toolbar/nav-button (actions/back
|
||||
#(do
|
||||
(re-frame/dispatch [:extensions/camera-cancel context])
|
||||
(re-frame/dispatch [:navigate-back])))]
|
||||
[toolbar/content-title (i18n/label :t/extensions-camera-send-picture)]]
|
||||
[camera/camera {:style {:flex 1}
|
||||
:aspect (:fill camera/aspects)
|
||||
:captureTarget (:disk camera/capture-targets)
|
||||
:ref #(reset! camera-ref %)}]
|
||||
[react/view styles/button-container
|
||||
[react/view styles/button
|
||||
[react/touchable-highlight {:on-press (fn []
|
||||
(let [camera @camera-ref]
|
||||
(-> (.capture camera)
|
||||
(.then #(process-image-and-finish (.-path %) context))
|
||||
(.catch #(re-frame/dispatch [:extensions/camera-error % context])))))}
|
||||
[react/view
|
||||
[icons/icon :main-icons/camera {:color :white}]]]]]]))
|
||||
|
||||
;TODO image picker doesn't notify when the user cancel image selection
|
||||
; in this case we cannot notify cancel to extension
|
||||
(defn- open-image-picker [context]
|
||||
(react/show-image-picker
|
||||
(fn [image]
|
||||
(let [path (get (js->clj image) "path")]
|
||||
(process-image-and-finish path context)))
|
||||
"photo"))
|
||||
|
||||
(defn pick-or-take-picture-list-selection [context]
|
||||
{:title (i18n/label :t/extensions-camera-send-picture)
|
||||
:options [{:label (i18n/label :t/image-source-gallery)
|
||||
:action #(open-image-picker context)}
|
||||
{:label (i18n/label :t/image-source-make-photo)
|
||||
:action (fn []
|
||||
(re-frame/dispatch [:request-permissions {:permissions [:camera :write-external-storage]
|
||||
:on-allowed #(re-frame/dispatch [:navigate-to :take-picture context])
|
||||
:on-denied #(re-frame/dispatch [:extensions/camera-denied context])}]))}]
|
||||
:on-cancel #(re-frame/dispatch [:extensions/camera-cancel context])})
|
|
@ -5,16 +5,18 @@
|
|||
[status-im.utils.fx :as fx]))
|
||||
|
||||
(fx/defn scan-qr-code
|
||||
[{:keys [db]} {:keys [modal?] :as identifier} qr-codes]
|
||||
[{:keys [db]} {:keys [modal? deny-handler] :as identifier} qr-codes]
|
||||
{:db (assoc db :qr-codes qr-codes)
|
||||
:request-permissions-fx {:permissions [:camera]
|
||||
:on-allowed #(re-frame/dispatch [(if modal? :navigate-to-modal :navigate-to)
|
||||
:qr-scanner {:current-qr-context identifier}])
|
||||
:on-denied (fn []
|
||||
:on-denied (if (nil? deny-handler)
|
||||
(fn []
|
||||
(utils/set-timeout
|
||||
#(utils/show-popup (i18n/label :t/error)
|
||||
(i18n/label :t/camera-access-error))
|
||||
50))}})
|
||||
50))
|
||||
#(re-frame/dispatch [deny-handler qr-codes]))}})
|
||||
|
||||
(fx/defn set-qr-code
|
||||
[{:keys [db]} context data]
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
[status-im.utils.core :as utils]
|
||||
[status-im.react-native.js-dependencies :as js-dependencies]))
|
||||
|
||||
(defn- callback [options]
|
||||
(defn- callback [options on-cancel]
|
||||
(fn [index]
|
||||
(when (< index (count options))
|
||||
(if (< index (count options))
|
||||
(when-let [handler (:action (nth options index))]
|
||||
(handler)))))
|
||||
(handler))
|
||||
(when on-cancel
|
||||
(on-cancel)))))
|
||||
|
||||
(defn- prepare-options [title message options]
|
||||
(let [destructive-opt-index (utils/first-index :destructive? options)] ;; TODO Can only be a single destructive?
|
||||
|
@ -18,7 +20,7 @@
|
|||
(when title {:title title})
|
||||
(when message {:message message})))))
|
||||
|
||||
(defn show [{:keys [title message options]}]
|
||||
(defn show [{:keys [title message options on-cancel]}]
|
||||
(.showActionSheetWithOptions (.-ActionSheetIOS js-dependencies/react-native)
|
||||
(prepare-options title message options)
|
||||
(callback options)))
|
||||
(callback options on-cancel)))
|
||||
|
|
|
@ -3,17 +3,21 @@
|
|||
|
||||
(def dialogs (.-default rn-dependencies/dialogs))
|
||||
|
||||
(defn show [{:keys [title options cancel-text]}]
|
||||
(defn show [{:keys [title options cancel-text on-cancel]}]
|
||||
(.. dialogs
|
||||
(showPicker title nil (clj->js {:items (mapv #(select-keys % [:label])
|
||||
options)
|
||||
:negativeText cancel-text
|
||||
:positiveText nil}))
|
||||
(then (fn [selected]
|
||||
(when-let [label (get-in (js->clj selected :keywordize-keys true)
|
||||
(let [result (js->clj selected :keywordize-keys true)]
|
||||
(if (not= (get result :action) "actionSelect")
|
||||
(when on-cancel
|
||||
(on-cancel))
|
||||
(when-let [label (get-in result
|
||||
[:selectedItem :label])]
|
||||
(when-let [action (->> options
|
||||
(filter #(= label (:label %)))
|
||||
first
|
||||
:action)]
|
||||
(action)))))))
|
||||
(action)))))))))
|
|
@ -5,7 +5,6 @@
|
|||
[status-im.ui.screens.profile.models :as profile.models]
|
||||
[status-im.ui.screens.profile.navigation]
|
||||
[status-im.ui.components.list-selection :as list-selection]
|
||||
[status-im.ui.screens.profile.models :as profile.models]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.utils.identicon :as identicon]
|
||||
[status-im.utils.universal-links.core :as universal-links]))
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
(re-frame/dispatch [callback-event base64]))
|
||||
on-error (fn [type error]
|
||||
(.log js/console type error))]
|
||||
(image-processing/img->base64 path on-success on-error)))))
|
||||
(image-processing/img->base64 path on-success on-error 150 150)))
|
||||
"photo"))
|
||||
|
||||
(defn send-transaction [chat-id {:keys [db] :as cofx}]
|
||||
(let [send-command (get-in db [:id->command ["send" #{:personal-chats}]])]
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
(re-frame/dispatch [:navigate-back]))
|
||||
on-error (fn [type error]
|
||||
(log/debug type error))]
|
||||
(image-processing/img->base64 path on-success on-error)))
|
||||
(image-processing/img->base64 path on-success on-error 150 150)))
|
||||
|
||||
(defn profile-photo-capture []
|
||||
(let [camera-ref (reagent/atom nil)]
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
[status-im.ui.screens.profile.contact.views :as profile.contact]
|
||||
[status-im.ui.screens.profile.group-chat.views :as profile.group-chat]
|
||||
[status-im.ui.screens.profile.photo-capture.views :refer [profile-photo-capture]]
|
||||
[status-im.extensions.views :refer [take-picture]]
|
||||
[status-im.ui.screens.wallet.main.views :as wallet.main]
|
||||
[status-im.ui.screens.wallet.collectibles.views :refer [collectibles-list]]
|
||||
[status-im.ui.screens.wallet.send.views :refer [send-transaction send-transaction-modal sign-message-modal]]
|
||||
|
@ -169,6 +170,7 @@
|
|||
:new add-new
|
||||
:new-chat new-chat
|
||||
:qr-scanner qr-scanner
|
||||
:take-picture take-picture
|
||||
:new-group new-group
|
||||
:add-participants-toggle-list add-participants-toggle-list
|
||||
:contact-toggle-list contact-toggle-list
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
(on-error :base64 error))]
|
||||
(read-file path "base64" on-success on-error)))
|
||||
|
||||
(defn img->base64 [path on-success on-error]
|
||||
(defn img->base64 [path on-success on-error max-width max-height]
|
||||
(let [on-resized (fn [image]
|
||||
(let [path (object/get image "path")]
|
||||
(log/debug "Resized: " path)
|
||||
|
@ -26,4 +26,4 @@
|
|||
on-error (fn [error]
|
||||
(log/debug "Resized error: " error)
|
||||
(on-error :resize error))]
|
||||
(resize path 150 150 on-resized on-error)))
|
||||
(resize path max-width max-height on-resized on-error)))
|
||||
|
|
|
@ -920,6 +920,7 @@
|
|||
"mailserver-error-content": "The mailserver you selected couldn't be reached.",
|
||||
"dapps-permissions": "Dapps permissions",
|
||||
"revoke-access": "Revoke access",
|
||||
"extensions-camera-send-picture": "Send picture",
|
||||
"mobile-syncing-sheet-title": "Sync using the mobile network",
|
||||
"mobile-syncing-sheet-details": "Status tends to use a lot of data when syncing chats. You can choose not to sync when on mobile network",
|
||||
"mobile-network-continue-syncing": "Continue syncing",
|
||||
|
|
Loading…
Reference in New Issue