Photo selector and album selector screens (#14867)

* feat: photo & album selector screens
This commit is contained in:
Omar Basem 2023-02-01 17:01:03 +04:00 committed by GitHub
parent fb9309f700
commit b8eab0c328
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 458 additions and 249 deletions

View File

@ -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

View File

@ -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}])

View File

@ -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]

View File

@ -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

View File

@ -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]}

View File

@ -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}}

View File

@ -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)]))

View File

@ -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)

View File

@ -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]}

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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})

View File

@ -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))])))

View File

@ -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)}]]))))

View File

@ -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]}]

View File

@ -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})

View File

@ -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}]]))])

View File

@ -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]

View File

@ -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]]))]))])

View File

@ -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}}

View File

@ -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

View File

@ -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)

View File

@ -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"
}