diff --git a/.gitignore b/.gitignore index 3022956c50..6868c2f710 100644 --- a/.gitignore +++ b/.gitignore @@ -178,4 +178,6 @@ test/appium/tests/users.py /lib/ ## visual tests -/artifacts \ No newline at end of file +/artifacts + +/.calva/ diff --git a/src/quo2/components/buttons/button.cljs b/src/quo2/components/buttons/button.cljs index 3f0be31dc9..602eeff049 100644 --- a/src/quo2/components/buttons/button.cljs +++ b/src/quo2/components/buttons/button.cljs @@ -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}]])]]])))) diff --git a/src/quo2/components/community/community_view.cljs b/src/quo2/components/community/community_view.cljs index a9a6a3129a..fd6d75fb79 100644 --- a/src/quo2/components/community/community_view.cljs +++ b/src/quo2/components/community/community_view.cljs @@ -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}]) diff --git a/src/quo2/components/community/token_gating.cljs b/src/quo2/components/community/token_gating.cljs new file mode 100644 index 0000000000..f7cd692086 --- /dev/null +++ b/src/quo2/components/community/token_gating.cljs @@ -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)]]))]))) diff --git a/src/quo2/components/info/info_message.cljs b/src/quo2/components/info/info_message.cljs index e6116f1ddf..c574ca72f2 100644 --- a/src/quo2/components/info/info_message.cljs +++ b/src/quo2/components/info/info_message.cljs @@ -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 diff --git a/src/quo2/components/info/information_box.cljs b/src/quo2/components/info/information_box.cljs index 2d2ade34bf..2562bee985 100644 --- a/src/quo2/components/info/information_box.cljs +++ b/src/quo2/components/info/information_box.cljs @@ -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 diff --git a/src/quo2/components/list_items/channel.cljs b/src/quo2/components/list_items/channel.cljs index 2b535ece58..82982f0b0a 100644 --- a/src/quo2/components/list_items/channel.cljs +++ b/src/quo2/components/list_items/channel.cljs @@ -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 diff --git a/src/quo2/components/tags/permission_tag.cljs b/src/quo2/components/tags/permission_tag.cljs index 2132b891f5..f39706a892 100644 --- a/src/quo2/components/tags/permission_tag.cljs +++ b/src/quo2/components/tags/permission_tag.cljs @@ -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} diff --git a/src/status_im2/contexts/communities/overview/view.cljs b/src/status_im2/contexts/communities/overview/view.cljs index 03f330d2c8..3c643dda5a 100644 --- a/src/status_im2/contexts/communities/overview/view.cljs +++ b/src/status_im2/contexts/communities/overview/view.cljs @@ -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 diff --git a/src/status_im2/contexts/quo_preview/community/token_gating.cljs b/src/status_im2/contexts/quo_preview/community/token_gating.cljs new file mode 100644 index 0000000000..06bbe07508 --- /dev/null +++ b/src/status_im2/contexts/quo_preview/community/token_gating.cljs @@ -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]]]])) diff --git a/src/status_im2/contexts/quo_preview/main.cljs b/src/status_im2/contexts/quo_preview/main.cljs index abcd082d48..fc9a2f2193 100644 --- a/src/status_im2/contexts/quo_preview/main.cljs +++ b/src/status_im2/contexts/quo_preview/main.cljs @@ -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}] diff --git a/translations/en.json b/translations/en.json index a346c50c6f..06233e46e7 100644 --- a/translations/en.json +++ b/translations/en.json @@ -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 don’t 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.",