Implement Token Gating (#13899)

* feat: implement token gating component

* fix: add .calva to gitignore
This commit is contained in:
Christoph Pader 2022-12-01 14:30:07 +01:00 committed by GitHub
parent 5947769881
commit f18044c9dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 767 additions and 204 deletions

4
.gitignore vendored
View File

@ -178,4 +178,6 @@ test/appium/tests/users.py
/lib/
## visual tests
/artifacts
/artifacts
/.calva/

View File

@ -7,147 +7,149 @@
[quo2.components.icon :as quo2.icons]))
(def themes
{:light {:primary {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/primary-50
:pressed colors/primary-60
:disabled colors/primary-50}}
:secondary {:icon-color colors/primary-50
:label {:style {:color colors/primary-50}}
:background-color {:default colors/primary-50-opa-20
:pressed colors/primary-50-opa-40
:disabled colors/primary-50-opa-20}}
:grey {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}}
:background-color {:default colors/neutral-10
:pressed colors/neutral-20
:disabled colors/neutral-10}}
:dark-grey {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}}
:background-color {:default colors/neutral-20
:pressed colors/neutral-30
:disabled colors/neutral-20}}
:outline {:icon-color colors/neutral-50
:icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}}
:border-color {:default colors/neutral-20
:pressed colors/neutral-40
:disabled colors/neutral-20}}
:ghost {:icon-color colors/neutral-50
:icon-secondary-color colors/neutral-50
:label {:style {:color colors/neutral-100}}
:background-color {:pressed colors/neutral-10}}
:danger {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/danger-50
:pressed colors/danger-60
:disabled colors/danger-50}}
:positive {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/success-50
:pressed colors/success-60
:disabled colors/success-50-opa-30}}
:photo-bg {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label {:style {:color colors/neutral-100}}
:background-color {:default colors/white-opa-40
:pressed colors/white-opa-50
:disabled colors/white-opa-40}}
:blur-bg {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label {:style {:color colors/neutral-100}}
:background-color {:default colors/neutral-80-opa-5
:pressed colors/neutral-80-opa-10
:disabled colors/neutral-80-opa-5}}
:blur-bg-outline {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label {:style {:color colors/neutral-100}}
:border-color {:default colors/neutral-80-opa-10
:pressed colors/neutral-80-opa-20
:disabled colors/neutral-80-opa-10}}
{:light {:primary {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/primary-50
:pressed colors/primary-60
:disabled colors/primary-50}}
:secondary {:icon-color colors/primary-50
:label-color colors/primary-50
:background-color {:default colors/primary-50-opa-20
:pressed colors/primary-50-opa-40
:disabled colors/primary-50-opa-20}}
:grey {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-50
:label-color colors/neutral-100
:background-color {:default colors/neutral-10
:pressed colors/neutral-20
:disabled colors/neutral-10}}
:dark-grey {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-50
:label-color colors/neutral-100
:background-color {:default colors/neutral-20
:pressed colors/neutral-30
:disabled colors/neutral-20}}
:outline {:icon-color colors/neutral-50
:icon-secondary-color colors/neutral-50
:label-color colors/neutral-100
:border-color {:default colors/neutral-20
:pressed colors/neutral-40
:disabled colors/neutral-20}}
:ghost {:icon-color colors/neutral-50
:icon-secondary-color colors/neutral-50
:label-color colors/neutral-100
:background-color {:pressed colors/neutral-10}}
:danger {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/danger-50
:pressed colors/danger-60
:disabled colors/danger-50}}
:positive {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/success-50
:pressed colors/success-60
:disabled colors/success-50-opa-30}}
:photo-bg {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label-color colors/neutral-100
:background-color {:default colors/white-opa-40
:pressed colors/white-opa-50
:disabled colors/white-opa-40}}
:blur-bg {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label-color colors/neutral-100
:background-color {:default colors/neutral-80-opa-5
:pressed colors/neutral-80-opa-10
:disabled colors/neutral-80-opa-5}}
:blur-bg-outline {:icon-color colors/neutral-100
:icon-secondary-color colors/neutral-80-opa-40
:label-color colors/neutral-100
:border-color {:default colors/neutral-80-opa-10
:pressed colors/neutral-80-opa-20
:disabled colors/neutral-80-opa-10}}
:shell {:icon-color colors/white
:label {:style {:color colors/white}}
:label-color colors/white
:background-color {:default colors/neutral-95
:pressed colors/neutral-95
:disabled colors/neutral-95}}}
:dark {:primary {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/primary-60
:pressed colors/primary-50
:disabled colors/primary-60}}
:secondary {:icon-color colors/primary-50
:label {:style {:color colors/primary-50}}
:background-color {:default colors/primary-50-opa-20
:pressed colors/primary-50-opa-30
:disabled colors/primary-50-opa-20}}
:grey {:icon-color colors/white
:icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}}
:background-color {:default colors/neutral-80
:pressed colors/neutral-60
:disabled colors/neutral-80}}
:dark-grey {:icon-color colors/white
:icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}}
:background-color {:default colors/neutral-70
:pressed colors/neutral-60
:disabled colors/neutral-70}}
:outline {:icon-color colors/neutral-40
:icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}}
:border-color {:default colors/neutral-70
:pressed colors/neutral-60
:disabled colors/neutral-70}}
:ghost {:icon-color colors/neutral-40
:icon-secondary-color colors/neutral-40
:label {:style {:color colors/white}}
:background-color {:pressed colors/neutral-80}}
:danger {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/danger-60
:pressed colors/danger-50
:disabled colors/danger-60}}
:positive {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/success-60
:pressed colors/success-50
:disabled colors/success-60-opa-30}}
:photo-bg {:icon-color colors/white
:icon-secondary-color colors/neutral-30
:label {:style {:color colors/white}}
:background-color {:default colors/neutral-80-opa-40
:pressed colors/neutral-80-opa-50
:disabled colors/neutral-80-opa-40}}
:blur-bg {:icon-color colors/white
:icon-secondary-color colors/white-opa-40
:label {:style {:color colors/white}}
:background-color {:default colors/white-opa-5
:pressed colors/white-opa-10
:disabled colors/white-opa-5}}
:blur-bg-outline {:icon-color colors/white
:icon-secondary-color colors/white-opa-40
:label {:style {:color colors/white}}
:border-color {:default colors/white-opa-10
:pressed colors/white-opa-20
:disabled colors/white-opa-5}}
:shell {:icon-color colors/white
:label {:style {:color colors/white}}
:background-color {:default colors/neutral-95}}}})
:dark {:primary {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/primary-60
:pressed colors/primary-50
:disabled colors/primary-60}}
:secondary {:icon-color colors/primary-50
:label-color colors/primary-50
:background-color {:default colors/primary-50-opa-20
:pressed colors/primary-50-opa-30
:disabled colors/primary-50-opa-20}}
:grey {:icon-color colors/white
:icon-secondary-color colors/neutral-40
:label-color colors/white
:background-color {:default colors/neutral-80
:pressed colors/neutral-60
:disabled colors/neutral-80}}
:dark-grey {:icon-color colors/white
:icon-secondary-color colors/neutral-40
:label-color colors/white
:background-color {:default colors/neutral-70
:pressed colors/neutral-60
:disabled colors/neutral-70}}
:outline {:icon-color colors/neutral-40
:icon-secondary-color colors/neutral-40
:label-color colors/white
:border-color {:default colors/neutral-70
:pressed colors/neutral-60
:disabled colors/neutral-70}}
:ghost {:icon-color colors/neutral-40
:icon-secondary-color colors/neutral-40
:label-color colors/white
:background-color {:pressed colors/neutral-80}}
:danger {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/danger-60
:pressed colors/danger-50
:disabled colors/danger-60}}
:positive {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/success-60
:pressed colors/success-50
:disabled colors/success-60-opa-30}}
:photo-bg {:icon-color colors/white
:icon-secondary-color colors/neutral-30
:label-color colors/white
:background-color {:default colors/neutral-80-opa-40
:pressed colors/neutral-80-opa-50
:disabled colors/neutral-80-opa-40}}
:blur-bg {:icon-color colors/white
:icon-secondary-color colors/white-opa-40
:label-color colors/white
:background-color {:default colors/white-opa-5
:pressed colors/white-opa-10
:disabled colors/white-opa-5}}
:blur-bg-outline {:icon-color colors/white
:icon-secondary-color colors/white-opa-40
:label-color colors/white
:border-color {:default colors/white-opa-10
:pressed colors/white-opa-20
:disabled colors/white-opa-5}}
:shell {:icon-color colors/white
:label-color colors/white
:background-color {:default colors/neutral-95}}}})
(defn shape-style-container [type icon size]
{:border-radius (if (and icon (#{:primary :secondary :danger} type))
24
(case size
56 12
40 12
32 10
24 8))})
(defn style-container [type size disabled background-color border-color icon above width before after]
(merge {:height size
:align-items :center
:justify-content :center
:flex-direction (if above :column :row)
:border-radius (if (and icon (#{:primary :secondary :danger} type))
24
(case size
56 12
40 12
32 10
24 8))
:background-color background-color
:padding-horizontal (when-not (or icon before after)
(case size 56 16 40 16 32 12 24 8))
@ -159,6 +161,7 @@
(case size 56 0 40 9 32 5 24 3))
:padding-bottom (when-not (or icon before after)
(case size 56 0 40 9 32 5 24 4))}
(shape-style-container type icon size)
(when width
{:width width})
(when icon
@ -169,14 +172,19 @@
(when disabled
{:opacity 0.3})))
(defn community-themed? [type community-color]
(and (= type :community) (string? community-color)))
(defn button
"with label
[button opts \"label\"]
opts
{:type :primary/:secondary/:grey/:dark-grey/:outline/:ghost/
:danger/:photo-bg/:blur-bg/:blur-bg-ouline/:shell
:danger/:photo-bg/:blur-bg/:blur-bg-ouline/:shell/:community
:size 40/32/24
:icon true/false
:community-color '#FFFFFF'
:community-text-color '#000000'
:before :icon-keyword
:after :icon-keyword}
@ -184,13 +192,13 @@
[button {:icon true} :main-icons/close-circle]"
[_ _]
(let [pressed (reagent/atom false)]
(fn [{:keys [on-press disabled type size before after above width
(fn [{:keys [on-press disabled type size community-color community-text-color before after above width
override-theme override-background-color
on-long-press accessibility-label icon icon-no-color style test-ID]
:or {type :primary
size 40}}
children]
(let [{:keys [icon-color icon-secondary-color background-color label border-color]}
(let [{:keys [icon-color icon-secondary-color background-color label-color border-color]}
(get-in themes [(or
override-theme
(theme/get-theme)) type])
@ -212,48 +220,65 @@
(reset! pressed nil))})
[rn/view {:style (merge
(style-container
type
size
disabled
(or override-background-color (get background-color state))
(get border-color state)
icon
above
width
before
after)
(shape-style-container type icon size)
{:background-color
(if (= state :pressed)
(colors/theme-colors colors/neutral-100 colors/white)
:transparent)}
style)}
(when above
[rn/view
[quo2.icons/icon above {:container-style {:margin-bottom 2}
:color icon-secondary-color
:size icon-size}]])
(when before
[rn/view
[quo2.icons/icon before {:container-style {:margin-left (if (= size 40) 12 8)
:margin-right 4}
[rn/view {:style (merge
(style-container
type
size
disabled
(or override-background-color (get background-color state))
(get border-color state)
icon
above
width
before
after)
(when
(community-themed? type community-color)
(merge
{:background-color community-color}
(when (= state :pressed) {:opacity 0.9}))))}
(when above
[rn/view
[quo2.icons/icon above {:container-style {:margin-bottom 2}
:color icon-secondary-color
:size icon-size}]])
[rn/view
(cond
(or icon icon-no-color)
[quo2.icons/icon children {:color icon-color
:no-color icon-no-color
:size icon-size}]
(when before
[rn/view
[quo2.icons/icon before {:container-style {:margin-left (if (= size 40) 12 8)
:margin-right 4}
:color icon-secondary-color
:size icon-size}]])
[rn/view
(cond
(or icon icon-no-color)
[quo2.icons/icon children {:color icon-color
:no-color icon-no-color
:size icon-size}]
(string? children)
[text/text (merge {:size (when (#{56 24} size) :paragraph-2)
:weight :medium
:number-of-lines 1}
label)
children]
(string? children)
[text/text {:size (when (#{56 24} size) :paragraph-2)
:weight :medium
:number-of-lines 1
:style {:color (if
(and
(community-themed? type community-color)
(string? community-text-color))
community-text-color
label-color)}}
(vector? children)
children)]
(when after
[rn/view
[quo2.icons/icon after {:container-style {:margin-left 4
:margin-right (if (= size 40) 12 8)}
:color icon-secondary-color
:size icon-size}]])]]))))
children]
(vector? children)
children)]
(when after
[rn/view
[quo2.icons/icon after {:container-style {:margin-left 4
:margin-right (if (= size 40) 12 8)}
:color icon-secondary-color
:size icon-size}]])]]]))))

View File

@ -69,10 +69,12 @@
:style {:margin-top (if (= size :large) 8 2)}}
description])])
(defn permission-tag-container [{:keys [locked? tokens]}]
(defn permission-tag-container [{:keys [locked? tokens on-press]}]
[permission/tag {:background-color (colors/theme-colors
colors/neutral-10
colors/neutral-80)
:locked? locked?
:tokens tokens
:size 24}])
:size 24
:on-press on-press}])

View File

@ -0,0 +1,259 @@
(ns quo2.components.community.token-gating
(:require [quo.react-native :as rn]
[quo2.components.avatars.channel-avatar :as channel-avatar]
[quo2.components.buttons.button :as button]
[quo2.components.icon :as icon]
[quo2.components.info.information-box :as information-box]
[quo2.components.markdown.text :as text]
[quo2.components.tags.token-tag :as token-tag]
[quo2.foundations.colors :as colors]
[status-im.i18n.i18n :as i18n]
[status-im.ui.components.fast-image :as fast-image]))
(def ^:private token-tag-horizontal-spacing 7)
(def token-tag-vertical-spacing 5)
(def styles {:container {:flex 1
:padding-horizontal 20}
:header-container {:flex-direction :row
:align-items :center}
:header-spacing-community {:margin-bottom 16}
:header-spacing-channel {:margin-bottom 24}
:header-avatar {:margin-right 8}
:header-community-avatar-image {:width 32 :height 32}
:header-title-container {:flex 1
:flex-direction :row
:align-items :center}
:header-title-lock {:margin-left 6}
:header-info-button {:margin-left 8}
:token-requirement-text-spacing {:margin-vertical 8}
:token-requirement-list-spacing {:padding-vertical (- 8 token-tag-vertical-spacing)}
:token-row {:flex-direction :row
:flex-wrap :wrap
:align-items :center}
:token-row-container {:flex-direction :row
:align-items :center
:margin-horizontal (- token-tag-horizontal-spacing)}
:token-row-container-no-shift {:margin-left 0}
:token-row-or-text {:margin-right 3}
:token-tag-spacing {:margin-horizontal token-tag-horizontal-spacing
:margin-vertical token-tag-vertical-spacing}
:divider {:height 1
:margin-horizontal -20
:margin-top (- 12 token-tag-vertical-spacing)
:margin-bottom 8}
:membership-request-denied {:margin-top 13}
:enter-button {:margin-top 16}
:info-text {:margin-top 12
:text-align :center
:padding-horizontal 20}})
(defn multiple-token-requirements? [token-requirement-lists]
(when (vector? (first token-requirement-lists))
(> (count token-requirement-lists) 1)))
(defn is-token-requirement-met? [token-requirement-list]
(and (seq token-requirement-list)
(every?
(fn [token]
(get token :is-sufficient?))
token-requirement-list)))
(defn are-multiple-token-requirements-met? [token-requirement-lists]
(if (multiple-token-requirements? token-requirement-lists)
(some is-token-requirement-met? token-requirement-lists)
(is-token-requirement-met? token-requirement-lists)))
(defn token-requirement-list-row [tokens community-color]
[rn/view {:style (merge
(get styles :token-row)
(get styles :token-requirement-list-spacing))}
(map-indexed (fn [token-index token]
(let [{:keys [token-img-src token amount is-sufficient? is-purchasable?]} token]
^{:key token-index}
[rn/view {:style (get styles :token-tag-spacing)}
[token-tag/token-tag {:token token
:value amount
:size 24
:border-color community-color
:is-required is-sufficient?
:is-purchasable is-purchasable?
:token-img-src token-img-src}]]))
tokens)])
(defn token-requirement-list [props community-color]
(let [{:keys [gate token-requirements-changed? required-tokens-lost?]} props
[gate-type token-requirement-lists] gate
multiple-token-requirements? (multiple-token-requirements? token-requirement-lists)
is-sufficient? (are-multiple-token-requirements-met? token-requirement-lists)
you-must-hold-label (if (= gate-type :join)
(cond
token-requirements-changed? :t/you-must-now-hold
required-tokens-lost? :t/you-must-always-hold
:else :t/you-must-hold)
:t/you-must-hold)
message-label (cond
(= gate-type :join)
(cond
token-requirements-changed? :t/community-join-requirements-changed
required-tokens-lost? :t/community-join-requirements-tokens-lost
:else (if is-sufficient? :t/community-join-requirements-met :t/community-join-requirements-not-met))
(= gate-type :read)
(if is-sufficient?
:t/community-channel-read-requirements-met
:t/community-channel-read-requirements-not-met)
(= gate-type :write)
(if is-sufficient?
:t/community-channel-write-requirements-met
:t/community-channel-write-requirements-not-met))]
[rn/view
[rn/view {:style (get styles :token-requirement-text-spacing)}
[text/text {:weight :medium
:number-of-lines 1}
(i18n/label
message-label)]
[text/text {:size :paragraph-2
:number-of-lines 1}
(i18n/label
you-must-hold-label)]]
(if multiple-token-requirements?
(map-indexed
(fn [token-requirement-index tokens]
^{:key token-requirement-index}
[rn/view {:style (merge
(get styles :token-row-container)
(when-not (= token-requirement-index 0)
(get styles :token-row-container-no-shift)))}
(when-not (= token-requirement-index 0)
[text/text {:style (get styles :token-row-or-text)
:size :paragraph-2} "or"])
[token-requirement-list-row tokens community-color]])
token-requirement-lists)
[rn/view {:style (get styles :token-row-container)}
[token-requirement-list-row token-requirement-lists community-color]])]))
(defn is-community-locked? [community]
(not (are-multiple-token-requirements-met? (get-in community [:gates :join]))))
(defn is-channel-locked? [channel]
(not (are-multiple-token-requirements-met?
(or (get-in channel [:gates :read])
(get-in channel [:gates :write])))))
(defn token-gating
"[token-gating opts]
opts
{
:community {
:name string
:community-avatar-img-src string
:community-color string
:community-text-color string
:token-requirements-changed? boolean
:required-tokens-lost? boolean
}
:channel {
:name string
:emoji string
:emoji-background-color :string
:on-enter-channel callback
:membership-request-denied? boolean
}
}"
[_ _]
(fn [{:keys [community channel]}]
(let [type (if (some? community) :community :channel)
{:keys [name
emoji
emoji-background-color
on-enter-channel
community-avatar-img-src
community-color
community-text-color
membership-request-denied?
token-requirements-changed?
required-tokens-lost?
gates]} (if (= type :community) community channel)
locked? (if
(= type :community)
(is-community-locked? community)
(is-channel-locked? channel))]
[rn/view {:style (merge (get styles :container) {:background-color (colors/theme-colors colors/white colors/neutral-90)})}
[rn/view {:style (merge
(get styles :header-container)
(if
(= type :community)
(get styles :header-spacing-community)
(get styles :header-spacing-channel)))}
[rn/view {:style (get styles :header-avatar)}
(if (= type :community)
[fast-image/fast-image {:source community-avatar-img-src
:style (get styles :header-community-avatar-image)}]
[channel-avatar/channel-avatar {:big? true
:locked? locked?
:emoji emoji
:emoji-background-color emoji-background-color}])]
[rn/view {:style (get styles :header-title-container)}
[text/text {:weight :semi-bold
:number-of-lines 1
:size :heading-1
:style (get styles :header-text)} (if (= type :community) name (str "# " name))]
(when (= type :community)
[icon/icon (if locked?
:main-icons2/locked
:main-icons2/unlocked)
{:container-style (get styles :header-title-lock)
:color (colors/theme-colors
colors/neutral-50
colors/neutral-40)}])]
[button/button {:type :outline
:size 32
:icon true
:style (get styles :header-info-button)} :main-icons2/info]]
(map-indexed (fn [gate-index gate]
^{:key gate-index}
[:<>
(when-not (= gate-index 0)
[rn/view {:style (merge
(get styles :divider)
{:background-color (colors/theme-colors
colors/neutral-10
colors/neutral-80)})}])
[token-requirement-list {:gate gate :token-requirements-changed? token-requirements-changed? :required-tokens-lost? required-tokens-lost?} community-color]])
gates)
(when (= type :channel)
(if membership-request-denied?
[information-box/information-box {:type :error
:icon :main-icons2/untrustworthy
:no-icon-color? true
:style (get styles :membership-request-denied)}
(i18n/label
:t/membership-request-denied)]
[:<>
[button/button {:type :community
:community-color community-color
:community-text-color community-text-color
:style (get styles :enter-button)
:disabled locked?
:on-press on-enter-channel}
(str "# " (i18n/label
:t/enter-channel))]
[text/text
{:size :paragraph-2
:style (merge
(get styles :info-text)
{:color (colors/theme-colors
colors/neutral-50
colors/neutral-40)})}
(i18n/label
:t/community-enter-channel-info)]]))])))

View File

@ -19,19 +19,21 @@
(defn info-message
"[info-message opts \"message\"]
opts
{:type :default/:success/:error
:size :default/:tiny
:icon :main-icons/info ;; info message icon
:text-color colors/white ;; text color override
:icon-color colors/white ;; icon color override"
[{:keys [type size icon text-color icon-color]} message]
{:type :default/:success/:error
:size :default/:tiny
:icon :main-icons/info ;; info message icon
:text-color colors/white ;; text color override
:icon-color colors/white ;; icon color override
:no-icon-color? false ;; disable tint color for icon"
[{:keys [type size icon text-color icon-color no-icon-color?]} message]
(let [weight (if (= size :default) :regular :medium)
size (if (= size :default) :paragraph-2 :label)
text-color (or text-color (get-color type))
icon-color (or icon-color text-color)]
[rn/view {:style {:flex-direction :row
:flex 1}}
[quo2.icons/icon icon {:color icon-color
[quo2.icons/icon icon {:color icon-color
:no-color no-icon-color?
:size 12
:container-style {:margin-top 3}}]
[text/text {:size size

View File

@ -49,11 +49,12 @@
:closed? true/false ;; information box's state
:id :information-box-id ;; unique id (required for closable? information box)
:icon :main-icons/info ;; information box icon
:no-icon-color? false ;; disable tint color for icon
:style style
:button-label \"PressMe\" ;; add action button with label
:on-button-press action ;; (required for information box with button-label)
:on-close on-close ;; (optional on-close call)"
[{:keys [type closable? closed? id icon style button-label on-button-press on-close]} message]
[{:keys [type closable? closed? id icon style button-label on-button-press on-close no-icon-color?]} message]
(let [background-color (get-color-by-type type :bg)
border-color (get-color-by-type type :border)
icon-color (get-color-by-type type :icon)
@ -73,7 +74,8 @@
[info-message/info-message {:size :default
:icon icon
:text-color text-color
:icon-color icon-color} message]
:icon-color icon-color
:no-icon-color? no-icon-color?} message]
(when closable?
[rn/touchable-opacity
{:on-press on-close

View File

@ -1,14 +1,14 @@
(ns quo2.components.list-items.channel
(:require [react-native.core :as rn]
[quo2.foundations.colors :as colors]
(:require [quo2.components.avatars.channel-avatar :as channel-avatar]
[quo2.components.counter.counter :as quo2.counter]
[quo2.components.icon :as quo2.icons]
[quo2.components.avatars.channel-avatar :as channel-avatar]
[quo2.components.markdown.text :as quo2.text]
[quo2.theme :as theme]))
[quo2.foundations.colors :as colors]
[quo2.theme :as theme]
[react-native.core :as rn]))
(defn list-item [{:keys [name locked? mentions-count unread-messages?
muted? is-active-channel? emoji channel-color]
muted? is-active-channel? emoji channel-color on-press]
:or {channel-color colors/primary-50}}]
[rn/view {:style (merge {:height 48
:display :flex
@ -20,7 +20,8 @@
:padding-left 12
:padding-right 12}
(when is-active-channel?
{:background-color (colors/theme-alpha channel-color 0.05 0.05)}))}
{:background-color (colors/theme-alpha channel-color 0.05 0.05)}))
:on-press on-press}
[rn/view {:display :flex
:flex-direction :row
:justify-content :flex-start

View File

@ -104,11 +104,12 @@
(defn tag
[_ _]
(fn [{:keys [locked tokens size background-color]
(fn [{:keys [locked tokens size background-color on-press]
:or {size 24}}]
[base-tag/base-tag {:background-color background-color
:size size
:type :permission}
:type :permission
:on-press on-press}
[rn/view {:flex-direction :row
:align-items :center
:justify-content :flex-end}

View File

@ -12,11 +12,19 @@
[reagent.core :as reagent]
[quo.platform :as platform]
[status-im2.contexts.communities.requests.actions.view :as requests.actions]
[status-im2.contexts.communities.home.actions.view :as home.actions]))
[status-im2.contexts.communities.home.actions.view :as home.actions]
[quo2.components.community.token-gating :as token-gating]
[status-im.constants :as constants]
[status-im.react-native.resources :as resources]
[status-im.utils.utils :as utils]))
(def knc-token-img (js/require "../resources/images/tokens/mainnet/KNC.png"))
(def mana-token-img (js/require "../resources/images/tokens/mainnet/MANA.png"))
(def rare-token-img (js/require "../resources/images/tokens/mainnet/RARE.png"))
(def eth-token-img (js/require "../resources/images/tokens/mainnet/ETH.png"))
(def dai-token-img (js/require "../resources/images/tokens/mainnet/DAI.png"))
;; Mocked list items
(def user-list
[{:full-name "Alicia K"}
{:full-name "Marcus C"}
@ -32,16 +40,69 @@
:size :label}
"Join Alicia, Marcus and 2 more"]]) ;; TODO remove mocked data and use from contacts list/communities members
(defn open-token-gating-mocked [name emoji channel-color]
#(rf/dispatch
[:bottom-sheet/show-sheet
{:content
(constantly [token-gating/token-gating
{:channel {:name name
:community-color (colors/custom-color :pink 50)
:emoji emoji
:emoji-background-color channel-color
:on-enter-channel (fn [] (utils/show-popup "Entered channel" "Wuhuu!! You successfully entered the channel :)"))
:gates {:read [{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? false
:is-purchasable true}
{:token "RARE"
:token-img-src rare-token-img
:amount 10
:is-sufficient? false}]
:write [{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "DAI"
:token-img-src dai-token-img
:amount 20
:is-purchasable true
:is-sufficient? false}
{:token "ETH"
:token-img-src eth-token-img
:amount 0.5
:is-sufficient? false}]}}}])
:content-height 210}]))
(def list-of-channels {:Welcome [{:name "welcome"
:emoji "🤝"}
{:name "onboarding"
:emoji "🍑"}
:emoji "🍑"
:locked? true
:on-press #((open-token-gating-mocked
"onboarding"
"🍑"
(colors/custom-color :pink 50)))}
{:name "intro"
:emoji "🦄"}]
:emoji "🦄"
:locked? true
:on-press #((open-token-gating-mocked
"intro"
"🦄"
(colors/custom-color :pink 50)))}]
:General [{:name "general"
:emoji "🐷"}
{:name "people-ops"
:emoji "🌏"}
:emoji "🌏"
:locked? true
:on-press #((open-token-gating-mocked
"onboarding"
"🌏"
(colors/custom-color :blue 50)))}
{:name "announcements"
:emoji "🎺"}]
:Mobile [{:name "mobile"
@ -123,9 +184,10 @@
(max 0)
(min (if platform/ios? 100 124)))))
(defn community-card-page-view [{:keys [name description locked joined
status tokens cover tags community-color] :as community}]
(defn community-card-page-view [{:keys [name id description locked joined
status tokens cover images tags community-color] :as community}]
(let [community-icon (memoize (fn [] [communities.icon/community-icon-redesign community 24]))
thumbnail-image (get-in images [:thumbnail])
scroll-height (reagent/atom scroll-0)
channel-heights (reagent/atom [])
first-channel-height (reagent/atom 0)]
@ -211,7 +273,32 @@
[quo/permission-tag-container
{:locked locked
:status status
:tokens tokens}]])
:tokens tokens
:on-press #(rf/dispatch
[:bottom-sheet/show-sheet
{:content
(constantly [token-gating/token-gating
{:community {:name name
:community-color colors/primary-50
:community-avatar (cond
(= id constants/status-community-id)
(resources/get-image :status-logo)
(seq thumbnail-image)
thumbnail-image)
:gates {:join [{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? false
:is-purchasable true}
{:token "RARE"
:token-img-src rare-token-img
:amount 10
:is-sufficient? false}]}}}])
:content-height 210}])}]])
(when joined
[rn/view {:position :absolute

View File

@ -0,0 +1,164 @@
(ns status-im2.contexts.quo-preview.community.token-gating
(:require [quo.previews.preview :as preview]
[quo.react-native :as rn]
[quo2.components.community.token-gating :as quo2]
[quo2.foundations.colors :as colors]
[reagent.core :as reagent]
[status-im.utils.utils :as utils]))
(def styles {:container-sandbox {:flex 1
:padding-vertical 20
:border-top-left-radius 20
:border-top-right-radius 20}})
(def descriptor [{:label "Type:"
:key :type
:type :select
:options [{:key :community
:value "Community"}
{:key :channel
:value "Channel"}]}
{:label "Tokens sufficient:"
:key :is-sufficient?
:type :boolean}
{:label "Many tokens:"
:key :many-tokens?
:type :boolean}
{:label "Membership request denied:"
:key :membership-request-denied?
:type :boolean}])
(def eth-token-img (js/require "../resources/images/tokens/mainnet/ETH.png"))
(def knc-token-img (js/require "../resources/images/tokens/mainnet/KNC.png"))
(def mana-token-img (js/require "../resources/images/tokens/mainnet/MANA.png"))
(def rare-token-img (js/require "../resources/images/tokens/mainnet/RARE.png"))
(def dai-token-img (js/require "../resources/images/tokens/mainnet/DAI.png"))
(def fxc-token-img (js/require "../resources/images/tokens/mainnet/FXC.png"))
(def usdt-token-img (js/require "../resources/images/tokens/mainnet/USDT.png"))
(def snt-token-img (js/require "../resources/images/tokens/mainnet/SNT.png"))
(defn join-gate-options-base [is-sufficient? many-tokens?]
(into
[{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? is-sufficient?
:is-purchasable? true}
{:token "RARE"
:token-img-src rare-token-img
:amount 10
:is-sufficient? is-sufficient?}]
(when many-tokens? [{:token "FXC"
:token-img-src fxc-token-img
:amount 20
:is-sufficient? true}
{:token "SNT"
:token-img-src snt-token-img
:amount 10000
:is-sufficient? is-sufficient?}])))
(defn write-gate-options-base [is-sufficient?]
[{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "DAI"
:token-img-src dai-token-img
:amount 20
:is-purchasable? true
:is-sufficient? is-sufficient?}
{:token "ETH"
:token-img-src eth-token-img
:amount 0.5
:is-sufficient? is-sufficient?}])
(defn get-mocked-props [props]
(let [{:keys [type is-sufficient? many-tokens? membership-request-denied?]} props]
(if (= type :community) {:community {:name "Ethereum"
:community-color "#14044d"
:community-avatar-img-src eth-token-img
:gates {:join (if
many-tokens?
[(join-gate-options-base is-sufficient? many-tokens?)
[{:token "FXC"
:token-img-src fxc-token-img
:amount 20
:is-sufficient? true}
{:token "USDT"
:token-img-src usdt-token-img
:amount 20
:is-sufficient? false}]]
(join-gate-options-base is-sufficient? many-tokens?))}}}
{:channel {:name "onboarding"
:community-color (colors/custom-color :pink 50)
:community-text-color colors/white
:emoji "🍑"
:emoji-background-color "#F38888"
:on-enter-channel #(utils/show-popup "Entered channel" "Wuhuu!! You successfully entered the channel :)")
:membership-request-denied? membership-request-denied?
:gates {:read (into [{:token "KNC"
:token-img-src knc-token-img
:amount 200
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? is-sufficient?
:is-purchasable? true}
{:token "RARE"
:token-img-src rare-token-img
:amount 10
:is-sufficient? is-sufficient?}]
(when many-tokens? [{:token "FXC"
:token-img-src fxc-token-img
:amount 20
:is-sufficient? true}
{:token "SNT"
:token-img-src snt-token-img
:amount 10000
:is-sufficient? is-sufficient?}]))
:write (if
many-tokens?
[(write-gate-options-base is-sufficient?)
[{:token "FXC"
:token-img-src fxc-token-img
:amount 20
:is-sufficient? true}
{:token "MANA"
:token-img-src mana-token-img
:amount 10
:is-sufficient? is-sufficient?}
{:token "USDT"
:token-img-src usdt-token-img
:amount 20
:is-sufficient? false}]]
(write-gate-options-base is-sufficient?))}}})))
(def state (reagent/atom {:type :channel
:is-sufficient? false
:many-tokens? false
:membership-request-denied? false}))
(defn preview-token-gating []
(let [preview-props (get-mocked-props @state)]
[rn/view {:style {:background-color (colors/theme-colors
colors/neutral-10
colors/neutral-80)
:flex 1}}
[rn/view {:style {:flex 1}}
[rn/view {:style {:position :absolute
:left 0
:right 0
:top 0}}
[preview/customizer state descriptor]]]
[rn/view {:height (if (= (:type @state) :community) 280 495) :margin-top 20}
[rn/view {:style (merge
(get styles :container-sandbox)
{:background-color (colors/theme-colors
colors/white
colors/neutral-90)})}
[quo2/token-gating preview-props]]]]))

View File

@ -53,6 +53,7 @@
[status-im2.contexts.quo-preview.wallet.network-amount :as network-amount]
[status-im2.contexts.quo-preview.navigation.page-nav :as page-nav]
[status-im2.contexts.quo-preview.avatars.account-avatar :as account-avatar]
[status-im2.contexts.quo-preview.community.token-gating :as token-gating]
[re-frame.core :as re-frame]))
(def screens-categories
@ -94,7 +95,10 @@
:component community-membership-list-view/preview-community-list-view}
{:name :discover-card
:insets {:top false}
:component discover-card/preview-discoverd-card}]
:component discover-card/preview-discoverd-card}
{:name :token-gating
:insets {:top false}
:component token-gating/preview-token-gating}]
:counter [{:name :counter
:insets {:top false}
:component counter/preview-counter}]

View File

@ -175,6 +175,7 @@
"member-kick": "Kick member",
"member-ban": "Ban member",
"membership-requests": "Membership requests",
"membership-request-denied": "Membership request denied",
"community-members-title": "Members",
"community-requests-to-join-title": "Membership requests",
"name-your-channel": "Name your channel",
@ -216,6 +217,15 @@
"community-image-pick": "Pick an image",
"community-image-delete": "",
"community-image-remove": "Remove",
"community-join-requirements-met": "Join requirements met",
"community-join-requirements-not-met": "Join requirements not met",
"community-join-requirements-changed": "Token requirements have changed",
"community-join-requirements-tokens-lost": "You no longer have the tokens required",
"community-channel-read-requirements-met": "View only requirements met",
"community-channel-read-requirements-not-met": "View only requirements not met",
"community-channel-write-requirements-met": "View and post requirements met",
"community-channel-write-requirements-not-met": "View and post requirements not met",
"community-enter-channel-info": "Entering channel will reveal your public addresses to the node owner",
"community-color": "Community colour",
"community-link": "Community link",
"community-color-placeholder": "Pick a colour",
@ -434,6 +444,7 @@
"delete-profile": "Delete profile",
"delete-my-profile": "Delete my profile",
"delete-profile-warning": "Warning: If you dont have your seed phrase written down, you will lose access to your funds after you delete your profile",
"enter-channel": "Enter channel",
"profile-deleted-title": "Profile deleted",
"profile-deleted-content": "Your profile was successfully deleted",
"profile-deleted-keycard": "You can now restore another keypair on your Keycard",
@ -1842,6 +1853,9 @@
"blank-messages-text": "Your messages will be here",
"blank-contacts-text": "Your contacts will be here",
"groups": "Groups",
"you-must-hold": "You must hold:",
"you-must-now-hold": "You must now hold:",
"you-must-always-hold": "You must always hold:",
"shell-placeholder-title": "Your apps will run here",
"no-pinned-messages-desc": "This chat doesn't have any pinned messages.",
"no-pinned-messages-community-desc": "This channel doesn't have any pinned messages.",