diff --git a/.env b/.env index 53c6aeaf6f..5b8f7d0b8d 100644 --- a/.env +++ b/.env @@ -22,7 +22,6 @@ KEYCARD_TEST_MENU=0 QR_READ_TEST_MENU=1 ENABLE_ROOT_ALERT=1 ENABLE_QUO_PREVIEW=1 -MAX_IMAGES_BATCH=5 APN_TOPIC=im.status.ethereum.pr COMMUNITIES_ENABLED=1 DATABASE_MANAGEMENT_ENABLED=1 diff --git a/.env.e2e b/.env.e2e index 6a0fa064dd..fb3b958e39 100644 --- a/.env.e2e +++ b/.env.e2e @@ -21,7 +21,6 @@ COMMANDS_ENABLED=1 KEYCARD_TEST_MENU=1 QR_READ_TEST_MENU=1 ENABLE_ROOT_ALERT=0 -MAX_IMAGES_BATCH=5 APN_TOPIC=im.status.ethereum.pr VERIFY_TRANSACTION_CHAIN_ID=5 VERIFY_ENS_CHAIN_ID=5 diff --git a/.env.jenkins b/.env.jenkins index ec06cda656..37663ce1e8 100644 --- a/.env.jenkins +++ b/.env.jenkins @@ -26,7 +26,6 @@ VERIFY_TRANSACTION_CHAIN_ID=5 VERIFY_ENS_CHAIN_ID=5 TEST_STATEOFUS=1 BLANK_PREVIEW=0 -MAX_IMAGES_BATCH=5 DATABASE_MANAGEMENT_ENABLED=1 COMMUNITIES_ENABLED=1 COMMUNITIES_MANAGEMENT_ENABLED=1 diff --git a/.env.nightly b/.env.nightly index a81b1fa797..15e3adcb35 100644 --- a/.env.nightly +++ b/.env.nightly @@ -17,7 +17,6 @@ RPC_NETWORKS_ONLY=0 PARTITIONED_TOPIC=0 CONTRACT_NODES=1 ENABLE_ROOT_ALERT=1 -MAX_IMAGES_BATCH=5 BLANK_PREVIEW=0 COMMUNITIES_ENABLED=1 DATABASE_MANAGEMENT_ENABLED=1 diff --git a/resources/images/icons2/24x24/flash-camera@2x.png b/resources/images/icons2/24x24/flash-camera@2x.png new file mode 100644 index 0000000000..c5c139b46a Binary files /dev/null and b/resources/images/icons2/24x24/flash-camera@2x.png differ diff --git a/resources/images/icons2/24x24/flash-camera@3x.png b/resources/images/icons2/24x24/flash-camera@3x.png new file mode 100644 index 0000000000..6c6ddd88ea Binary files /dev/null and b/resources/images/icons2/24x24/flash-camera@3x.png differ diff --git a/resources/images/icons2/48x48/rotate-camera@2x.png b/resources/images/icons2/48x48/rotate-camera@2x.png new file mode 100644 index 0000000000..6c2b4ee0f4 Binary files /dev/null and b/resources/images/icons2/48x48/rotate-camera@2x.png differ diff --git a/resources/images/icons2/48x48/rotate-camera@3x.png b/resources/images/icons2/48x48/rotate-camera@3x.png new file mode 100644 index 0000000000..0916067d47 Binary files /dev/null and b/resources/images/icons2/48x48/rotate-camera@3x.png differ diff --git a/src/quo2/components/icons/icons.clj b/src/quo2/components/icons/icons.clj index c746c92f99..f0b8599fbb 100644 --- a/src/quo2/components/icons/icons.clj +++ b/src/quo2/components/icons/icons.clj @@ -35,4 +35,5 @@ (get-icons 16) (get-icons 20) (get-icons 24) - (get-icons 32))) + (get-icons 32) + (get-icons 48))) diff --git a/src/quo2/foundations/colors.cljs b/src/quo2/foundations/colors.cljs index 916406ce2c..7fdbf3dab0 100644 --- a/src/quo2/foundations/colors.cljs +++ b/src/quo2/foundations/colors.cljs @@ -136,6 +136,8 @@ ;;Solid (def black "#000000") (def black-opa-0 (alpha black 0)) +(def black-opa-30 (alpha black 0.3)) +(def black-opa-60 (alpha black 0.6)) (def onboarding-header-black "#000716") ;;;;Primary @@ -180,6 +182,8 @@ (def danger-50 "#E95460") (def danger-60 "#BA434D") +(def system-yellow "#FFD60A") + ;;50 with transparency (def danger-50-opa-5 (alpha danger-50 0.05)) (def danger-50-opa-10 (alpha danger-50 0.1)) diff --git a/src/react_native/camera_kit.cljs b/src/react_native/camera_kit.cljs index 0963861adb..187fa11e7d 100644 --- a/src/react_native/camera_kit.cljs +++ b/src/react_native/camera_kit.cljs @@ -1,8 +1,16 @@ (ns react-native.camera-kit (:require ["react-native-camera-kit" :refer (Camera CameraType)] - [reagent.core :as reagent])) + [reagent.core :as reagent] + [oops.core :as oops] + [taoensso.timbre :as log])) (def camera (reagent/adapt-react-class Camera)) (def camera-type-front (.-Front CameraType)) (def camera-type-back (.-Back CameraType)) + +(defn capture + [^js camera-ref on-success] + (-> (.capture camera-ref) + (.then #(on-success (oops/oget % :uri))) + (.catch #(log/warn "couldn't capture photo" {:error %})))) diff --git a/src/status_im/chat/models/images.cljs b/src/status_im/chat/models/images.cljs index ec9e45ed90..31a5161de5 100644 --- a/src/status_im/chat/models/images.cljs +++ b/src/status_im/chat/models/images.cljs @@ -4,8 +4,8 @@ [react-native.share :as share] [react-native.cameraroll :as cameraroll] [status-im.ui.components.react :as react] - [status-im2.config :as config] [react-native.fs :as fs] + [status-im2.constants :as constants] [utils.re-frame :as rf] [status-im.utils.platform :as platform] [taoensso.timbre :as log])) @@ -48,7 +48,7 @@ [{:keys [db]} chat-id uri] (let [current-chat-id (or chat-id (:current-chat-id db)) images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])] - (when (and (< (count images) config/max-images-batch) + (when (and (< (count images) constants/max-album-photos) (not (get images uri))) {::image-selected [uri current-chat-id]}))) @@ -68,5 +68,5 @@ [{:keys [db]} chat-id] (let [current-chat-id (or chat-id (:current-chat-id db)) images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])] - (when (< (count images) config/max-images-batch) + (when (< (count images) constants/max-album-photos) {::chat-open-image-picker-camera current-chat-id}))) diff --git a/src/status_im2/common/device_permissions.cljs b/src/status_im2/common/device_permissions.cljs new file mode 100644 index 0000000000..0df5c80b5c --- /dev/null +++ b/src/status_im2/common/device_permissions.cljs @@ -0,0 +1,18 @@ +(ns status-im2.common.device-permissions + (:require + [quo2.foundations.colors :as colors] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn camera + [on-allowed] + (rf/dispatch + [:request-permissions + {:permissions [:camera] + :on-allowed on-allowed + :on-denied #(rf/dispatch + [:toasts/upsert + {:icon :i/info + :icon-color colors/danger-50 + :override-theme :light + :text (i18n/label :t/camera-permission-denied)}])}])) diff --git a/src/status_im2/config.cljs b/src/status_im2/config.cljs index 2674e4ea64..e54787279e 100644 --- a/src/status_im2/config.cljs +++ b/src/status_im2/config.cljs @@ -26,7 +26,6 @@ (def snoopy-enabled? (enabled? (get-config :SNOOPY 0))) (def dev-build? (enabled? (get-config :DEV_BUILD 0))) (def max-message-delivery-attempts (js/parseInt (get-config :MAX_MESSAGE_DELIVERY_ATTEMPTS "6"))) -(def max-images-batch (js/parseInt (get-config :MAX_IMAGES_BATCH "1"))) ;; NOTE: only disabled in releases (def local-notifications? (enabled? (get-config :LOCAL_NOTIFICATIONS "1"))) (def blank-preview? (enabled? (get-config :BLANK_PREVIEW "1"))) diff --git a/src/status_im2/contexts/chat/camera/style.cljs b/src/status_im2/contexts/chat/camera/style.cljs new file mode 100644 index 0000000000..4e340a0209 --- /dev/null +++ b/src/status_im2/contexts/chat/camera/style.cljs @@ -0,0 +1,97 @@ +(ns status-im2.contexts.chat.camera.style + (:require [quo2.foundations.colors :as colors] + [react-native.platform :as platform] + [react-native.reanimated :as reanimated])) + +(def screen-container + {:flex 1 + :background-color colors/black}) + +(def flash-container + {:position :absolute + :top 50 + :left 25}) + +(defn camera-window + [width height top] + {:width width + :height height + :top top}) + +(def zoom-button-container + {:width 37 + :height 37 + :justify-content :center + :align-items :center}) + +(defn zoom-container + [top insets] + {:width 157 + :height 43 + :border-radius 100 + :position :absolute + :background-color colors/black-opa-60 + :align-self :center + :justify-content :space-around + :align-items :center + :flex-direction :row + :bottom (+ top (:bottom insets) (when platform/android? (:top insets)) 18)}) + +(defn zoom-button + [size] + (reanimated/apply-animations-to-style + {:width size + :height size} + {:background-color colors/black-opa-30 + :justify-content :center + :align-items :center + :border-radius 50})) + +(defn bottom-area + [top insets] + {:left 20 + :right 20 + :position :absolute + :height (+ top (when platform/android? (:top insets))) + :bottom (:bottom insets)}) + +(def photo-text + {:color colors/system-yellow + :margin-top 18 + :font-size 14 + :align-self :center}) + +(def actions-container + {:flex-direction :row + :margin-top 20 + :align-items :center + :justify-content :space-between}) + +(def outer-circle + {:width 69 + :height 69 + :background-color colors/black + :border-radius 69 + :border-width 6 + :border-color colors/white + :justify-content :center + :align-items :center}) + +(def inner-circle + {:width 53 + :height 53 + :border-radius 53 + :background-color colors/white}) + +(defn confirmation-container + [insets] + {:position :absolute + :bottom 0 + :left 0 + :right 0 + :background-color "#131313" + :height (+ 69 (:bottom insets)) + :flex-direction :row + :padding-horizontal 20 + :justify-content :space-between + :padding-top 18}) diff --git a/src/status_im2/contexts/chat/camera/view.cljs b/src/status_im2/contexts/chat/camera/view.cljs new file mode 100644 index 0000000000..68f1558db3 --- /dev/null +++ b/src/status_im2/contexts/chat/camera/view.cljs @@ -0,0 +1,101 @@ +(ns status-im2.contexts.chat.camera.view + (:require + [quo2.core :as quo] + [quo2.foundations.colors :as colors] + [react-native.camera-kit :as camera-kit] + [react-native.core :as rn] + [react-native.reanimated :as reanimated] + [react-native.safe-area :as safe-area] + [reagent.core :as reagent] + [utils.i18n :as i18n] + [status-im2.contexts.chat.camera.style :as style] + [utils.re-frame :as rf])) + +(defn- f-zoom-button + [{:keys [value current-zoom]}] + (let [selected? (= @current-zoom value) + size (reanimated/use-shared-value (if selected? 37 25))] + (rn/use-effect #(reanimated/animate size (if selected? 37 25)) [@current-zoom]) + [rn/touchable-opacity + {:on-press #(reset! current-zoom value) + :style style/zoom-button-container + :accessibility-label (str "zoom-" value)} + [reanimated/view {:style (style/zoom-button size)} + [quo/text + {:size (if selected? :paragraph-2 :label) + :weight :semi-bold + :style {:color (if selected? + colors/system-yellow + colors/white)}} + (str value (when selected? "x"))]]])) + +(defn zoom-button + [args] + [:f> f-zoom-button args]) + +(defn snap-button + [camera-ref uri] + [rn/view + {:style style/outer-circle + :accessibility-label :snap} + [rn/touchable-opacity + {:on-press (fn [] + (camera-kit/capture @camera-ref #(reset! uri %))) + :style style/inner-circle}]]) + +(defn camera-screen + [] + (let [camera-ref (atom nil) + uri (reagent/atom nil) + current-zoom (reagent/atom "1")] + (fn [] + (let [window (rn/get-window) + {:keys [width height]} window + camera-window-height (* width 1.33) + insets (safe-area/get-insets) + top (/ (- height camera-window-height (:bottom insets)) 2)] + [rn/view {:style style/screen-container} + (when-not @uri + [rn/view {:style style/flash-container} + [quo/icon :i/flash-camera + {:color colors/white + :size 24}]]) + (if @uri + [rn/image + {:style (style/camera-window width camera-window-height top) + :source {:uri @uri}}] + [camera-kit/camera + {:ref #(reset! camera-ref %) + :style (style/camera-window width camera-window-height top)}]) + (when-not @uri + [rn/view {:style (style/zoom-container top insets)} + [zoom-button {:value "0.5" :current-zoom current-zoom}] + [zoom-button {:value "1" :current-zoom current-zoom}] + [zoom-button {:value "2" :current-zoom current-zoom}] + [zoom-button {:value "3" :current-zoom current-zoom}]]) + (if @uri + [rn/view {:style (style/confirmation-container insets)} + [quo/text + {:on-press #(reset! uri nil) + :style {:font-size 17 + :color colors/white}} + (i18n/label :t/retake)] + [quo/text + {:on-press (fn [] + (rf/dispatch [:photo-selector/camera-roll-pick {:uri @uri}]) + (rf/dispatch [:navigate-back])) + :style {:font-size 17 + :color colors/white}} + (i18n/label :t/use-photo)]] + [rn/view {:style (style/bottom-area top insets)} + [quo/text {:style style/photo-text} (i18n/label :t/PHOTO)] + [rn/view {:style style/actions-container} + [quo/text + {:on-press #(rf/dispatch [:navigate-back]) + :style {:font-size 17 + :color colors/white} + :accessibility-label :cancel} + (i18n/label :t/cancel)] + [snap-button camera-ref uri] + [quo/icon :i/rotate-camera + {:size 48 :color colors/white :accessibility-label :flip-camera}]]])])))) diff --git a/src/status_im2/contexts/chat/composer/actions/view.cljs b/src/status_im2/contexts/chat/composer/actions/view.cljs index d42f32333d..c1062437d2 100644 --- a/src/status_im2/contexts/chat/composer/actions/view.cljs +++ b/src/status_im2/contexts/chat/composer/actions/view.cljs @@ -1,6 +1,7 @@ (ns status-im2.contexts.chat.composer.actions.view (:require [quo2.core :as quo] + [quo2.foundations.colors :as colors] [react-native.core :as rn] [react-native.permissions :as permissions] [react-native.platform :as platform] @@ -9,6 +10,7 @@ [status-im2.common.alert.events :as alert] [status-im2.contexts.chat.composer.constants :as comp-constants] [status-im2.contexts.chat.messages.list.view :as messages.list] + [status-im2.common.device-permissions :as device-permissions] [utils.i18n :as i18n] [utils.re-frame :as rf] [status-im2.contexts.chat.composer.actions.style :as style] @@ -148,16 +150,32 @@ 50)}])) :max-duration-ms constants/audio-max-duration-ms}]])) +(defn images-limit-toast + [] + (rf/dispatch [:toasts/upsert + {:id :random-id + :icon :info + :icon-color colors/danger-50-opa-40 + :container-style {:top (when platform/ios? 20)} + :text (i18n/label :t/only-6-images)}])) + + +(defn go-to-camera + [images-count] + (device-permissions/camera #(if (>= images-count constants/max-album-photos) + (images-limit-toast) + (rf/dispatch [:navigate-to :camera-screen])))) (defn camera-button [] - [quo/button - {:on-press #(js/alert "to be implemented") - :icon true - :type :outline - :size 32 - :style {:margin-right 12}} - :i/camera]) + (let [images-count (count (vals (rf/sub [:chats/sending-image])))] + [quo/button + {:on-press #(go-to-camera images-count) + :icon true + :type :outline + :size 32 + :style {:margin-right 12}} + :i/camera])) (defn open-photo-selector [{:keys [input-ref]} diff --git a/src/status_im2/contexts/chat/photo_selector/events.cljs b/src/status_im2/contexts/chat/photo_selector/events.cljs index 320f181e70..b6cb8b2367 100644 --- a/src/status_im2/contexts/chat/photo_selector/events.cljs +++ b/src/status_im2/contexts/chat/photo_selector/events.cljs @@ -2,9 +2,9 @@ (:require [react-native.cameraroll :as cameraroll] [clojure.string :as string] [re-frame.core :as re-frame] + [status-im2.constants :as constants] [utils.i18n :as i18n] [react-native.permissions :as permissions] - [status-im2.config :as config] [utils.re-frame :as rf] [status-im.utils.image-processing :as image-processing] [taoensso.timbre :as log] @@ -141,6 +141,6 @@ [{:keys [db]} image chat-id] (let [current-chat-id (or chat-id (:current-chat-id db)) images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])] - (when (and (< (count images) config/max-images-batch) + (when (and (< (count images) constants/max-album-photos) (not (some #(= (:uri image) (:uri %)) images))) {:camera-roll-image-selected [image current-chat-id]}))) diff --git a/src/status_im2/contexts/syncing/scan_sync_code/view.cljs b/src/status_im2/contexts/syncing/scan_sync_code/view.cljs index 85d54ca6a4..69b3683ba7 100644 --- a/src/status_im2/contexts/syncing/scan_sync_code/view.cljs +++ b/src/status_im2/contexts/syncing/scan_sync_code/view.cljs @@ -12,6 +12,7 @@ [react-native.reanimated :as reanimated] [react-native.safe-area :as safe-area] [reagent.core :as reagent] + [status-im2.common.device-permissions :as device-permissions] [status-im2.constants :as constants] [status-im2.contexts.syncing.scan-sync-code.style :as style] [status-im2.contexts.syncing.utils :as sync-utils] @@ -27,19 +28,6 @@ (defonce dismiss-animations (atom nil)) (defonce navigate-back-fn (atom nil)) -(defn request-camera-permission - [] - (rf/dispatch - [:request-permissions - {:permissions [:camera] - :on-allowed #(reset! camera-permission-granted? true) - :on-denied #(rf/dispatch - [:toasts/upsert - {:icon :i/info - :icon-color colors/danger-50 - :override-theme :light - :text (i18n/label :t/camera-permission-denied)}])}])) - (defn perform-preflight-check "Performing the check for the first time will trigger local network access permission in iOS. @@ -140,7 +128,8 @@ :button-icon :i/camera :button-label :t/enable-camera :accessibility-label :request-camera-permission - :on-press request-camera-permission})) + :on-press (fn [] + (device-permissions/camera #(reset! camera-permission-granted? true)))})) (defn- camera-and-local-network-access-permission-view [] diff --git a/src/status_im2/navigation/options.cljs b/src/status_im2/navigation/options.cljs index e176dcaa29..07a0419ac5 100644 --- a/src/status_im2/navigation/options.cljs +++ b/src/status_im2/navigation/options.cljs @@ -114,7 +114,7 @@ :animate true :drawBehind true :translucent true} - :navigationBar {:backgroundColor colors/black} + :navigationBar {:backgroundColor colors/neutral-100} :layout {:componentBackgroundColor :transparent :backgroundColor :transparent ;; issue: https://github.com/wix/react-native-navigation/issues/7726 @@ -129,6 +129,9 @@ :decelerate :factor 1.5}}]}}}) +(def camera-screen + {:navigationBar {:backgroundColor colors/black}}) + (defn merge-top-bar [root-options options] (let [options (:topBar options)] diff --git a/src/status_im2/navigation/screens.cljs b/src/status_im2/navigation/screens.cljs index 942ed1a9cb..c0d2ad1be1 100644 --- a/src/status_im2/navigation/screens.cljs +++ b/src/status_im2/navigation/screens.cljs @@ -6,6 +6,7 @@ [status-im2.contexts.chat.lightbox.view :as lightbox] [status-im2.contexts.chat.messages.view :as chat] [status-im2.contexts.chat.photo-selector.view :as photo-selector] + [status-im2.contexts.chat.camera.view :as camera-screen] [status-im2.contexts.communities.discover.view :as communities.discover] [status-im2.contexts.communities.overview.view :as communities.overview] [status-im2.contexts.onboarding.intro.view :as intro] @@ -107,6 +108,10 @@ :options {:sheet? true} :component photo-selector/photo-selector} + {:name :camera-screen + :options options/camera-screen + :component camera-screen/camera-screen} + {:name :new-contact :options {:sheet? true} :component add-new-contact/new-contact} diff --git a/translations/en.json b/translations/en.json index 9869aff834..9fca77bf26 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2245,5 +2245,8 @@ "chat-unmuted-successfully": "Chat unmuted successfully! ", "channel-unmuted-successfully": "Channel unmuted successfully! ", "photo-saved": "Photo saved to your device", - "community-unmuted": "Community unmuted" + "community-unmuted": "Community unmuted", + "retake": "Retake", + "use-photo": "Use Photo", + "PHOTO": "PHOTO" }