Fix chat permission-context (#19284)

* feat: moved permission-context logic to quo component

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* feat: multiple-token-gating and previews

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* fix: refactored quo component

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* feat: request join community on press

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* feat: added shadow (rn-shadow-2)

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* fix: formatting

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* test: added tests for permission-context

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* feat: added blur

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* fix: adjusted shadows

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* fix: added shadow mocks

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* fix: shadow rendered below the context

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* fix: addressed review comments

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>

* fix: replaced seq with string/blank?

---------

Signed-off-by: Cristian Lungu <lungucristian95@gmail.com>
This commit is contained in:
Lungu Cristian 2024-04-08 17:14:07 +03:00 committed by GitHub
parent 74e84644aa
commit aeef913f63
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 349 additions and 110 deletions

View File

@ -150,3 +150,7 @@ fixed because even bugfix version upgrade causes runtime errors with current ver
## "react-native-draggable-flatlist": "https://github.com/computerjazz/react-native-draggable-flatlist"
A drag-and-drop-enabled FlatList component for React Native
## "react-native-shadow-2": "https://github.com/SrBrahma/react-native-shadow-2"
A shadow component that is consistent across both platforms

View File

@ -54,6 +54,7 @@
"react-native-permissions": "3.8.0",
"react-native-reanimated": "3.6.1",
"react-native-redash": "18.1.0",
"react-native-shadow-2": "^7.0.8",
"react-native-shake": "^3.3.1",
"react-native-share": "10.0.2",
"react-native-static-safe-area-insets": "^2.2.0",

View File

@ -348,6 +348,9 @@
(def react-native-blur
(clj->js {:BlurView {}}))
(def react-native-shadow-2
(clj->js {:Shadow {}}))
(def react-native-camera-roll
(clj->js {:default #js {}
:CameraRoll #js {}}))
@ -409,6 +412,7 @@
"@react-native-community/audio-toolkit" react-native-audio-toolkit
"@react-native-clipboard/clipboard" react-native-clipboard
"react-native-share" react-native-share
"react-native-shadow-2" react-native-shadow-2
"@react-native-async-storage/async-storage" async-storage
"react-native-svg" react-native-svg
"react-native-transparent-video" react-native-transparent-video

View File

@ -1,14 +1,68 @@
(ns quo.components.drawers.permission-context.component-spec
(:require
[quo.components.drawers.permission-context.view :as permission-context]
[react-native.core :as rn]
[test-helpers.component :as h]))
(h/describe "permission context"
(h/test "it tests the default render"
(h/render [permission-context/view
[rn/text
{:accessibility-label :accessibility-id}
"a sample label"]])
(-> (js/expect (h/get-by-label-text :accessibility-id))
(.toBeTruthy))))
(js/beforeEach (fn []
(-> (js/jest.spyOn js/console "error")
(.mockImplementation js/jest.fn))))
(h/describe "action type"
(h/test "shows the label"
(h/render [permission-context/view
{:type :action
:action-label "test"
:action-icon :i/communities}])
(-> (js/expect (h/get-by-text "test"))
(.toBeTruthy)))
(h/test "fails due to missing props"
(-> (js/expect
(fn []
(h/render [permission-context/view
{:type :action}])))
(.toThrow))))
(h/describe "single-token-gating type"
(h/test "shows the token-tag container"
(h/render [permission-context/view
{:type :single-token-gating
:token-value "23"
:token-symbol :eth}])
(-> (js/expect (h/get-by-label-text :permission-context-single-token))
(.toBeTruthy)))
(h/test "fails due to wrong props"
(-> (js/expect
(fn []
(h/render [permission-context/view
{:type :single-token-gating
:action-label "test"
:action-icon :i/communities}])))
(.toThrow))))
(h/describe "multiple-token-gating"
(h/test "shows the token-tag container"
(h/render [permission-context/view
{:type :multiple-token-gating
:token-groups [[:eth :snt] [:snt :eth :snt]]}])
(-> (js/expect (h/get-by-label-text :permission-context-multiple-token))
(.toBeTruthy)))
(h/test "fails if token-groups is empty"
(-> (js/expect
(fn []
(h/render [permission-context/view
{:type :multiple-token-gating
:token-groups []}])))
(.toThrow)))
(h/test "fails due to wrong props"
(-> (js/expect
(fn []
(h/render [permission-context/view
{:type :multiple-token-gating
:token-value "23"
:token-symbol :eth}])))
(.toThrow)))))

View File

@ -0,0 +1,34 @@
(ns quo.components.drawers.permission-context.schema)
(def ^:private ?base
[:map {:closed true}
[:type [:enum :action :single-token-gating :multiple-token-gating]]
[:blur? {:optional true} [:maybe :boolean]]
[:container-style {:optional true} [:maybe :map]]
[:on-press {:optional true} [:maybe fn?]]])
(def ^:private ?action
[:map {:closed true}
[:action-label :string]
[:action-icon [:qualified-keyword {:namespace :i}]]])
(def ^:private ?token-symbol [:or :keyword :string])
(def ^:private ?single-token-gating
[:map {:closed true}
[:token-value :string]
[:token-symbol ?token-symbol]])
(def ^:private ?multiple-token-gating
[:map {:closed true}
[:token-groups
[:sequential {:min 1} [:sequential {:min 1} ?token-symbol]]]])
(def ?schema
[:=>
[:cat
[:multi {:dispatch :type}
[:action [:merge ?base ?action]]
[:single-token-gating [:merge ?base ?single-token-gating]]
[:multiple-token-gating [:merge ?base ?multiple-token-gating]]]]
:any])

View File

@ -3,21 +3,31 @@
[quo.foundations.colors :as colors]
[react-native.safe-area :as safe-area]))
(def radius 20)
(def ^:private radius 20)
(def blur-container
{:background-color :transparent
:position :absolute
:top 0
:left 0
:right 0
:bottom 0})
(defn container
[]
[blur? theme]
{:flex-direction :row
:background-color (colors/theme-colors colors/white colors/neutral-90)
:overflow :hidden
:background-color (when-not blur?
(colors/theme-colors colors/white
colors/neutral-95
theme))
:padding-top 12
:padding-bottom (+ 12 (safe-area/get-bottom))
:justify-content :center
:padding-horizontal 20
:shadow-offset {:width 0
:height 2}
:shadow-radius radius
:border-top-left-radius radius
:border-top-right-radius radius
:elevation 2
:shadow-opacity 1
:shadow-color colors/shadow})
:border-top-right-radius radius})
(def token-group
{:flex-direction :row
:margin-left 3})

View File

@ -1,13 +1,102 @@
(ns quo.components.drawers.permission-context.view
(:require
[clojure.string :as string]
[quo.components.buttons.button.view :as button]
[quo.components.drawers.permission-context.schema :as component-schema]
[quo.components.drawers.permission-context.style :as style]
[quo.components.list-items.preview-list.view :as preview-list]
[quo.components.markdown.text :as text]
[quo.components.tags.number-tag.view :as number-tag]
[quo.components.tags.token-tag.view :as token-tag]
[quo.foundations.colors :as colors]
[react-native.core :as rn]))
[quo.theme]
[react-native.blur :as blur]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.shadow :as shadow]
[schema.core :as schema]
[utils.i18n :as i18n]))
(defn- single-token-gating
[{:keys [blur? token-value token-symbol]}]
[rn/view
{:style {:flex-direction :row}
:accessibility-label :permission-context-single-token}
[text/text {:style {:margin-right 3}}
(i18n/label :t/hold-to-post-1)]
[token-tag/view
{:size :size-24
:option false
:blur? blur?
:token-value token-value
:token-symbol token-symbol}]
[text/text {:style {:margin-left 3}}
(i18n/label :t/hold-to-post-2)]])
(defn- token-group
[blur? idx group]
^{:key idx}
[rn/view {:style style/token-group}
(when-not (zero? idx)
[text/text {:style {:margin-right 3}}
(string/lower-case (i18n/label :t/or))])
[preview-list/view
{:type :tokens
:blur? blur?
:size :size-24} group]])
(defn- multiple-token-gating
[{:keys [token-groups blur?]}]
(let [visible-token-groups (take 2 token-groups)
extra-groups? (-> token-groups count (> 2))]
[rn/view
{:style {:flex-direction :row}
:accessibility-label :permission-context-multiple-token}
[text/text (i18n/label :t/hold-to-post-1)]
[rn/view {:style {:flex-direction :row}}
(map-indexed (partial token-group blur?) visible-token-groups)]
(when extra-groups?
[:<>
[text/text {:style {:margin-horizontal 3}}
(string/lower-case (i18n/label :t/or))]
[number-tag/view
{:size :size-24
:number "9999" ;; show the options tag
:blur? blur?
:type :rounded}]])
[text/text {:style {:margin-left 3}}
(i18n/label :t/hold-to-post-2)]]))
(defn- view-internal
[{:keys [on-press blur? container-style] :as props}]
(let [theme (quo.theme/use-theme-value)
context-type (:type props)]
[shadow/view
{:offset [0 4]
:paint-inside false
:start-color (colors/theme-colors colors/neutral-100-opa-8 colors/neutral-100-opa-60)
:distance 25
:style {:align-self :stretch}}
[rn/view {:style (merge (style/container blur? theme) container-style)}
(when blur?
[blur/view
{:style style/blur-container
:blur-amount 20
:blur-radius (if platform/ios? 20 10)
:overlay-color (colors/theme-colors colors/white-70-blur colors/neutral-95-opa-70-blur)
:blur-type :transparent}])
[button/button
{:type :ghost
:size 24
:on-press on-press
:icon-left (when (= context-type :action)
(:action-icon props))}
(condp = context-type
:action (:action-label props)
:single-token-gating [single-token-gating
(select-keys props [:token-value :token-symbol :blur?])]
:multiple-token-gating [multiple-token-gating
(select-keys props [:token-groups :blur?])])]]]))
(def view (schema/instrument #'view-internal component-schema/?schema))
(defn view
[children on-press]
[rn/touchable-highlight
{:on-press on-press
:style (style/container)
:underlay-color (colors/theme-colors :transparent colors/neutral-95-opa-70)}
children])

View File

@ -93,6 +93,7 @@
;;100 with transparency
(def neutral-100-opa-0 (alpha neutral-100 0))
(def neutral-100-opa-5 (alpha neutral-100 0.05))
(def neutral-100-opa-8 (alpha neutral-100 0.08))
(def neutral-100-opa-10 (alpha neutral-100 0.1))
(def neutral-100-opa-30 (alpha neutral-100 0.3))
(def neutral-100-opa-50 (alpha neutral-100 0.5))

View File

@ -0,0 +1,6 @@
(ns react-native.shadow
(:require
["react-native-shadow-2" :refer [Shadow]]
[reagent.core :as reagent]))
(def view (reagent/adapt-react-class Shadow))

View File

@ -1,5 +1,6 @@
(ns status-im.contexts.chat.messenger.composer.view
(:require
[clojure.string :as string]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.blur :as blur]
@ -166,12 +167,10 @@
(defn composer
[props]
(let [{:keys [chat-id
contact-request-state
group-chat
able-to-send-message?]
:as chat} (rf/sub [:chats/current-chat-chat-view])]
(when (seq chat)
(let [current-chat-id (rf/sub [:chats/current-chat-id])
able-to-send-message? (rf/sub [:chats/able-to-send-message?])]
(when-not (string/blank? current-chat-id)
(if able-to-send-message?
[:f> f-composer props]
[contact-requests.bottom-drawer/view chat-id contact-request-state group-chat]))))
[contact-requests.bottom-drawer/view
{:contact-id current-chat-id}]))))

View File

@ -9,34 +9,45 @@
[utils.re-frame :as rf]))
(defn view
[contact-id contact-request-state group-chat]
(let [customization-color (rf/sub [:profile/customization-color])
[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity contact-id])]
[{:keys [contact-id]}]
(let [customization-color (rf/sub [:profile/customization-color])
[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity
contact-id])
{:keys [contact-request-state]} (rf/sub [:chats/current-chat-chat-view])
chat-type (rf/sub [:chats/chat-type])
contact-request-send? (or (not contact-request-state)
(= contact-request-state
constants/contact-request-state-none))
contact-request-received? (= contact-request-state
constants/contact-request-state-received)
contact-request-pending? (= contact-request-state
constants/contact-request-state-sent)]
[rn/view {:style style/container}
[quo/permission-context
[quo/button
{:type :ghost
:size 24
:on-press #(rf/dispatch [:chat.ui/show-profile contact-id])
:icon-left (if (= contact-request-state constants/contact-request-state-sent)
:i/pending-state
:i/add-user)}
(cond
group-chat
(i18n/label :t/group-chat-not-member)
{:blur? true
:on-press (condp = chat-type
:community-chat #(rf/dispatch [:navigate-to :community-account-selection])
#(rf/dispatch [:chat.ui/show-profile contact-id]))
:type :action
:action-icon (cond
(= chat-type :community-chat) :i/communities
contact-request-pending? :i/pending-state
:else :i/add-user)
:action-label (cond
(= chat-type :community-chat)
(i18n/label :t/join-community-to-post)
(or (not contact-request-state)
(= contact-request-state
constants/contact-request-state-none))
(i18n/label :t/contact-request-chat-add {:name primary-name})
(= chat-type :group-chat)
(i18n/label :t/group-chat-not-member)
(= contact-request-state
constants/contact-request-state-received)
(str primary-name " sent you a contact request")
contact-request-send?
(i18n/label :t/contact-request-chat-add {:name primary-name})
(= contact-request-state
constants/contact-request-state-sent)
(i18n/label :t/contact-request-chat-pending))]]
contact-request-received?
(i18n/label :t/contact-request-chat-received {:name primary-name})
contact-request-pending?
(i18n/label :t/contact-request-chat-pending))}]
[quo/floating-shell-button
{:jump-to
{:on-press #(rf/dispatch [:shell/navigate-to-jump-to])

View File

@ -1,64 +1,58 @@
(ns status-im.contexts.preview.quo.drawers.permission-drawers
(:require
[quo.core :as quo]
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[status-im.common.resources :as resources]))
[reagent.core :as reagent]
[status-im.contexts.preview.quo.preview :as preview]))
(def token-icon (resources/get-mock-image :status-logo))
(def descriptor
[{:key :type
:type :select
:options [{:key :action}
{:key :single-token-gating}
{:key :multiple-token-gating}]}
{:key :blur?
:type :boolean}])
(defn example-1
[]
[:<>
[quo/text {:style {:margin-right 4}} "Hold"]
[quo/permission-tag
{:size 24
:locked? false
:tokens [{:id 1
:group [{:id 1 :token-icon token-icon}
{:id 2 :token-icon token-icon}
{:id 3 :token-icon token-icon}
{:id 4 :token-icon token-icon}
{:id 5 :token-icon token-icon}]}]
:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80)}]
[quo/text
{:style {:margin-left 4
:margin-right 4}} "Or"]
[quo/permission-tag
{:size 24
:locked? false
:tokens [{:id 1
:group [{:id 1 :token-icon token-icon}
{:id 2 :token-icon token-icon}
{:id 3 :token-icon token-icon}
{:id 4 :token-icon token-icon}
{:id 5 :token-icon token-icon}]}]
:background-color (colors/theme-colors colors/neutral-10 colors/neutral-80)}]
[quo/text {:style {:margin-left 4}} "To post"]])
(def single-token-gating-props
[{:token-value "50"
:token-symbol "SNT"}
{:token-value "0.01"
:token-symbol "ETH"}])
(defn example-2
[]
[:<>
[quo/text {:style {:margin-right 4}} "Hold"]
[quo/token-tag
{:size :size-24
:token-value 5
:token-symbol "ETH"}]
[quo/text {:style {:margin-left 4}} "To post"]])
(def multiple-token-gating-props
[{:token-groups [[:eth :knc :snt :rare :mana]]}
{:token-groups [[:snt :eth :knc]
[:eth :knc :snt :rare :mana]]}
{:token-groups [[:snt :eth :knc :rare :mana :snt]
[:eth :knc :snt :rare :mana]
[:rare :mana]]}])
(defn example-3
[]
[:<>
[quo/icon :i/communities {:color (colors/theme-colors colors/neutral-100 colors/white)}]
[quo/text {:style {:margin-right 4}} "Join community to post"]])
(def action-props
[{:action-label "You sure you know this guy?"
:action-icon :i/pending-state}
{:action-label "Join community to post"
:action-icon :i/communities}])
(defn view
[]
(fn []
[:<>
[rn/view {:margin-top 60}
[quo/permission-context [example-1] #(js/alert "drawer pressed")]]
[rn/view {:margin-top 60}
[quo/permission-context [example-2] #(js/alert "drawer pressed")]]
[rn/view {:margin-top 60}
[quo/permission-context [example-3] #(js/alert "drawer pressed")]]]))
(let [state (reagent/atom {:type :multiple-token-gating
:blur? true})
blur? (reagent/cursor state [:blur?])
type (reagent/cursor state [:type])]
(fn []
[preview/preview-container
{:state state
:blur? @blur?
:show-blur-background? true
:component-container-style {:padding-horizontal 0}
:blur-container-style {:padding-horizontal 0}
:descriptor descriptor}
(->> (condp = @type
:action action-props
:single-token-gating single-token-gating-props
:multiple-token-gating multiple-token-gating-props)
(map-indexed (fn [idx props]
[:<>
^{:key idx}
[quo/permission-context
(merge @state props {:container-style {:margin-bottom 8}})]])))])))

View File

@ -202,6 +202,21 @@
(get-in contacts [chat-id :contact-request-state])))
(not (contains? blocked-users-set chat-id))))))))
(re-frame/reg-sub
:chats/able-to-send-message?
:<- [:chats/current-chat]
(fn [current-chat]
(get current-chat :able-to-send-message?)))
(re-frame/reg-sub
:chats/chat-type
:<- [:chats/current-chat]
(fn [current-chat]
(condp apply [current-chat]
chat.events/community-chat? :community-chat
chat.events/group-chat? :group-chat
:chat)))
(re-frame/reg-sub
:chats/current-chat-chat-view
:<- [:chats/current-chat]

View File

@ -14,6 +14,7 @@ const transformIgnorePatterns = () => {
'react-native-reanimated',
'react-native-redash',
'react-native-redash',
'react-native-shadow-2',
'react-native-shake',
'react-native-static-safe-area-insets',
].join('|');

View File

@ -692,6 +692,8 @@
"history": "History",
"history-nodes": "Status nodes",
"hold-card": "Hold card to the back\n of your phone",
"hold-to-post-1": "Hold",
"hold-to-post-2": "to post",
"home": "Home",
"hooks": "Hooks",
"how-to-scan": "How to scan",
@ -756,6 +758,7 @@
"join-open-community": "Join Community",
"joined-community": "You joined “{{community}}”",
"join-decentralised-communities": "Join Decentralized Communities",
"join-community-to-post": "Join community to post",
"http-gateway-error": "Oops, request failed!",
"sign-request-failed": "Could not sign message",
"simple": "Simple",
@ -2128,6 +2131,7 @@
"contact-request-chat-pending": "Your contact request is pending",
"contact-profile-request-pending": "Contact request pending",
"contact-request-chat-add": "Add {{name}} as contact to send a message",
"contact-request-chat-received": "{{name}} sent you a contact request",
"join-request": "Join request",
"join-one-user": "Join {{user}}",
"join-two-users": "Join {{user1}} and {{user2}}",

View File

@ -4080,6 +4080,11 @@ color-support@^1.1.2:
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
colord@2.9.2:
version "2.9.2"
resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.2.tgz#25e2bacbbaa65991422c07ea209e2089428effb1"
integrity sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==
colorette@^1.0.7:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40"
@ -8139,6 +8144,13 @@ react-native-safe-modules@^1.0.3:
dependencies:
dedent "^0.6.0"
react-native-shadow-2@^7.0.8:
version "7.0.8"
resolved "https://registry.yarnpkg.com/react-native-shadow-2/-/react-native-shadow-2-7.0.8.tgz#df365ba1d8d22b7c63f1afc66d75308c3d6af4e1"
integrity sha512-6QlmbJvHCmDGa85jwtfrVxhro1oGm/ezTlciom0A+i0UFAzxlvmRiMHyKXg1hEB2PQkKPoG6ZpANju4mpqPqGA==
dependencies:
colord "2.9.2"
react-native-shake@^3.3.1:
version "3.4.0"
resolved "https://registry.yarnpkg.com/react-native-shake/-/react-native-shake-3.4.0.tgz#3fa8f682651104b39c0b6c199bfa2ab10b36ce28"