diff --git a/src/quo2/components/dividers/divider_label.cljs b/src/quo2/components/dividers/divider_label.cljs index ea1f3cc7bb..fa774ff6c3 100644 --- a/src/quo2/components/dividers/divider_label.cljs +++ b/src/quo2/components/dividers/divider_label.cljs @@ -14,7 +14,7 @@ counter-value -> number increase-padding-top? -> boolean blur? -> boolean" - [{:keys [label chevron-position counter-value increase-padding-top? blur?]}] + [{:keys [label chevron-position counter-value increase-padding-top? blur? container-style]}] (let [dark? (colors/dark?) border-and-counter-bg-color (if dark? (if blur? colors/white-opa-5 colors/neutral-70) @@ -25,12 +25,13 @@ [rn/view {:accessible true :accessibility-label :divider-label - :style {:border-top-width 1 - :border-top-color border-and-counter-bg-color - :padding-top padding-top - :padding-horizontal 16 - :align-items :center - :flex-direction :row}} + :style (merge {:border-top-width 1 + :border-top-color border-and-counter-bg-color + :padding-top padding-top + :padding-horizontal 16 + :align-items :center + :flex-direction :row} + container-style)} (when (= chevron-position :left) [rn/view {:test-ID :divider-label-icon-left diff --git a/src/quo2/components/notifications/toast.cljs b/src/quo2/components/notifications/toast.cljs index 6e7ff6a6f6..8ac476a306 100644 --- a/src/quo2/components/notifications/toast.cljs +++ b/src/quo2/components/notifications/toast.cljs @@ -9,7 +9,7 @@ (def ^:private themes {:container {:dark {:background-color colors/white-opa-70} - :light {:background-color colors/neutral-80-opa-70}} + :light {:background-color colors/neutral-80-opa-90}} :text {:dark {:color colors/neutral-100} :light {:color colors/white}} :icon {:dark {:color colors/neutral-100} @@ -51,8 +51,8 @@ [i18n/label :t/undo]]]) (defn- toast-container - [{:keys [left middle right]}] - [rn/view {:style {:padding-left 12 :padding-right 12}} + [{:keys [left middle right container-style]}] + [rn/view {:style (merge {:padding-left 12 :padding-right 12} container-style)} [rn/view {:style (merge-theme-style :container {:flex-direction :row @@ -74,14 +74,15 @@ (when right right)]]) (defn toast - [{:keys [icon icon-color text action undo-duration undo-on-press]}] + [{:keys [icon icon-color text action undo-duration undo-on-press container-style]}] [toast-container - {:left (when icon - [icon/icon icon - {:container-style {:width 20 :height 20} - :color (or icon-color - (get-in themes [:icon (theme/get-theme) :color]))}]) - :middle text - :right (if undo-duration - [toast-undo-action undo-duration undo-on-press] - action)}]) + {:left (when icon + [icon/icon icon + {:container-style {:width 20 :height 20} + :color (or icon-color + (get-in themes [:icon (theme/get-theme) :color]))}]) + :middle text + :right (if undo-duration + [toast-undo-action undo-duration undo-on-press] + action) + :container-style container-style}]) diff --git a/src/status_im/chat/models/images.cljs b/src/status_im/chat/models/images.cljs index 8be9d3b934..7ad7775e36 100644 --- a/src/status_im/chat/models/images.cljs +++ b/src/status_im/chat/models/images.cljs @@ -104,7 +104,7 @@ (re-frame/reg-fx ::camera-roll-get-photos - (fn [[num end-cursor]] + (fn [[num end-cursor album]] (permissions/request-permissions {:permissions [:read-external-storage] :on-allowed (fn [] @@ -115,19 +115,69 @@ {:first num :after end-cursor :assetType "Photos" - :groupTypes "All" + :groupTypes (if (= album (i18n/label :t/recent)) "All" "Albums") + :groupName (when (not= album (i18n/label :t/recent)) album) :include (clj->js ["imageSize"])}) - (.getPhotos CameraRoll - #js - {:first num - :assetType "Photos" - :groupTypes "All" - :include (clj->js ["imageSize"])})) + (.getPhotos + CameraRoll + #js + {:first num + :assetType "Photos" + :groupTypes (if (= album (i18n/label :t/recent)) "All" "Albums") + :groupName (when (not= album (i18n/label :t/recent)) album) + :include (clj->js ["imageSize"])})) (.then #(let [response (types/js->clj %)] (re-frame/dispatch [:on-camera-roll-get-photos (:edges response) (:page_info response) end-cursor]))) (.catch #(log/warn "could not get camera roll photos"))))}))) +(re-frame/reg-fx + :chat.ui/camera-roll-get-albums + (fn [] + (let [albums (atom [{:title :smart-albums :data []} + {:title (i18n/label :t/my-albums) :data []}])] + ;; Get the "recent" album first + (-> + (.getPhotos CameraRoll + #js + {:first 1 + :groupTypes "All"}) + (.then + (fn [res] + (let [recent-album {:title (i18n/label :t/recent) + :count "--" + :uri (get-in (first (:edges (types/js->clj res))) [:node :image :uri])}] + (swap! albums update-in [0 :data] conj recent-album) + ;; Get albums, then loop over albums and get each one's cover (first photo) + (-> + (.getAlbums CameraRoll #js {:assetType "All"}) + (.then + (fn [response] + (let [response (types/js->clj response)] + (reduce + (fn [_ album] + (-> + (.getPhotos + CameraRoll + #js + {:first 1 + :groupTypes "Albums" + :groupName (:title album)}) + (.then (fn [res] + (let [uri (get-in (first (:edges (types/js->clj res))) + [:node :image :uri])] + (swap! albums update-in [1 :data] conj (merge album {:uri uri})) + (when (= (count (get-in @albums [1 :data])) (count response)) + (swap! albums update-in + [1 :data] + #(->> % + (sort-by :title))) + (re-frame/dispatch [:on-camera-roll-get-albums @albums]))))))) + nil + response)))) + (.catch #(log/warn "could not get camera roll albums")))))))))) + + (rf/defn image-captured {:events [:chat.ui/image-captured]} [{:keys [db]} chat-id uri] @@ -139,15 +189,20 @@ (rf/defn on-end-reached {:events [:camera-roll/on-end-reached]} - [_ end-cursor loading? has-next-page?] + [_ end-cursor selected-album loading? has-next-page?] (when (and (not loading?) has-next-page?) (re-frame/dispatch [:chat.ui/camera-roll-loading-more true]) - (re-frame/dispatch [:chat.ui/camera-roll-get-photos 20 end-cursor]))) + (re-frame/dispatch [:chat.ui/camera-roll-get-photos 20 end-cursor selected-album]))) (rf/defn camera-roll-get-photos {:events [:chat.ui/camera-roll-get-photos]} - [_ num end-cursor] - {::camera-roll-get-photos [num end-cursor]}) + [_ num end-cursor selected-album] + {::camera-roll-get-photos [num end-cursor selected-album]}) + +(rf/defn camera-roll-get-albums + {:events [:chat.ui/camera-roll-get-albums]} + [_] + {:chat.ui/camera-roll-get-albums []}) (rf/defn camera-roll-loading-more {:events [:chat.ui/camera-roll-loading-more]} @@ -164,22 +219,32 @@ (assoc :camera-roll/has-next-page (:has_next_page page-info)) (assoc :camera-roll/loading-more false))})) +(rf/defn on-camera-roll-get-albums + {:events [:on-camera-roll-get-albums]} + [{:keys [db]} albums] + {:db (-> db + (assoc :camera-roll/albums albums) + (assoc :camera-roll/loading-albums false))}) + (rf/defn clear-sending-images {:events [:chat.ui/clear-sending-images]} - [{:keys [db]} current-chat-id] - {:db (update-in db [:chat/inputs current-chat-id :metadata] assoc :sending-image {})}) + [{:keys [db]}] + {:db (update-in db [:chat/inputs (:current-chat-id db) :metadata] assoc :sending-image {})}) (rf/defn cancel-sending-image {:events [:chat.ui/cancel-sending-image]} [{:keys [db] :as cofx} chat-id] - (let [current-chat-id (or chat-id (:current-chat-id db))] - (clear-sending-images cofx current-chat-id))) + (clear-sending-images cofx)) (rf/defn image-selected {:events [:chat.ui/image-selected]} [{:keys [db]} current-chat-id original uri] {:db - (update-in db [:chat/inputs current-chat-id :metadata :sending-image uri] merge original {:uri uri})}) + (update-in db + [:chat/inputs current-chat-id :metadata :sending-image (:uri original)] + merge + original + {:resized-uri uri})}) (rf/defn image-unselected {:events [:chat.ui/image-unselected]} @@ -215,6 +280,11 @@ (not (some #(= (:uri image) (:uri %)) images))) {::image-selected [image current-chat-id]})))) +(rf/defn camera-roll-select-album + {:events [:chat.ui/camera-roll-select-album]} + [{:keys [db]} album] + {:db (assoc db :camera-roll/selected-album album)}) + (rf/defn save-image-to-gallery {:events [:chat.ui/save-image-to-gallery]} [_ base64-uri] diff --git a/src/status_im/chat/models/input.cljs b/src/status_im/chat/models/input.cljs index 80cf56309a..2330a9e062 100644 --- a/src/status_im/chat/models/input.cljs +++ b/src/status_im/chat/models/input.cljs @@ -33,7 +33,7 @@ (rf/defn set-chat-input-text "Set input text for current-chat. Takes db and input text and cofx - as arguments and returns new fx. Always clear all validation messages." + as arguments and returns new fx. Always clear all validation messages." {:events [:chat.ui/set-chat-input-text]} [{db :db} new-input chat-id] (let [current-chat-id (or chat-id (:current-chat-id db))] @@ -150,11 +150,11 @@ [{db :db} chat-id input-text] (let [images (get-in db [:chat/inputs chat-id :metadata :sending-image]) album-id (str (random-uuid))] - (mapv (fn [[_ {:keys [uri width height]}]] + (mapv (fn [[_ {:keys [resized-uri width height]}]] {:chat-id chat-id :album-id album-id :content-type constants/content-type-image - :image-path (utils/safe-replace uri #"file://" "") + :image-path (utils/safe-replace resized-uri #"file://" "") :image-width width :image-height height ;; TODO: message not received if text field is diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 9d120169d9..32edb24bee 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -161,8 +161,10 @@ (rf/defn on-going-in-background [{:keys [db now]}] - {:db (assoc db :app-in-background-since now) - :dispatch-n [[:audio-recorder/on-background] [:audio-message/on-background]]}) + {:db (assoc db :app-in-background-since now) + ;; event not implemented + ;; :dispatch-n [[:audio-recorder/on-background] [:audio-message/on-background]] + }) (rf/defn app-state-change {:events [:app-state-change]} diff --git a/src/status_im/ui/screens/communities/create.cljs b/src/status_im/ui/screens/communities/create.cljs index e680d081f3..b5a559aac5 100644 --- a/src/status_im/ui/screens/communities/create.cljs +++ b/src/status_im/ui/screens/communities/create.cljs @@ -90,8 +90,10 @@ {:style {:padding-top 16 :align-items :center}} [rn/touchable-opacity - {:on-press #(rf/dispatch [:bottom-sheet/show-sheet - {:content (bottom-sheet (boolean image) editing?)}])} + {:on-press (fn [] + (rn/dismiss-keyboard!) + (rf/dispatch [:bottom-sheet/show-sheet + {:content (bottom-sheet (boolean image) editing?)}]))} [rn/view {:style {:width 128 :height 128}} diff --git a/src/status_im/ui2/screens/chat/composer/images/view.cljs b/src/status_im/ui2/screens/chat/composer/images/view.cljs index 1f3ed32bc2..9a60d4cf12 100644 --- a/src/status_im/ui2/screens/chat/composer/images/view.cljs +++ b/src/status_im/ui2/screens/chat/composer/images/view.cljs @@ -11,7 +11,7 @@ ;; so we need some magic here with paddings so close button isn't cut [rn/view {:padding-top 12 :padding-bottom 8 :padding-right 12} [rn/image - {:source {:uri (:uri (val item))} + {:source {:uri (:resized-uri (val item))} :style style/small-image}] [rn/touchable-opacity {:on-press (fn [] (rf/dispatch [:chat.ui/image-unselected (val item)])) diff --git a/src/status_im2/constants.cljs b/src/status_im2/constants.cljs index 08e577bd9a..da8476c2d3 100644 --- a/src/status_im2/constants.cljs +++ b/src/status_im2/constants.cljs @@ -207,27 +207,30 @@ (def ^:const delete-message-undo-time-limit-ms 4000) (def ^:const delete-message-for-me-undo-time-limit-ms 4000) -(def ^:const album-image-sizes - {2 {0 146 - 1 146} - 3 {0 [292 194] - 1 [145 97] - 2 [145 97]} - 4 {0 146 - 1 146 - 2 146 - 3 146} - 5 {0 146 - 1 146 - 2 97 - 3 97 - 4 97} - :default {0 146 - 1 146 - 2 72.5 - 3 72.5 - 4 72.5 - 5 72.5}}) +(def ^:const max-album-photos 6) +(def ^:const image-size 146) + +(def album-image-sizes ; sometimes we subtract 1 or 0.5 for padding + {2 {0 image-size + 1 image-size} + 3 {0 [(* image-size 2) (* image-size 1.5)] + 1 [(- image-size 1) (- (* image-size 0.67) 1)] + 2 [(- image-size 1) (- (* image-size 0.67) 1)]} + 4 {0 image-size + 1 image-size + 2 image-size + 3 image-size} + 5 {0 image-size + 1 image-size + 2 (- (* image-size 0.67) 1) + 3 (- (* image-size 0.67) 1) + 4 (- (* image-size 0.67) 1)} + :default {0 image-size + 1 image-size + 2 (- (* image-size 0.5) 0.5) + 3 (- (* image-size 0.5) 0.5) + 4 (- (* image-size 0.5) 0.5) + 5 (- (* image-size 0.5) 0.5)}}) (def ^:const spam-message-frequency-threshold 4) (def ^:const spam-interval-ms 1000) diff --git a/src/status_im2/contexts/chat/events.cljs b/src/status_im2/contexts/chat/events.cljs index 60f4299991..c6b62fc768 100644 --- a/src/status_im2/contexts/chat/events.cljs +++ b/src/status_im2/contexts/chat/events.cljs @@ -324,10 +324,12 @@ (rf/dispatch [:bottom-sheet/hide]) (rf/dispatch [:chat.ui/remove-chat chat-id]))}}) -(rf/defn navigate-to-horizontal-images - {:events [:chat.ui/navigate-to-horizontal-images]} - [cofx messages index] - (navigation/navigate-to cofx :images-horizontal {:messages messages :index index})) +(rf/defn navigate-to-user-pinned-messages + "Takes coeffects map and chat-id, returns effects necessary for navigation and preloading data" + {:events [:chat.ui/navigate-to-pinned-messages]} + [cofx chat-id] + (navigation/navigate-to cofx :chat-pinned-messages {:chat-id chat-id})) + (rf/defn update-shared-element-id {:events [:chat.ui/update-shared-element-id]} diff --git a/src/status_im2/contexts/chat/images_horizontal/style.cljs b/src/status_im2/contexts/chat/lightbox/style.cljs similarity index 94% rename from src/status_im2/contexts/chat/images_horizontal/style.cljs rename to src/status_im2/contexts/chat/lightbox/style.cljs index 09fcf0f48f..85c3994e2f 100644 --- a/src/status_im2/contexts/chat/images_horizontal/style.cljs +++ b/src/status_im2/contexts/chat/lightbox/style.cljs @@ -1,4 +1,4 @@ -(ns status-im2.contexts.chat.images-horizontal.style +(ns status-im2.contexts.chat.lightbox.style (:require [quo2.foundations.colors :as colors])) (defn container-view diff --git a/src/status_im2/contexts/chat/images_horizontal/view.cljs b/src/status_im2/contexts/chat/lightbox/view.cljs similarity index 96% rename from src/status_im2/contexts/chat/images_horizontal/view.cljs rename to src/status_im2/contexts/chat/lightbox/view.cljs index 6c6470dd6c..fe3a67a85a 100644 --- a/src/status_im2/contexts/chat/images_horizontal/view.cljs +++ b/src/status_im2/contexts/chat/lightbox/view.cljs @@ -1,12 +1,13 @@ -(ns status-im2.contexts.chat.images-horizontal.view +(ns status-im2.contexts.chat.lightbox.view (:require [quo2.core :as quo] [quo2.foundations.colors :as colors] [react-native.core :as rn] + [react-native.fast-image :as fast-image] [utils.re-frame :as rf] [react-native.safe-area :as safe-area] [reagent.core :as reagent] [oops.core :as oops] - [status-im2.contexts.chat.images-horizontal.style :as style] + [status-im2.contexts.chat.lightbox.style :as style] [utils.datetime :as datetime] [react-native.linear-gradient :as linear-gradient])) @@ -20,7 +21,7 @@ window-width (:width (rn/get-window)) height (* (or (:image-height message) 1000) (/ window-width (or (:image-width message) 1000)))] - [rn/image + [fast-image/fast-image {:source {:uri (:image (:content message))} :style {:width window-width :height height @@ -77,7 +78,7 @@ (defn small-image [item] - [rn/image + [fast-image/fast-image {:source {:uri (:image (:content item))} :style {:width small-image-size :height small-image-size @@ -105,7 +106,7 @@ :content-container-style {:padding-vertical 12 :padding-horizontal padding-horizontal}}]])) -(defn images-horizontal +(defn lightbox [] (let [{:keys [messages index]} (rf/sub [:get-screen-params]) ;; The initial value of data is the image that was pressed (and not the whole album) in order for diff --git a/src/status_im2/contexts/chat/messages/composer/controls/view.cljs b/src/status_im2/contexts/chat/messages/composer/controls/view.cljs index 2f02ccce29..ad381258ac 100644 --- a/src/status_im2/contexts/chat/messages/composer/controls/view.cljs +++ b/src/status_im2/contexts/chat/messages/composer/controls/view.cljs @@ -5,7 +5,6 @@ [utils.i18n :as i18n] [quo2.core :as quo] [status-im2.common.not-implemented :as not-implemented] - [status-im2.contexts.chat.photo-selector.view :as photo-selector] [status-im2.contexts.chat.messages.composer.controls.style :as style] [status-im2.contexts.chat.messages.list.view :as messages.list] [status-im.ui.components.permissions :as permissions] @@ -45,9 +44,7 @@ (permissions/request-permissions {:permissions [:read-external-storage :write-external-storage] :on-allowed #(rf/dispatch - [:bottom-sheet/show-sheet - {:content (fn [] - (photo-selector/photo-selector chat-id))}]) + [:open-modal :photo-selector {:chat-id chat-id}]) :on-denied (fn [] (background-timer/set-timeout #(utils-old/show-popup (i18n/label :t/error) diff --git a/src/status_im2/contexts/chat/messages/content/album/style.cljs b/src/status_im2/contexts/chat/messages/content/album/style.cljs index f9b7353700..1872933a3d 100644 --- a/src/status_im2/contexts/chat/messages/content/album/style.cljs +++ b/src/status_im2/contexts/chat/messages/content/album/style.cljs @@ -1,5 +1,6 @@ (ns status-im2.contexts.chat.messages.content.album.style - (:require [quo2.foundations.colors :as colors])) + (:require [quo2.foundations.colors :as colors] + [status-im2.constants :as constants])) (def max-album-height 292) @@ -7,15 +8,18 @@ [portrait?] {:flex-direction (if portrait? :column :row) :flex-wrap :wrap - :max-height max-album-height}) + :max-height max-album-height + :border-radius 12 + :width (inc (* constants/image-size 2)) + :overflow :hidden}) (defn image - [dimensions index] + [dimensions index portrait?] {:width (:width dimensions) :height (:height dimensions) :margin-left (when (or (and (not= index 0) (not= index 2) (not= count 3)) (= count 3) - (= index 2)) + (and portrait? (= index 2))) 1) :margin-bottom (when (< index 2) 1) :align-self :flex-start}) diff --git a/src/status_im2/contexts/chat/messages/content/album/view.cljs b/src/status_im2/contexts/chat/messages/content/album/view.cljs index 2fbf6bcc9e..ec2e1291f3 100644 --- a/src/status_im2/contexts/chat/messages/content/album/view.cljs +++ b/src/status_im2/contexts/chat/messages/content/album/view.cljs @@ -2,11 +2,11 @@ (:require [quo2.core :as quo] [quo2.foundations.colors :as colors] [react-native.core :as rn] + [react-native.fast-image :as fast-image] [status-im2.contexts.chat.messages.content.album.style :as style] [status-im2.constants :as constants] [utils.re-frame :as rf] [status-im2.contexts.chat.messages.content.image.view :as image])) -(def max-display-count 6) (def rectangular-style-count 3) @@ -32,7 +32,7 @@ (defn border-brr [index count] (when (or (and (= index 1) (< count rectangular-style-count)) - (and (= index (- (min count max-display-count) 1)) (> count 2))) + (and (= index (- (min count constants/max-album-photos) 1)) (> count 2))) 12)) (defn find-size @@ -42,7 +42,7 @@ {:width (second size-arr) :height (first size-arr) :album-style album-style})) (defn album-message - [{:keys [albumize?] :as message} {:keys [on-long-press] :as context}] + [{:keys [albumize?] :as message}] (let [shared-element-id (rf/sub [:shared-element-id]) first-image (first (:album message)) album-style (if (> (:image-width first-image) (:image-height first-image)) @@ -57,7 +57,7 @@ {:style (style/album-container portrait?)} (map-indexed (fn [index item] - (let [images-size-key (if (< images-count max-display-count) images-count :default) + (let [images-size-key (if (< images-count constants/max-album-photos) images-count :default) size (get-in constants/album-image-sizes [images-size-key index]) dimensions (if (not= images-count rectangular-style-count) {:width size :height size} @@ -67,22 +67,17 @@ :active-opacity 1 :on-press (fn [] (rf/dispatch [:chat.ui/update-shared-element-id (:message-id item)]) - (js/setTimeout #(rf/dispatch [:chat.ui/navigate-to-horizontal-images - (:album message) index]) - 100)) - :on-long-press on-long-press} - [rn/image - {:style (merge - (style/image dimensions index) - {:border-top-left-radius (border-tlr index) - :border-top-right-radius (border-trr index images-count album-style) - :border-bottom-left-radius (border-blr index images-count album-style) - :border-bottom-right-radius (border-brr index images-count)}) + (js/setTimeout #(rf/dispatch [:navigate-to :lightbox + {:messages (:album message) :index index}]) + 100))} + [fast-image/fast-image + {:style (style/image dimensions index portrait?) :source {:uri (:image (:content item))} :native-ID (when (and (= shared-element-id (:message-id item)) - (< index max-display-count)) + (< index constants/max-album-photos)) :shared-element)}] - (when (and (> images-count max-display-count) (= index (- max-display-count 1))) + (when (and (> images-count constants/max-album-photos) + (= index (- constants/max-album-photos 1))) [rn/view {:style (merge style/overlay {:border-bottom-right-radius (border-brr index images-count)})} @@ -90,11 +85,10 @@ {:weight :bold :size :heading-2 :style {:color colors/white}} - (str "+" (- images-count (dec max-display-count)))]])])) + (str "+" (- images-count (dec constants/max-album-photos)))]])])) (:album message))] - [:<> (map-indexed (fn [index item] - [image/image-message index item context]) + [image/image-message index item]) (:album message))]))) diff --git a/src/status_im2/contexts/chat/messages/content/image/view.cljs b/src/status_im2/contexts/chat/messages/content/image/view.cljs index a872582ff4..2c0c7b41ec 100644 --- a/src/status_im2/contexts/chat/messages/content/image/view.cljs +++ b/src/status_im2/contexts/chat/messages/content/image/view.cljs @@ -1,5 +1,6 @@ (ns status-im2.contexts.chat.messages.content.image.view (:require [react-native.core :as rn] + [react-native.fast-image :as fast-image] [utils.re-frame :as rf])) (defn calculate-dimensions @@ -15,24 +16,24 @@ {:width calculated-width :height calculated-height})))) (defn image-message - [index {:keys [content image-width image-height message-id] :as message} {:keys [on-long-press]}] + [index {:keys [content image-width image-height message-id] :as message}] (let [dimensions (calculate-dimensions (or image-width 1000) (or image-height 1000)) text (:text content)] (fn [] (let [shared-element-id (rf/sub [:shared-element-id])] [rn/touchable-opacity - {:style {:margin-top (when (> index 0) 20) :width (:width dimensions)} - :on-press (fn [] - (rf/dispatch [:chat.ui/update-shared-element-id message-id]) - (js/setTimeout #(rf/dispatch [:chat.ui/navigate-to-horizontal-images - [message] 0]) - 100)) - :on-long-press on-long-press} - [rn/view - ;; This text comp is temporary. Should later use - ;; `status-im2.contexts.chat.messages.content.text.view` - (when (and (not= text "placeholder") (= index 0)) [rn/text text]) - [rn/image - {:source {:uri (:image content)} - :style (merge dimensions {:border-radius 12}) - :native-ID (when (= shared-element-id message-id) :shared-element)}]]])))) + {:active-opacity 1 + :key message-id + :style {:margin-top (when (> index 0) 20)} + :on-press (fn [] + (rf/dispatch [:chat.ui/update-shared-element-id message-id]) + (js/setTimeout #(rf/dispatch [:navigate-to :lightbox + {:messages [message] :index 0}]) + 100))} + ;; This text comp is temporary. Should later use + ;; `status-im2.contexts.chat.messages.content.text.view` + (when (and (not= text "placeholder") (= index 0)) [rn/text text]) + [fast-image/fast-image + {:source {:uri (:image content)} + :style (merge dimensions {:border-radius 12}) + :native-ID (when (= shared-element-id message-id) :shared-element)}]])))) diff --git a/src/status_im2/contexts/chat/messages/content/view.cljs b/src/status_im2/contexts/chat/messages/content/view.cljs index 39e9ace3f6..b88c420f93 100644 --- a/src/status_im2/contexts/chat/messages/content/view.cljs +++ b/src/status_im2/contexts/chat/messages/content/view.cljs @@ -1,6 +1,7 @@ (ns status-im2.contexts.chat.messages.content.view (:require [react-native.core :as rn] [quo2.foundations.colors :as colors] + [status-im.utils.utils :as utils] [status-im2.contexts.chat.messages.content.style :as style] [status-im2.contexts.chat.messages.content.pin.view :as pin] [status-im2.constants :as constants] @@ -16,8 +17,7 @@ [utils.re-frame :as rf] [status-im.ui2.screens.chat.messages.message :as old-message] [status-im2.common.not-implemented :as not-implemented] - [utils.datetime :as datetime] - [status-im.utils.utils :as utils])) + [utils.datetime :as datetime])) (defn avatar [{:keys [content last-in-group? pinned quoted-message from]}] diff --git a/src/status_im2/contexts/chat/photo_selector/album_selector/style.cljs b/src/status_im2/contexts/chat/photo_selector/album_selector/style.cljs new file mode 100644 index 0000000000..f73b9f7246 --- /dev/null +++ b/src/status_im2/contexts/chat/photo_selector/album_selector/style.cljs @@ -0,0 +1,22 @@ +(ns status-im2.contexts.chat.photo-selector.album-selector.style + (:require [quo2.foundations.colors :as colors])) + +(defn album-container + [selected?] + {:flex-direction :row + :padding-horizontal 12 + :padding-vertical 8 + :margin-horizontal 8 + :border-radius 12 + :align-items :center + :background-color (when selected? colors/primary-50-opa-5)}) + +(def cover + {:width 40 + :height 40 + :border-radius 10}) + +(def divider + {:padding-horizontal 20 + :margin-top 16 + :margin-bottom 8}) diff --git a/src/status_im2/contexts/chat/photo_selector/album_selector/view.cljs b/src/status_im2/contexts/chat/photo_selector/album_selector/view.cljs new file mode 100644 index 0000000000..ad611f0e5c --- /dev/null +++ b/src/status_im2/contexts/chat/photo_selector/album_selector/view.cljs @@ -0,0 +1,64 @@ +(ns status-im2.contexts.chat.photo-selector.album-selector.view + (:require + [quo2.core :as quo] + [react-native.core :as rn] + [utils.i18n :as i18n] + [utils.re-frame :as rf] + [quo2.foundations.colors :as colors] + [status-im2.contexts.chat.photo-selector.view :refer [album-title]] + [status-im2.contexts.chat.photo-selector.album-selector.style :as style])) + +(defn album + [{:keys [title count uri]} index _ selected-album] + (let [selected? (= selected-album title)] + [rn/touchable-opacity + {:on-press (fn [] + (rf/dispatch [:chat.ui/camera-roll-select-album title]) + (rf/dispatch [:navigate-back])) + :style (style/album-container selected?) + :accessibility-label (str "album-" index)} + [rn/image + {:source {:uri uri} + :style style/cover}] + [rn/view {:style {:margin-left 12}} + [quo/text {:weight :medium} title] + [quo/text + {:size :paragraph-2 + :style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}} + (str count " " (i18n/label :t/images))]] + (when selected? + [rn/view + {:style {:position :absolute + :right 16}} + [quo/icon :i/check + {:size 20 :color (colors/theme-colors colors/primary-50 colors/primary-60)}]])])) + +(defn section-header + [{:keys [title]}] + (when (not= title "smart-albums") + [quo/divider-label + {:label title + :container-style style/divider}])) + +(defn album-selector + [] + [:f> + (fn [] + (let [albums (rf/sub [:camera-roll/albums]) + selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent))] + (rn/use-effect-once + (fn [] + (rf/dispatch [:chat.ui/camera-roll-get-albums]) + js/undefined)) + [rn/view {:style {:padding-top 20}} + [album-title false selected-album] + [rn/section-list + {:data albums + :render-fn album + :render-data selected-album + :sections albums + :sticky-section-headers-enabled false + :render-section-header-fn section-header + :style {:margin-top 12} + :content-container-style {:padding-bottom 40} + :key-fn :title}]]))]) diff --git a/src/status_im2/contexts/chat/photo_selector/style.cljs b/src/status_im2/contexts/chat/photo_selector/style.cljs index 434a016d2d..54fe25dbc9 100644 --- a/src/status_im2/contexts/chat/photo_selector/style.cljs +++ b/src/status_im2/contexts/chat/photo_selector/style.cljs @@ -3,28 +3,31 @@ [react-native.platform :as platform])) (defn gradient-container - [safe-area] - {:width "100%" - :height (+ (:bottom safe-area) 65) + [insets] + {:left 0 + :right 0 + :height (+ (:bottom insets) (if platform/ios? 65 85)) :position :absolute - :bottom (if platform/ios? 0 (:bottom safe-area))}) + :bottom 0}) -(defn buttons-container - [safe-area] - {:flex-direction :row - :justify-content :space-between - :padding-horizontal 20 - :bottom (+ (:bottom safe-area) 33)}) +(def buttons-container + {:position :absolute + :flex-direction :row + :left 0 + :right 0 + :margin-top 20 + :margin-bottom 12 + :justify-content :center + :z-index 1}) (defn clear-container [] {:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80) - :position :absolute - :align-self :flex-end :padding-horizontal 12 :padding-vertical 5 - :right 20 - :border-radius 10}) + :border-radius 10 + :position :absolute + :right 20}) (defn camera-button-container [] @@ -34,17 +37,21 @@ :border-radius 10 :justify-content :center :align-items :center - :margin-left 20 - :margin-bottom 24}) + :position :absolute + :left 20}) -(def title-container - {:flex-direction :row - :position :absolute - :align-self :center}) +(defn title-container + [] + {:flex-direction :row + :background-color (colors/theme-colors colors/neutral-10 colors/neutral-80) + :border-radius 10 + :padding-horizontal 12 + :padding-vertical 5 + :align-self :center}) (defn chevron-container [] - {:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80) + {:background-color (colors/theme-colors colors/neutral-30 colors/neutral-100) :width 14 :height 14 :border-radius 7 @@ -55,10 +62,12 @@ (defn image [window-width index] - {:width (- (/ window-width 3) 0.67) - :height (/ window-width 3) - :margin-left (when (not= (mod index 3) 0) 1) - :margin-bottom 1}) + {:width (- (/ window-width 3) 0.67) + :height (/ window-width 3) + :margin-left (when (not= (mod index 3) 0) 1) + :margin-bottom 1 + :border-top-left-radius (when (= index 0) 10) + :border-top-right-radius (when (= index 2) 10)}) (defn overlay [window-width] diff --git a/src/status_im2/contexts/chat/photo_selector/view.cljs b/src/status_im2/contexts/chat/photo_selector/view.cljs index eb2cf5203c..37252737a6 100644 --- a/src/status_im2/contexts/chat/photo_selector/view.cljs +++ b/src/status_im2/contexts/chat/photo_selector/view.cljs @@ -1,48 +1,46 @@ (ns status-im2.contexts.chat.photo-selector.view - (:require [utils.i18n :as i18n] - [quo.components.safe-area :as safe-area] - [quo2.components.notifications.info-count :as info-count] - [quo2.core :as quo] - [quo2.foundations.colors :as colors] - [react-native.core :as rn] - [react-native.linear-gradient :as linear-gradient] - [reagent.core :as reagent] - [status-im2.contexts.chat.photo-selector.style :as style] - [status-im.utils.core :as utils] - [utils.re-frame :as rf])) - -(def selected (reagent/atom [])) + (:require + [react-native.platform :as platform] + [status-im2.constants :as constants] + [utils.i18n :as i18n] + [react-native.safe-area :as safe-area] + [quo2.components.notifications.info-count :as info-count] + [quo2.core :as quo] + [quo2.foundations.colors :as colors] + [react-native.core :as rn] + [react-native.linear-gradient :as linear-gradient] + [reagent.core :as reagent] + [status-im2.contexts.chat.photo-selector.style :as style] + [status-im.utils.core :as utils] + [quo.react] + [utils.re-frame :as rf])) (defn on-press-confirm-selection - [chat-id] - (rf/dispatch [:chat.ui/clear-sending-images chat-id]) + [selected] + (rf/dispatch [:chat.ui/clear-sending-images]) (doseq [item @selected] (rf/dispatch [:chat.ui/camera-roll-pick item])) (reset! selected []) - (rf/dispatch [:bottom-sheet/hide])) + (rf/dispatch [:navigate-back])) (defn bottom-gradient - [chat-id selected-images] - [:f> - (fn [] - (let [safe-area (safe-area/use-safe-area)] - (when (or (seq @selected) selected-images) - [linear-gradient/linear-gradient - {:colors [:black :transparent] - :start {:x 0 :y 1} - :end {:x 0 :y 0} - :style (style/gradient-container safe-area)} - [quo/button - {:style {:align-self :stretch - :margin-horizontal 20} - :on-press (fn [] - (rf/dispatch [:bottom-sheet/hide]) - (on-press-confirm-selection chat-id)) - :accessibility-label :confirm-selection} - (i18n/label :t/confirm-selection)]])))]) + [selected-images insets selected] + (when (or (seq @selected) (seq selected-images)) + [linear-gradient/linear-gradient + {:colors [:black :transparent] + :start {:x 0 :y 1} + :end {:x 0 :y 0} + :style (style/gradient-container insets)} + [quo/button + {:style {:align-self :stretch + :margin-horizontal 20 + :margin-top 12} + :on-press #(on-press-confirm-selection selected) + :accessibility-label :confirm-selection} + (i18n/label :t/confirm-selection)]])) (defn clear-button - [] + [selected] (when (seq @selected) [rn/touchable-opacity {:on-press #(reset! selected []) @@ -52,66 +50,87 @@ (defn remove-selected [coll item] - (vec (remove #(= % item) coll))) + (vec (remove #(= (:uri item) (:uri %)) coll))) (defn image - [item index _ {:keys [window-width]}] + [item index _ {:keys [window-width selected]}] [rn/touchable-opacity {:active-opacity 1 :on-press (fn [] - (if (some #{item} @selected) + (if (some #(= (:uri item) (:uri %)) @selected) (swap! selected remove-selected item) - (swap! selected conj item))) + (if (>= (count @selected) constants/max-album-photos) + (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)}]) + (swap! selected conj item)))) :accessibility-label (str "image-" index)} [rn/image {:source {:uri (:uri item)} :style (style/image window-width index)}] - (when (some #{item} @selected) + (when (some #(= (:uri item) (:uri %)) @selected) [rn/view {:style (style/overlay window-width)}]) - (when (some #{item} @selected) + (when (some #(= (:uri item) (:uri %)) @selected) [info-count/info-count {:style style/image-count :accessibility-label (str "count-" index)} - (inc (utils/first-index #(= item %) @selected))])]) + (inc (utils/first-index #(= (:uri item) (:uri %)) @selected))])]) + +(defn album-title + [photos? selected-album] + [rn/touchable-opacity + {:style (style/title-container) + :active-opacity 1 + :accessibility-label :album-title + :on-press #(rf/dispatch (if photos? + [:open-modal :album-selector] + [:navigate-back]))} + [quo/text {:weight :medium} selected-album] + [rn/view {:style (style/chevron-container)} + [quo/icon (if photos? :i/chevron-down :i/chevron-up) + {:color (colors/theme-colors colors/neutral-100 colors/white)}]]]) (defn photo-selector - [chat-id] - (rf/dispatch [:chat.ui/camera-roll-get-photos 20]) - (let [selected-images (keys (rf/sub [:chats/sending-image]))] - [:f> - (fn [] - (rn/use-effect-once + [] + [:f> + (fn [] + (let [selected-images (rf/sub [:chats/sending-image]) + selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent)) + selected (reagent/atom [])] + (rn/use-effect (fn [] + (rf/dispatch [:chat.ui/camera-roll-get-photos 20 nil selected-album]) (if selected-images - (reset! selected (vec selected-images)) - (reset! selected [])) - js/undefined)) - (let [{window-height :height window-width :width} (rn/use-window-dimensions) - safe-area (safe-area/use-safe-area) - camera-roll-photos (rf/sub [:camera-roll/photos]) - end-cursor (rf/sub [:camera-roll/end-cursor]) - loading? (rf/sub [:camera-roll/loading-more]) - has-next-page? (rf/sub [:camera-roll/has-next-page])] - [rn/view {:style {:height (- window-height (:top safe-area))}} - [rn/touchable-opacity - {:on-press #(js/alert "Camera: not implemented") - :style (style/camera-button-container)} - [quo/icon :i/camera {:color (colors/theme-colors colors/neutral-100 colors/white)}]] - [rn/view - {:style style/title-container} - [quo/text {:weight :medium} (i18n/label :t/recent)] - [rn/view {:style (style/chevron-container)} - [quo/icon :i/chevron-down {:color (colors/theme-colors colors/neutral-100 colors/white)}]]] - [clear-button] - [rn/flat-list - {:key-fn identity - :render-fn image - :render-data {:window-width window-width} - :data camera-roll-photos - :num-columns 3 - :content-container-style {:width "100%" - :padding-bottom (+ (:bottom safe-area) 100)} - :style {:border-radius 20} - :on-end-reached #(rf/dispatch [:camera-roll/on-end-reached end-cursor loading? - has-next-page?])}] - [bottom-gradient chat-id selected-images]]))])) + (reset! selected (vec (vals selected-images))) + (reset! selected []))) + [selected-album]) + [safe-area/consumer + (fn [insets] + (let [window-width (:width (rn/get-window)) + camera-roll-photos (rf/sub [:camera-roll/photos]) + end-cursor (rf/sub [:camera-roll/end-cursor]) + loading? (rf/sub [:camera-roll/loading-more]) + has-next-page? (rf/sub [:camera-roll/has-next-page])] + [rn/view {:style {:flex 1}} + [rn/view + {:style style/buttons-container} + [album-title true selected-album] + [clear-button selected]] + [rn/flat-list + {:key-fn identity + :render-fn image + :render-data {:window-width window-width :selected selected} + :data camera-roll-photos + :num-columns 3 + :content-container-style {:width "100%" + :padding-bottom (+ (:bottom insets) 100) + :padding-top 80} + :on-end-reached #(rf/dispatch [:camera-roll/on-end-reached end-cursor + selected-album loading? + has-next-page?])}] + [bottom-gradient selected-images insets selected]]))]))]) + + diff --git a/src/status_im2/navigation/screens.cljs b/src/status_im2/navigation/screens.cljs index bc37ba47e3..76c23c69cd 100644 --- a/src/status_im2/navigation/screens.cljs +++ b/src/status_im2/navigation/screens.cljs @@ -9,9 +9,12 @@ [status-im2.contexts.quo-preview.main :as quo.preview] [status-im2.contexts.shell.view :as shell] [status-im2.contexts.syncing.view :as settings-syncing] - [status-im2.contexts.chat.images-horizontal.view :as images-horizontal] + [status-im2.contexts.chat.lightbox.view :as lightbox] [status-im2.config :as config] - [quo.design-system.colors :as colors])) + [quo.design-system.colors :as colors] + [status-im2.contexts.chat.photo-selector.album-selector.view :as album-selector] + [react-native.platform :as platform] + [status-im2.contexts.chat.photo-selector.view :as photo-selector])) (def components []) @@ -32,7 +35,7 @@ :options {:topBar {:visible false}} :component chat/chat} - {:name :images-horizontal + {:name :lightbox :insets {:top false :bottom false} :options {:topBar {:visible false} :statusBar {:backgroundColor colors/black-persist @@ -46,7 +49,15 @@ :toId :shared-element :interpolation {:type :decelerate}}]}}} - :component images-horizontal/images-horizontal} + :component lightbox/lightbox} + {:name :photo-selector + :options {:topBar {:visible false}} + :component photo-selector/photo-selector} + + {:name :album-selector + :options {:topBar {:visible false} + :modalPresentationStyle (if platform/ios? :overCurrentContext :none)} + :component album-selector/album-selector} {:name :new-contact :options {:topBar {:visible false}} diff --git a/src/status_im2/subs/chat/messages.cljs b/src/status_im2/subs/chat/messages.cljs index 5af01f3797..580cea89d0 100644 --- a/src/status_im2/subs/chat/messages.cljs +++ b/src/status_im2/subs/chat/messages.cljs @@ -110,23 +110,24 @@ (defn albumize-messages [messages] - (get (reduce (fn [{:keys [messages albums]} message] - (let [album-id (:album-id message) - albums (cond-> albums album-id (update album-id conj message)) - messages (if album-id - (conj (filterv #(not= album-id (:album-id %)) messages) - {:album (get albums album-id) - :album-id album-id - :albumize? (:albumize? message) - :message-id album-id - :content-type constants/content-type-album}) - (conj messages message))] - {:messages messages - :albums albums})) - {:messages [] - :albums {}} - messages) - :messages)) + (get + (reduce (fn [{:keys [messages albums]} message] + (let [album-id (:album-id message) + albums (cond-> albums album-id (update album-id conj message)) + messages (if album-id + (conj (filterv #(not= album-id (:album-id %)) messages) + {:album (get albums album-id) + :album-id album-id + :albumize? (:albumize? message) + :message-id album-id + :content-type constants/content-type-album}) + (conj messages message))] + {:messages messages + :albums albums})) + {:messages [] + :albums {}} + messages) + :messages)) (re-frame/reg-sub :chats/chat-messages diff --git a/src/status_im2/subs/root.cljs b/src/status_im2/subs/root.cljs index 3bfd3b5f54..9e2457ca59 100644 --- a/src/status_im2/subs/root.cljs +++ b/src/status_im2/subs/root.cljs @@ -109,6 +109,8 @@ (reg-root-key-sub :camera-roll/end-cursor :camera-roll/end-cursor) (reg-root-key-sub :camera-roll/has-next-page :camera-roll/has-next-page) (reg-root-key-sub :camera-roll/loading-more :camera-roll/loading-more) +(reg-root-key-sub :camera-roll/albums :camera-roll/albums) +(reg-root-key-sub :camera-roll/selected-album :camera-roll/selected-album) (reg-root-key-sub :group-chat/invitations :group-chat/invitations) (reg-root-key-sub :chats/mention-suggestions :chats/mention-suggestions) (reg-root-key-sub :chat/inputs-with-mentions :chat/inputs-with-mentions) diff --git a/translations/en.json b/translations/en.json index 6d4e5a2ab2..1269938b74 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1956,5 +1956,8 @@ "wants-to-join": "wants to join", "connect-with-users": "Connect with users", "invite-friends-and-family": "Invite your friends and family to Status", - "you-have-no-contacts": "You have no contacts" + "you-have-no-contacts": "You have no contacts", + "my-albums": "My albums", + "images": "images", + "only-6-images": "You can only add 6 images to your message" }