improve photo-selector and adjust according to the latest designs (#16053)

This commit is contained in:
flexsurfer 2023-06-01 10:35:57 +02:00 committed by GitHub
parent 68e53e05bd
commit a4bc18ee3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 368 additions and 645 deletions

View File

@ -220,10 +220,10 @@
only icon
[button {:icon true} :i/close-circle]"
[_ _]
(let [pressed (reagent/atom false)]
(let [pressed-in (reagent/atom false)]
(fn
[{:keys [on-press disabled type size before after above
width customization-color override-theme override-background-color
[{:keys [on-press disabled type size before after above icon-secondary-no-color
width customization-color override-theme override-background-color pressed
on-long-press accessibility-label icon icon-no-color style inner-style test-ID]
:or {type :primary
size 40
@ -235,7 +235,7 @@
override-theme
(theme/get-theme)) type])
state (cond disabled :disabled
@pressed :pressed
(or @pressed-in pressed) :pressed
:else :default)
icon-size (when (= 24 size) 12)
icon-secondary-color (or icon-secondary-color icon-color)]
@ -243,8 +243,8 @@
(merge {:test-ID test-ID
:disabled disabled
:accessibility-label accessibility-label
:on-press-in #(reset! pressed true)
:on-press-out #(reset! pressed nil)}
:on-press-in #(reset! pressed-in true)
:on-press-out #(reset! pressed-in nil)}
(when on-press
{:on-press on-press})
(when on-long-press
@ -306,5 +306,6 @@
[quo2.icons/icon after
{:container-style {:margin-left 4
:margin-right (if (= size 40) 12 8)}
:no-color icon-secondary-no-color
:color icon-secondary-color
:size icon-size}]])]]]))))

View File

@ -1,165 +1,14 @@
(ns quo2.components.dropdowns.dropdown
(:require [quo2.components.icon :as icons]
[quo2.components.markdown.text :as text]
[quo2.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]))
(defn apply-anim
[dd-height val]
(reanimated/animate-shared-value-with-delay dd-height
val
300
:easing1
0))
(def sizes
{:big {:icon-size 20
:font {:font-size :paragraph-1}
:height 40
:padding {:padding-with-icon {:padding-vertical 9
:padding-horizontal 12}
:padding-with-no-icon {:padding-vertical 9
:padding-left 12
:padding-right 8}}}
:medium {:icon-size 20
:font {:font-size :paragraph-1}
:height 32
:padding {:padding-with-icon {:padding-vertical 5
:padding-horizontal 8}
:padding-with-no-icon {:padding-vertical 5
:padding-left 12
:padding-right 8}}}
:small {:icon-size 15
:font {:font-size :paragraph-2}
:height 24
:padding {:padding-with-icon {:padding-vertical 3
:padding-horizontal 6}
:padding-with-no-icon {:padding-vertical 3
:padding-horizontal 6}}}})
(defn color-by-10
[color]
(colors/alpha color 0.6))
(defn dropdown-comp
[{:keys [icon dd-height size disabled? dd-color use-border? border-color]}]
(let [dark? (colors/dark?)
{:keys [width height width-with-icon padding font icon-size]} (size sizes)
{:keys [padding-with-icon padding-with-no-icon]} padding
font-size (:font-size font)
spacing (case size
:big 4
:medium 2
:small 2)
open? (reagent/atom false)]
(fn []
[rn/touchable-opacity
(cond->
{:on-press (fn []
(if (swap! open? not)
(apply-anim dd-height 120)
(apply-anim dd-height 0)))
:style (cond->
(merge
(if icon
padding-with-icon
padding-with-no-icon)
{:width (if icon
width-with-icon
width)
:height height
:border-radius (case size
:big 12
:medium 10
:small 8)
:flex-direction :row
:align-items :center
:background-color (if @open?
dd-color
(color-by-10 dd-color))})
use-border? (assoc :border-width 1
:border-color (if @open?
border-color
(color-by-10 border-color))))}
disabled? (assoc-in [:style :opacity] 0.3)
disabled? (assoc :disabled true))
(when icon
[icons/icon icon
{:no-color true
:size 20
:container-style {:margin-right spacing
:margin-top 1
:width icon-size
:height icon-size}}])
[text/text
{:size font-size
:weight :medium
:font :font-medium
:color :main} "Dropdown"]
[icons/icon
(if @open?
(if dark?
:main-icons/pullup-dark
:main-icons/pullup)
(if dark?
:main-icons/dropdown-dark
:main-icons/dropdown))
{:size 20
:no-color true
:container-style {:width (+ icon-size 3)
:border-radius 20
:margin-left (if (= :small size)
2
4)
:margin-top 1
:height (+ icon-size 4)}}]])))
(defn items-comp
[{:keys [items on-select]}]
(let [items-count (count items)]
[rn/scroll-view
{:horizontal false
:nestedScrollEnabled true}
(doall
(map-indexed (fn [index item]
[rn/touchable-opacity
{:key (str item index)
:style {:padding 4
:border-bottom-width (if (= index (- items-count 1))
0
1)
:border-color (colors/theme-colors
colors/neutral-100
colors/white)
:text-align :center}
:on-press #(on-select item)}
[text/text {:style {:text-align :center}} item]])
items))]))
(defn- f-dropdown
[{:keys [items icon text default-item on-select size disabled? border-color use-border? dd-color]}]
(let [dd-height (reanimated/use-shared-value 0)]
[rn/view {:style {:flex-grow 1}}
[dropdown-comp
{:items items
:icon icon
:disabled? disabled?
:size size
:dd-color dd-color
:text text
:border-color (colors/custom-color-by-theme border-color 50 60)
:use-border? use-border?
:default-item default-item
:dd-height dd-height}]
[reanimated/view
{:style (reanimated/apply-animations-to-style
{:height dd-height}
{})}
[items-comp
{:items items
:on-select on-select}]]]))
(:require [quo2.components.buttons.button :as button]))
(defn dropdown
[params]
[:f> f-dropdown params])
[_ _]
(fn [{:keys [on-change selected] :as opts} children]
[button/button
(merge
opts
{:after (if selected :i/pullup :i/dropdown)
:icon-secondary-no-color true
:pressed selected
:on-press #(when on-change (on-change selected))})
children]))

View File

@ -0,0 +1,16 @@
(ns react-native.cameraroll
(:require ["@react-native-community/cameraroll" :as CameraRoll]
[utils.transforms :as transforms]
[taoensso.timbre :as log]))
(defn get-photos
[opts callback]
(-> (.getPhotos CameraRoll (clj->js opts))
(.then #(callback (transforms/js->clj %)))
(.catch #(log/warn "could not get camera roll photos" %))))
(defn get-albums
[opts callback]
(-> (.getAlbums CameraRoll (clj->js opts))
(.then #(callback (transforms/js->clj %)))
(.catch #(log/warn "could not get camera roll albums" %))))

View File

@ -17,6 +17,7 @@
(def view (reagent/adapt-react-class (.-View ^js react-native)))
(def scroll-view (reagent/adapt-react-class (.-ScrollView ^js react-native)))
(def image (reagent/adapt-react-class (.-Image ^js react-native)))
(defn image-get-size [uri callback] (.getSize ^js (.-Image ^js react-native) uri callback))
(def text (reagent/adapt-react-class (.-Text ^js react-native)))
(def text-input (reagent/adapt-react-class (.-TextInput ^js react-native)))

View File

@ -91,7 +91,7 @@
[flat-list
(merge props
{:data data
:render-fn (fn [item]
(if (:header? item)
(render-section-header-fn item)
(render-fn item)))})]))
:render-fn (fn [p1 p2 p3 p4]
(if (:header? p1)
[render-section-header-fn p1 p2 p3 p4]
[render-fn p1 p2 p3 p4]))})]))

View File

@ -1,47 +1,17 @@
(ns status-im.chat.models.images
(:require ["@react-native-community/cameraroll" :as CameraRoll]
["react-native-blob-util" :default ReactNativeBlobUtil]
[clojure.string :as string]
[re-frame.core :as re-frame]
[utils.i18n :as i18n]
[status-im.ui.components.permissions :as permissions]
[react-native.permissions :as permissions]
[status-im.ui.components.react :as react]
[status-im2.config :as config]
[status-im.utils.fs :as fs]
[utils.re-frame :as rf]
[status-im.utils.image-processing :as image-processing]
[status-im.utils.platform :as platform]
[status-im.utils.types :as types]
[status-im.utils.utils :as utils]
[taoensso.timbre :as log]))
(def maximum-image-size-px 2000)
(defn- resize-and-call
[uri cb]
(react/image-get-size
uri
(fn [width height]
(let [resize? (> (max width height) maximum-image-size-px)]
(image-processing/resize
uri
(if resize? maximum-image-size-px width)
(if resize? maximum-image-size-px height)
60
(fn [^js resized-image]
(let [path (.-path resized-image)
path (if (string/starts-with? path "file") path (str "file://" path))]
(cb {:resized-uri path
:width width
:height height})))
#(log/error "could not resize image" %))))))
(defn result->id
[^js result]
(if platform/ios?
(.-localIdentifier result)
(.-path result)))
(def temp-image-url (str (fs/cache-dir) "/StatusIm_Image.jpeg"))
(defn download-image-http
@ -78,108 +48,6 @@
#(re-frame/dispatch [:chat.ui/image-captured current-chat-id (.-path %)])
{})))
(re-frame/reg-fx
::chat-open-image-picker
(fn [chat-id]
(react/show-image-picker
(fn [^js images]
;; NOTE(Ferossgp): Because we can't highlight the already selected images inside
;; gallery, we just clean previous state and set all newly picked images
(when (and platform/ios? (pos? (count images)))
(re-frame/dispatch [:chat.ui/clear-sending-images chat-id]))
(doseq [^js result (if platform/ios?
(take config/max-images-batch images)
[images])]
(resize-and-call (.-path result)
#(re-frame/dispatch [:chat.ui/image-selected chat-id (result->id result) %]))))
;; NOTE(Ferossgp): On android you cannot set max limit on images, when a user
;; selects too many images the app crashes.
{:media-type "photo"
:multiple platform/ios?})))
(re-frame/reg-fx
::image-selected
(fn [[image chat-id]]
(resize-and-call
(:uri image)
#(re-frame/dispatch [:chat.ui/image-selected chat-id image %]))))
(re-frame/reg-fx
::camera-roll-get-photos
(fn [[num end-cursor album]]
(permissions/request-permissions
{:permissions [:read-external-storage]
:on-allowed (fn []
(-> (if end-cursor
(.getPhotos
CameraRoll
#js
{:first num
:after end-cursor
:assetType "Photos"
: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 (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]
@ -189,81 +57,17 @@
(not (get images uri)))
{::image-selected [uri current-chat-id]})))
(rf/defn on-end-reached
{:events [:camera-roll/on-end-reached]}
[_ 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 selected-album])))
(rf/defn camera-roll-get-photos
{:events [:chat.ui/camera-roll-get-photos]}
[_ 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]}
[{:keys [db]} is-loading]
{:db (assoc db :camera-roll/loading-more is-loading)})
(rf/defn on-camera-roll-get-photos
{:events [:on-camera-roll-get-photos]}
[{:keys [db] :as cofx} photos page-info end-cursor]
(let [photos_x (when end-cursor (:camera-roll/photos db))]
{:db (-> db
(assoc :camera-roll/photos (concat photos_x (map #(get-in % [:node :image]) photos)))
(assoc :camera-roll/end-cursor (:end_cursor page-info))
(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]}]
{: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]
(clear-sending-images cofx))
(rf/defn image-selected
{:events [:chat.ui/image-selected]}
[{:keys [db]} current-chat-id original {:keys [resized-uri width height]}]
{:db
(update-in db
[:chat/inputs current-chat-id :metadata :sending-image (:uri original)]
merge
original
{:resized-uri resized-uri
:width width
:height height})})
(rf/defn image-unselected
{:events [:chat.ui/image-unselected]}
[{:keys [db]} original]
(let [current-chat-id (:current-chat-id db)]
{:db (update-in db [:chat/inputs current-chat-id :metadata :sending-image] dissoc (:uri original))}))
(rf/defn chat-open-image-picker
{:events [:chat.ui/open-image-picker]}
[{: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)
{::chat-open-image-picker current-chat-id})))
(rf/defn chat-show-image-picker-camera
{:events [:chat.ui/show-image-picker-camera]}
[{:keys [db]} chat-id]
@ -271,25 +75,3 @@
images (get-in db [:chat/inputs current-chat-id :metadata :sending-image])]
(when (< (count images) config/max-images-batch)
{::chat-open-image-picker-camera current-chat-id})))
(rf/defn camera-roll-pick
{:events [:chat.ui/camera-roll-pick]}
[{: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])]
(if (get-in db [:chats current-chat-id :timeline?])
{:db (assoc-in db [:chat/inputs current-chat-id :metadata :sending-image] {})
::image-selected [image current-chat-id]}
(when (and (< (count images) config/max-images-batch)
(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]
{::save-image-to-gallery base64-uri})

View File

@ -36,7 +36,7 @@
status-im.signals.core
status-im.stickers.core
status-im.transport.core
[status-im.ui.components.permissions :as permissions]
[react-native.permissions :as permissions]
[status-im.ui.components.react :as react]
status-im.ui.screens.privacy-and-security-settings.events
[status-im.utils.dimensions :as dimensions]

View File

@ -1,34 +0,0 @@
(ns status-im.ui.components.permissions
(:require ["react-native-permissions" :refer (requestMultiple PERMISSIONS RESULTS)]
[status-im.utils.platform :as platform]))
(def permissions-map
{:read-external-storage (cond
platform/android? (.-READ_EXTERNAL_STORAGE (.-ANDROID PERMISSIONS)))
:write-external-storage (cond
platform/low-device? (.-WRITE_EXTERNAL_STORAGE (.-ANDROID PERMISSIONS)))
:camera (cond
platform/android? (.-CAMERA (.-ANDROID PERMISSIONS))
platform/ios? (.-CAMERA (.-IOS PERMISSIONS)))
:record-audio (cond
platform/android? (.-RECORD_AUDIO (.-ANDROID PERMISSIONS))
platform/ios? (.-MICROPHONE (.-IOS PERMISSIONS)))})
(defn all-granted?
[permissions]
(let [permission-vals (distinct (vals permissions))]
(and (= (count permission-vals) 1)
(not (#{(.-BLOCKED RESULTS) (.-DENIED RESULTS)} (first permission-vals))))))
(defn request-permissions
[{:keys [permissions on-allowed on-denied]
:or {on-allowed #()
on-denied #()}}]
(let [permissions (remove nil? (mapv #(get permissions-map %) permissions))]
(if (empty? permissions)
(on-allowed)
(-> (requestMultiple (clj->js permissions))
(.then #(if (all-granted? (js->clj %))
(on-allowed)
(on-denied)))
(.catch on-denied)))))

View File

@ -11,7 +11,7 @@
[status-im.ui.components.chat-icon.screen :as chat-icon]
[status-im.ui.components.connectivity.view :as connectivity]
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.components.permissions :as components.permissions]
[react-native.permissions :as components.permissions]
[status-im.ui.components.react :as react]
[status-im.ui.components.tooltip.views :as tooltip]
[status-im.ui.components.webview :as components.webview]

View File

@ -1,7 +1,6 @@
(ns status-im2.contexts.chat.events-test
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im2.contexts.chat.events :as chat]
[status-im.chat.models.images :as images]
[status-im.utils.clocks :as utils.clocks]))
(deftest clear-history-test
@ -90,8 +89,3 @@
(testing "Pagination info should be reset on navigation"
(let [res (chat/navigate-to-chat {:db db} chat-id)]
(is (nil? (get-in res [:db :pagination-info chat-id :all-loaded?])))))))
(deftest camera-roll-loading-more-test
(let [cofx {:db {:camera-roll/loading-more false}}]
(is (= {:db {:camera-roll/loading-more true}}
(images/camera-roll-loading-more cofx true)))))

View File

@ -6,16 +6,16 @@
[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]
(defn render-album
[{:keys [title count uri]} index _ {:keys [album? 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]))
(rf/dispatch [:photo-selector/get-photos-for-selected-album])
(reset! album? false))
:style (style/album-container selected?)
:accessibility-label (str "album-" index)}
[rn/image
@ -31,7 +31,7 @@
[quo/text
{:size :paragraph-2
:style {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}}
(str count " " (i18n/label :t/images))]]
(when count (str count " " (i18n/label :t/images)))]]
(when selected?
[rn/view
{:style {:position :absolute
@ -39,9 +39,11 @@
[quo/icon :i/check
{:size 20 :color (colors/theme-colors colors/primary-50 colors/primary-60)}]])]))
(def no-title "no-title")
(defn section-header
[{:keys [title]}]
(when (not= title "smart-albums")
(when-not (= title no-title)
[quo/divider-label
{:label title
:container-style style/divider}]))
@ -51,22 +53,19 @@
(str (:title item) index))
(defn album-selector
[{:keys [on-scroll]}]
(rf/dispatch [:chat.ui/camera-roll-get-albums])
(fn [{:keys [scroll-enabled]}]
[{:keys [scroll-enabled on-scroll]} album? selected-album]
(let [albums (rf/sub [:camera-roll/albums])
selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent))]
[rn/view {:style {:padding-top 20}}
[album-title false]
albums-sections [{:title no-title :data (:smart-albums albums)}
{:title (i18n/label :t/my-albums) :data (:my-albums albums)}]]
[gesture/section-list
{:data albums
:render-fn album
:render-data selected-album
:sections albums
{:data albums-sections
:sections albums-sections
:render-data {:album? album? :selected-album selected-album}
:render-fn render-album
:sticky-section-headers-enabled false
:render-section-header-fn section-header
:style {:margin-top 12}
:content-container-style {:padding-bottom 40}
:content-container-style {:padding-top 64
:padding-bottom 40}
:key-fn key-fn
:scroll-enabled @scroll-enabled
:on-scroll on-scroll}]])))
:on-scroll on-scroll}]))

View File

@ -0,0 +1,146 @@
(ns status-im2.contexts.chat.photo-selector.events
(:require [react-native.cameraroll :as cameraroll]
[clojure.string :as string]
[re-frame.core :as re-frame]
[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]
[react-native.core :as rn]))
(def maximum-image-size-px 2000)
(defn- resize-photo
[uri callback]
(rn/image-get-size
uri
(fn [width height]
(let [resize? (> (max width height) maximum-image-size-px)]
(image-processing/resize
uri
(if resize? maximum-image-size-px width)
(if resize? maximum-image-size-px height)
60
(fn [^js resized-image]
(let [path (.-path resized-image)
path (if (string/starts-with? path "file") path (str "file://" path))]
(callback {:resized-uri path
:width width
:height height})))
#(log/error "could not resize image" %))))))
(re-frame/reg-fx
:camera-roll-request-permissions-and-get-photos
(fn [[num end-cursor album]]
(permissions/request-permissions
{:permissions [:read-external-storage]
:on-allowed
(fn []
(cameraroll/get-photos
(merge {:first num
:assetType "Photos"
:groupTypes (if (= album (i18n/label :t/recent)) "All" "Albums")
:groupName (when (not= album (i18n/label :t/recent)) album)
:include ["imageSize"]}
(when end-cursor
{:after end-cursor}))
#(re-frame/dispatch [:on-camera-roll-get-photos (:edges %) (:page_info %) end-cursor])))})))
(re-frame/reg-fx
:camera-roll-image-selected
(fn [[image chat-id]]
(resize-photo (:uri image) #(re-frame/dispatch [:photo-selector/image-selected chat-id image %]))))
(defn get-albums
[callback]
(let [albums (atom {:smart-albums []
:my-albums []})]
;; Get the "recent" album first
(cameraroll/get-photos
{:first 1 :groupTypes "All"}
(fn [res-recent]
(swap! albums assoc
:smart-albums
[{:title (i18n/label :t/recent)
:uri (get-in (first (:edges res-recent)) [:node :image :uri])}])
;; Get albums, then loop over albums and get each one's cover (first photo)
(cameraroll/get-albums
{:assetType "All"}
(fn [res-albums]
(let [response-count (count res-albums)]
(if (pos? response-count)
(doseq [album res-albums]
(cameraroll/get-photos
{:first 1 :groupTypes "Albums" :groupName (:title album)}
(fn [res]
(let [uri (get-in (first (:edges res)) [:node :image :uri])]
(swap! albums update :my-albums conj (merge album {:uri uri}))
(when (= (count (:my-albums @albums)) response-count)
(swap! albums update :my-albums #(sort-by :title %))
(callback @albums))))))
(callback @albums)))))))))
(re-frame/reg-fx
:camera-roll-get-albums
(fn []
(get-albums #(re-frame/dispatch [:on-camera-roll-get-albums %]))))
(rf/defn on-camera-roll-get-albums
{:events [:on-camera-roll-get-albums]}
[{:keys [db]} albums]
{:db (assoc db :camera-roll/albums albums)})
(rf/defn camera-roll-get-albums
{:events [:photo-selector/camera-roll-get-albums]}
[_]
{:camera-roll-get-albums nil})
(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 image-selected
{:events [:photo-selector/image-selected]}
[{:keys [db]} current-chat-id original {:keys [resized-uri width height]}]
{:db
(update-in db
[:chat/inputs current-chat-id :metadata :sending-image (:uri original)]
merge
original
{:resized-uri resized-uri
:width width
:height height})})
(rf/defn on-camera-roll-get-photos
{:events [:on-camera-roll-get-photos]}
[{:keys [db]} photos page-info end-cursor]
(let [photos_x (when end-cursor (:camera-roll/photos db))]
{:db (-> db
(assoc :camera-roll/photos (concat photos_x (map #(get-in % [:node :image]) photos)))
(assoc :camera-roll/end-cursor (:end_cursor page-info))
(assoc :camera-roll/has-next-page (:has_next_page page-info))
(assoc :camera-roll/loading-more false))}))
(rf/defn get-photos-for-selected-album
{:events [:photo-selector/get-photos-for-selected-album]}
[{:keys [db]} end-cursor]
{:camera-roll-request-permissions-and-get-photos [21 end-cursor
(or (:camera-roll/selected-album db)
(i18n/label :t/recent))]})
(rf/defn camera-roll-loading-more
{:events [:photo-selector/camera-roll-loading-more]}
[{:keys [db]} is-loading]
{:db (assoc db :camera-roll/loading-more is-loading)})
(rf/defn camera-roll-pick
{:events [:photo-selector/camera-roll-pick]}
[{: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)
(not (some #(= (:uri image) (:uri %)) images)))
{:camera-roll-image-selected [image current-chat-id]})))

View File

@ -15,17 +15,12 @@
:flex-direction :row
:left 0
:right 0
:top 0
:top 20
:justify-content :center
:z-index 1})
(defn clear-container
[]
{:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80)
:padding-horizontal 12
:padding-vertical 5
:border-radius 10
:position :absolute
(def clear-container
{:position :absolute
:right 20})
(defn close-button-container

View File

@ -1,150 +1,126 @@
(ns status-im2.contexts.chat.photo-selector.view
(:require
[react-native.gesture :as gesture]
[react-native.safe-area :as safe-area]
[react-native.platform :as platform]
[status-im2.constants :as constants]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[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.constants :as constants]
[status-im2.contexts.chat.photo-selector.style :as style]
[status-im.utils.core :as utils]
[quo.react]
[utils.re-frame :as rf]))
[status-im2.contexts.chat.photo-selector.album-selector.view :as album-selector]
utils.collection))
(defn on-press-confirm-selection
[selected]
(rf/dispatch [:chat.ui/clear-sending-images])
(doseq [item @selected]
(rf/dispatch [:chat.ui/camera-roll-pick item]))
(reset! selected [])
(rf/dispatch [:navigate-back]))
(defn bottom-gradient
[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 (:bottom 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 [])
:style (style/clear-container)
:accessibility-label :clear}
[quo/text {:weight :medium} (i18n/label :t/clear)]]))
(defn remove-selected
[coll item]
(vec (remove #(= (:uri item) (:uri %)) coll)))
(defn image
[item index _ {:keys [window-width selected]}]
[rn/touchable-opacity
{:active-opacity 1
:on-press (fn []
(if (some #(= (:uri item) (:uri %)) @selected)
(swap! selected remove-selected item)
(if (>= (count @selected) constants/max-album-photos)
(defn show-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)}])
:text (i18n/label :t/only-6-images)}]))
(defn on-press-confirm-selection
[selected close]
(rf/dispatch [:chat.ui/clear-sending-images])
(doseq [item selected]
(rf/dispatch [:photo-selector/camera-roll-pick item]))
(close))
(defn confirm-button
[selected-images sending-image close]
(when (not= selected-images sending-image)
[linear-gradient/linear-gradient
{:colors [:black :transparent]
:start {:x 0 :y 1}
:end {:x 0 :y 0}
:style (style/gradient-container (safe-area/get-bottom))}
[quo/button
{:style {:align-self :stretch
:margin-horizontal 20
:margin-top 12}
:on-press #(on-press-confirm-selection selected-images close)
:accessibility-label :confirm-selection}
(i18n/label :t/confirm-selection)]]))
(defn clear-button
[album? selected]
(when (and (not album?) (seq @selected))
[rn/view {:style style/clear-container}
[quo/button
{:type :grey
:size 32
:accessibility-label :clear
:on-press #(reset! selected [])}
(i18n/label :t/clear)]]))
(defn remove-selected
[coll item]
(vec (remove #(= (:uri item) (:uri %)) coll)))
(defn render-image
[item index _ {:keys [window-width selected]}]
(let [item-selected? (some #(= (:uri item) (:uri %)) @selected)]
[rn/touchable-opacity
{:on-press (fn []
(if item-selected?
(swap! selected remove-selected item)
(if (>= (count @selected) constants/max-album-photos)
(show-toast)
(swap! selected conj item))))
:accessibility-label (str "image-" index)}
[rn/image
{:source {:uri (:uri item)}
:style (style/image window-width index)}]
(when (some #(= (:uri item) (:uri %)) @selected)
[rn/view {:style (style/overlay window-width)}])
(when (some #(= (:uri item) (:uri %)) @selected)
(when item-selected?
[:<>
[rn/view {:style (style/overlay window-width)}]
[info-count/info-count
{:style style/image-count
:accessibility-label (str "count-" index)}
(inc (utils/first-index #(= (:uri item) (:uri %)) @selected))])])
(defn album-title
[photos? selected temporary-selected insets close]
(fn []
(let [selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent))]
[rn/touchable-opacity
{:style (style/title-container)
:active-opacity 1
:accessibility-label :album-title
:on-press (fn []
;; TODO: album-selector issue:
;; https://github.com/status-im/status-mobile/issues/15398
(if photos?
(do
(reset! temporary-selected @selected)
(rf/dispatch [:open-modal :album-selector {:insets insets}]))
(close)))}
[quo/text
{:weight :medium
:ellipsize-mode :tail
:number-of-lines 1
:style {:max-width 150}}
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)}]]])))
(inc (utils.collection/first-index #(= (:uri item) (:uri %)) @selected))]])]))
(defn photo-selector
[{:keys [scroll-enabled on-scroll close]}]
[:f>
(let [temporary-selected (reagent/atom [])
{:keys [insets]} (rf/sub [:get-screen-params])] ; used when switching albums
[{:keys [scroll-enabled on-scroll close] :as sheet}]
(rf/dispatch [:photo-selector/get-photos-for-selected-album])
(rf/dispatch [:photo-selector/camera-roll-get-albums])
(let [album? (reagent/atom false)
sending-image (into [] (vals (rf/sub [:chats/sending-image])))
selected-images (reagent/atom sending-image)
window-width (:width (rn/get-window))]
(fn []
(let [selected (reagent/atom []) ; currently selected
selected-images (rf/sub [:chats/sending-image])
selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent))]
(rn/use-effect
(fn []
(rf/dispatch [:chat.ui/camera-roll-get-photos 20 nil selected-album])
(if (seq selected-images)
(reset! selected (vec (vals selected-images)))
(reset! selected @temporary-selected)))
[selected-album])
[:f>
(fn []
(let [window-width (:width (rn/get-window))
camera-roll-photos (rf/sub [:camera-roll/photos])
(let [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 temporary-selected insets close]
[clear-button selected]]
has-next-page? (rf/sub [:camera-roll/has-next-page])
selected-album (or (rf/sub [:camera-roll/selected-album]) (i18n/label :t/recent))]
[rn/view {:style {:flex 1 :margin-top -20}}
[rn/view {:style style/buttons-container}
[quo/dropdown {:type :grey :size 32 :on-change #(swap! album? not) :selected @album?}
selected-album]
[clear-button @album? selected-images]]
(if @album?
[album-selector/album-selector sheet album? selected-album]
[:<>
[gesture/flat-list
{:key-fn identity
:render-fn image
:render-data {:window-width window-width :selected selected}
:render-fn render-image
:render-data {:window-width window-width :selected selected-images}
:data camera-roll-photos
:num-columns 3
:content-container-style {:width "100%"
:padding-bottom (+ (:bottom insets) 100)
:padding-bottom (+ (safe-area/get-bottom) 100)
:padding-top 64}
:on-scroll on-scroll
:scroll-enabled @scroll-enabled
:on-end-reached #(rf/dispatch [:camera-roll/on-end-reached end-cursor
selected-album loading?
has-next-page?])}]
[bottom-gradient selected-images insets selected]]))])))])
:on-end-reached (fn []
(when (and (not loading?) has-next-page?)
(rf/dispatch [:photo-selector/camera-roll-loading-more true])
(rf/dispatch [:photo-selector/get-photos-for-selected-album
end-cursor])))}]
[confirm-button @selected-images sending-image close]])]))))

View File

@ -1,76 +1,78 @@
(ns status-im2.contexts.quo-preview.dropdowns.dropdown
(:require [quo2.components.dropdowns.dropdown :as quo2]
[quo2.foundations.colors :as colors]
(:require [quo2.foundations.colors :as colors]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im2.contexts.quo-preview.preview :as preview]))
[status-im2.contexts.quo-preview.preview :as preview]
[quo2.core :as quo]))
(def descriptor
[{:label "Icon"
:key :icon
[{:label "Type:"
:key :type
:type :select
:options [{:key :main-icons/placeholder
:value "Placeholder"}
{:key :main-icons/locked
:value "Wallet"}]}
{:label "Disabled"
:key :disabled?
:type :boolean}
{:label "Default item"
:key :default-item
:type :text}
{:label "Use border?"
:key :use-border?
:type :boolean}
{:label "Border color"
:key :border-color
:type :select
:options (map
(fn [c]
{:key c
:value c})
(keys colors/customization))}
{:label "DD color"
:key :dd-color
:type :text}
{:label "Size"
:options [{:key :primary
:value "Primary"}
{:key :secondary
:value "Secondary"}
{:key :grey
:value "Grey"}
{:key :dark-grey
:value "Dark Grey"}
{:key :outline
:value "Outline"}
{:key :ghost
:value "Ghost"}
{:key :danger
:value "Danger"}
{:key :positive
:value "Positive"}]}
{:label "Size:"
:key :size
:type :select
:options [{:key :big
:value "big"}
{:key :medium
:value "medium"}
{:key :small
:value "small"}]}])
:options [{:key 56
:value "56"}
{:key 40
:value "40"}
{:key 32
:value "32"}
{:key 24
:value "24"}]}
{:label "Icon:"
:key :icon
:type :boolean}
{:label "Before icon:"
:key :before
:type :boolean}
{:label "Disabled:"
:key :disabled
:type :boolean}
{:label "Label"
:key :label
:type :text}])
(defn cool-preview
[]
(let [items ["Banana"
"Apple"
"COVID +18"
"Orange"
"Kryptonite"
"BMW"
"Meh"]
state (reagent/atom {:icon :main-icons/placeholder
:default-item "item1"
:use-border? false
:dd-color (colors/custom-color :purple 50)
:size :big})
selected-item (reagent/cursor state [:default-item])
on-select #(reset! selected-item %)]
(let [state (reagent/atom {:label "Press Me"
:size 40})
label (reagent/cursor state [:label])
before (reagent/cursor state [:before])
icon (reagent/cursor state [:icon])]
(fn []
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
[rn/view {:padding-bottom 150}
[preview/customizer state descriptor]
[rn/view
{:padding-vertical 60
:align-items :center}
[rn/text (str "Selected item: " @selected-item)]
[quo2/dropdown
(merge @state
{:on-select on-select
:items items})]]]])))
:flex-direction :row
:justify-content :center}
[quo/dropdown
(merge (dissoc @state
:theme
:before
:after)
{:on-press #(println "Hello world!")}
(when @before
{:before :i/placeholder}))
(if @icon :i/placeholder @label)]]]])))
(defn preview-dropdown
[]

View File

@ -18,7 +18,8 @@
status-im2.contexts.syncing.events
status-im2.contexts.chat.events
status-im2.common.password-authentication.events
status-im2.contexts.communities.overview.events))
status-im2.contexts.communities.overview.events
status-im2.contexts.chat.photo-selector.events))
(re-frame/reg-cofx
:now

View File

@ -5,7 +5,6 @@
[status-im2.contexts.add-new-contact.views :as add-new-contact]
[status-im2.contexts.chat.lightbox.view :as lightbox]
[status-im2.contexts.chat.messages.view :as chat]
[status-im2.contexts.chat.photo-selector.album-selector.view :as album-selector]
[status-im2.contexts.chat.photo-selector.view :as photo-selector]
[status-im2.contexts.communities.discover.view :as communities.discover]
[status-im2.contexts.communities.overview.view :as communities.overview]
@ -77,10 +76,6 @@
:options {:sheet? true}
:component photo-selector/photo-selector}
{:name :album-selector
:options {:sheet? true}
:component album-selector/album-selector}
{:name :new-contact
:options {:sheet? true}
:component add-new-contact/new-contact}