diff --git a/src/quo2/components/avatars/channel_avatar.cljs b/src/quo2/components/avatars/channel_avatar.cljs deleted file mode 100644 index 76a58c60aa..0000000000 --- a/src/quo2/components/avatars/channel_avatar.cljs +++ /dev/null @@ -1,50 +0,0 @@ -(ns quo2.components.avatars.channel-avatar - (:require [quo2.components.icon :as icons] - [quo2.components.markdown.text :as text] - [quo2.foundations.colors :as colors] - [quo2.theme :as theme] - [react-native.core :as rn])) - -(defn channel-avatar - [{:keys [big? locked? emoji-background-color emoji]}] - (let [lock-exists? locked? - dark? (theme/dark?)] - [rn/view - {:style {:width (if big? 32 24) - :height (if big? 32 24) - :border-radius (if big? 32 24) - :justify-content :center - :align-items :center - :background-color emoji-background-color}} - [rn/view - {:style {:display :flex - :justify-content :center - :align-items :center}} - [text/text - {:size (if big? - :paragraph-1 - :label)} emoji] - (when lock-exists? - [rn/view - {:style {:position :absolute - :left (if big? - 14 - 8) - :top (if big? - 15 - 8) - :background-color (if dark? - colors/neutral-90 - colors/white) - :border-radius 15 - :padding 2}} - [icons/icon - (if locked? - :main-icons/locked - :main-icons/unlocked) - {:color (if dark? - colors/neutral-40 - colors/neutral-50) - :container-style {:width 12 - :height 12} - :size 12}]])]])) diff --git a/src/quo2/components/avatars/channel_avatar/component_spec.cljs b/src/quo2/components/avatars/channel_avatar/component_spec.cljs new file mode 100644 index 0000000000..b1abee5439 --- /dev/null +++ b/src/quo2/components/avatars/channel_avatar/component_spec.cljs @@ -0,0 +1,35 @@ +(ns quo2.components.avatars.channel-avatar.component-spec + (:require [quo2.components.avatars.channel-avatar.view :as component] + [test-helpers.component :as h])) + +(h/describe "Channel Avatar" + (h/test "default render" + (h/render [component/view]) + (h/is-truthy (h/query-by-label-text :initials)) + (h/is-null (h/query-by-label-text :emoji)) + (h/is-null (h/query-by-label-text :lock))) + + (h/test "with emoji, no lock set, large size" + (let [emoji "🍓"] + (h/render [component/view {:emoji emoji :size :size/l}]) + (h/is-null (h/query-by-label-text :initials)) + (h/is-truthy (h/query-by-text emoji)) + (h/is-null (h/query-by-label-text :lock)))) + + (h/test "locked" + (h/render [component/view {:locked? true}]) + (h/is-truthy (h/query-by-label-text :lock))) + + (h/test "unlocked" + (h/render [component/view {:locked? false}]) + (h/is-truthy (h/query-by-label-text :lock))) + + (h/test "no emoji, smaller size" + (h/render [component/view {:full-name "Status Mobile"}]) + (h/is-truthy (h/query-by-text "S"))) + + (h/test "no emoji, large size" + (h/render [component/view + {:full-name "Status Mobile" + :size :size/l}]) + (h/is-truthy (h/query-by-text "SM")))) diff --git a/src/quo2/components/avatars/channel_avatar/style.cljs b/src/quo2/components/avatars/channel_avatar/style.cljs new file mode 100644 index 0000000000..0ebc8f771f --- /dev/null +++ b/src/quo2/components/avatars/channel_avatar/style.cljs @@ -0,0 +1,28 @@ +(ns quo2.components.avatars.channel-avatar.style + (:require [quo2.foundations.colors :as colors])) + +(def lock-icon-size 12) + +(defn outer-container + [{:keys [size color]}] + (let [size (if (= size :size/l) 32 24)] + {:width size + :height size + :border-radius size + :justify-content :center + :align-items :center + :background-color (colors/theme-alpha color 0.1 0.1)})) + +(defn lock-container + [size] + (let [distance (if (= size :size/l) 20 12)] + {:position :absolute + :left distance + :top distance + :background-color (colors/theme-colors colors/white colors/neutral-95) + :border-radius (* 2 lock-icon-size) + :padding 2})) + +(def lock-icon + {:width lock-icon-size + :height lock-icon-size}) diff --git a/src/quo2/components/avatars/channel_avatar/view.cljs b/src/quo2/components/avatars/channel_avatar/view.cljs new file mode 100644 index 0000000000..cdd3034972 --- /dev/null +++ b/src/quo2/components/avatars/channel_avatar/view.cljs @@ -0,0 +1,60 @@ +(ns quo2.components.avatars.channel-avatar.view + (:require [clojure.string :as string] + [quo2.components.avatars.channel-avatar.style :as style] + [quo2.components.icon :as icons] + [quo2.components.markdown.text :as text] + [quo2.foundations.colors :as colors] + [react-native.core :as rn] + utils.string)) + +(defn- initials + [full-name size color] + (let [amount-initials (if (= size :size/l) 2 1)] + [text/text + {:accessibility-label :initials + :size :paragraph-2 + :weight :semi-bold + :style {:color color}} + (utils.string/get-initials full-name amount-initials)])) + +(defn- lock + [locked? size] + ;; When `locked?` is nil, we must not display the unlocked icon. + (when (boolean? locked?) + [rn/view + {:accessibility-label :lock + :style (style/lock-container size)} + [icons/icon (if locked? :i/locked :i/unlocked) + {:color (colors/theme-colors colors/neutral-50 colors/neutral-40) + :container-style style/lock-icon + :size 12}]])) + +(defn view + "Options: + + :size - keyword (default nil) - Container size, for the moment, + only :size/l (meaning large) is supported. + + :emoji - string (default nil) + + :customization-color - color (default nil) If the component is used for a + community channel, then the default color should be the community custom + color. + + :locked? - nil/bool (default nil) - When true/false display a locked/unlocked + icon respectively. When nil does not show icon. + + :full-name - string (default nil) - When :emoji is blank, this value will be + used to extract the initials. + " + [{:keys [size emoji customization-color locked? full-name]}] + [rn/view + {:accessibility-label :channel-avatar + :style (style/outer-container {:size size :color customization-color})} + (if (string/blank? emoji) + [initials full-name size customization-color] + [text/text + {:accessibility-label :emoji + :size (if (= size :size/l) :paragraph-1 :label)} + (string/trim emoji)]) + [lock locked? size]]) diff --git a/src/quo2/components/avatars/user_avatar/view.cljs b/src/quo2/components/avatars/user_avatar/view.cljs index 3de035531b..efe5bad6c5 100644 --- a/src/quo2/components/avatars/user_avatar/view.cljs +++ b/src/quo2/components/avatars/user_avatar/view.cljs @@ -1,22 +1,9 @@ (ns quo2.components.avatars.user-avatar.view - (:require [clojure.string :as string] - [quo2.components.avatars.user-avatar.style :as style] + (:require [quo2.components.avatars.user-avatar.style :as style] [quo2.components.markdown.text :as text] [react-native.core :as rn] - [react-native.fast-image :as fast-image])) - -(defn trim-whitespace [s] (string/join " " (string/split (string/trim s) #"\s+"))) - -(defn- extract-initials - [full-name amount-initials] - (let [upper-case-first-letter (comp string/upper-case first) - names-list (string/split (trim-whitespace full-name) " ")] - (if (= (first names-list) "") - "" - (->> names-list - (map upper-case-first-letter) - (take amount-initials) - (string/join))))) + [react-native.fast-image :as fast-image] + utils.string)) (defn initials-avatar [{:keys [full-name size draw-ring? customization-color]}] @@ -29,7 +16,7 @@ {:style style/initials-avatar-text :size font-size :weight :semi-bold} - (extract-initials full-name amount-initials)]])) + (utils.string/get-initials full-name amount-initials)]])) (def valid-ring-sizes #{:big :medium :small}) diff --git a/src/quo2/components/list_items/channel.cljs b/src/quo2/components/list_items/channel.cljs index fbead8ba93..7bdd57f118 100644 --- a/src/quo2/components/list_items/channel.cljs +++ b/src/quo2/components/list_items/channel.cljs @@ -1,6 +1,6 @@ (ns quo2.components.list-items.channel - (:require [quo2.components.avatars.channel-avatar :as channel-avatar] - [quo2.components.common.unread-grey-dot.view :refer [unread-grey-dot]] + (:require [quo2.components.avatars.channel-avatar.view :as channel-avatar] + [quo2.components.common.unread-grey-dot.view :as unread-grey-dot] [quo2.components.counter.counter :as quo2.counter] [quo2.components.icon :as quo2.icons] [quo2.components.markdown.text :as quo2.text] @@ -13,10 +13,11 @@ (defn list-item [{:keys [locked? mentions-count unread-messages? - muted? is-active-channel? emoji channel-color] - :or {channel-color colors/primary-50} + muted? is-active-channel? emoji channel-color + default-color] :as props}] - (let [standard-props (apply dissoc props custom-props) + (let [channel-color (or channel-color default-color) + standard-props (apply dissoc props custom-props) name-text (:name props)] [rn/touchable-opacity standard-props [rn/view @@ -36,11 +37,12 @@ :align-items :center} :accessible true :accessibility-label :chat-name-text} - [channel-avatar/channel-avatar - {:big? true - :locked? locked? - :emoji-background-color (colors/theme-alpha channel-color 0.1 0.1) - :emoji emoji}] + [channel-avatar/view + {:size :size/l + :locked? locked? + :full-name (:name props) + :customization-color channel-color + :emoji emoji}] [quo2.text/text {:style (cond-> {:margin-left 12} (and (not locked?) muted?) @@ -63,4 +65,4 @@ mentions-count]] unread-messages? - [unread-grey-dot :unviewed-messages-public])])]])) + [unread-grey-dot/unread-grey-dot :unviewed-messages-public])])]])) diff --git a/src/quo2/core.cljs b/src/quo2/core.cljs index 94e359465a..2358888711 100644 --- a/src/quo2/core.cljs +++ b/src/quo2/core.cljs @@ -2,7 +2,7 @@ (:refer-clojure :exclude [filter]) (:require quo2.components.avatars.account-avatar - quo2.components.avatars.channel-avatar + quo2.components.avatars.channel-avatar.view quo2.components.avatars.group-avatar quo2.components.avatars.icon-avatar quo2.components.avatars.user-avatar.view @@ -121,7 +121,7 @@ ;;;; AVATAR (def account-avatar quo2.components.avatars.account-avatar/account-avatar) -(def channel-avatar quo2.components.avatars.channel-avatar/channel-avatar) +(def channel-avatar quo2.components.avatars.channel-avatar.view/view) (def group-avatar quo2.components.avatars.group-avatar/group-avatar) (def icon-avatar quo2.components.avatars.icon-avatar/icon-avatar) (def user-avatar quo2.components.avatars.user-avatar.view/user-avatar) diff --git a/src/status_im2/contexts/communities/overview/view.cljs b/src/status_im2/contexts/communities/overview/view.cljs index f67b43d774..13ede8eef4 100644 --- a/src/status_im2/contexts/communities/overview/view.cljs +++ b/src/status_im2/contexts/communities/overview/view.cljs @@ -49,7 +49,7 @@ (oops/oget event "nativeEvent.layout.y")) (defn- channel-chat-item - [community-id {chat-id :id muted? :muted? :as chat}] + [community-id community-color {chat-id :id muted? :muted? :as chat}] (let [sheet-content [actions/chat-actions (assoc chat :chat-type constants/community-chat-type) false] @@ -58,12 +58,13 @@ [rn/view {:key chat-id :style {:margin-top 4}} [quo/channel-list-item (assoc chat + :default-color community-color :on-long-press #(rf/dispatch [:show-bottom-sheet channel-sheet-data]) :muted? (or muted? (rf/sub [:chat/check-channel-muted? community-id chat-id])))]])) (defn channel-list-component - [{:keys [on-category-layout community-id on-first-channel-height-changed]} + [{:keys [on-category-layout community-id community-color on-first-channel-height-changed]} channels-list] [rn/view {:on-layout #(on-first-channel-height-changed @@ -91,7 +92,7 @@ :chevron-position :left}]) (when-not collapsed? (into [rn/view {:style {:padding-horizontal 8 :padding-bottom 8}}] - (map #(channel-chat-item community-id %)) + (map #(channel-chat-item community-id community-color %)) chats))]))]) (defn request-to-join-text @@ -262,7 +263,7 @@ description]) (defn community-content - [{:keys [name description joined tags id] + [{:keys [name description joined tags color id] :as community} pending? {:keys [on-category-layout on-first-channel-height-changed]}] @@ -280,6 +281,7 @@ [channel-list-component {:on-category-layout on-category-layout :community-id id + :community-color color :on-first-channel-height-changed on-first-channel-height-changed} (add-handlers-to-categorized-chats id chats-by-category)]])) diff --git a/src/status_im2/contexts/quo_preview/avatars/channel_avatar.cljs b/src/status_im2/contexts/quo_preview/avatars/channel_avatar.cljs index fcbbdc8545..cdad0064ae 100644 --- a/src/status_im2/contexts/quo_preview/avatars/channel_avatar.cljs +++ b/src/status_im2/contexts/quo_preview/avatars/channel_avatar.cljs @@ -1,53 +1,65 @@ (ns status-im2.contexts.quo-preview.avatars.channel-avatar - (:require [quo2.components.avatars.channel-avatar :as quo2] + (:require [quo2.core :as quo] [quo2.foundations.colors :as colors] [react-native.core :as rn] [reagent.core :as reagent] [status-im2.contexts.quo-preview.preview :as preview])) (def descriptor - [{:label "Big?" - :key :big? - :type :boolean} - {:label "Avatar color" - :key :emoji-background-color - :type :text} - {:label "Avatar color" + [{:label "Size:" + :key :size + :type :select + :options [{:key :size/l :value "Large"} + {:key :default :value "Default"}]} + {:label "Emoji:" :key :emoji :type :text} - {:label "is Locked?" - :key :locked? + {:label "Full name:" + :key :full-name + :type :text} + (preview/customization-color-option) + {:label "Locked state:" + :key :locked-state :type :select - :options [{:key nil - :value "None"} - {:key false + :options [{:key :not-set + :value "Not set"} + {:key :unlocked :value "Unlocked"} - {:key true + {:key :locked :value "Locked"}]}]) (defn cool-preview [] - (let [state (reagent/atom {:big? true - :locked? nil - :emoji "🍑" - :emoji-background-color :gray})] + (let [state (reagent/atom {:size :size/l + :locked-state :not-set + :emoji "🍑" + :full-name "Some channel" + :customization-color :blue})] (fn [] - [rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!} - [rn/view {:padding-bottom 150} - [rn/view {:flex 1} - [preview/customizer state descriptor]] - [rn/view - {:padding-vertical 60 - :flex-direction :row - :justify-content :center} - [quo2/channel-avatar @state]]]]))) + (let [customization-color (colors/custom-color-by-theme (:customization-color @state) 50 60) + locked? (case (:locked-state @state) + :not-set nil + :unlocked false + :locked true + nil)] + [rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!} + [rn/view {:style {:padding-bottom 150}} + [rn/view {:style {:flex 1}} + [preview/customizer state descriptor]] + [rn/view + {:style {:padding-vertical 60 + :flex-direction :row + :justify-content :center}} + [quo/channel-avatar + (assoc @state + :locked? locked? + :customization-color customization-color)]]]])))) (defn preview-channel-avatar [] [rn/view - {:background-color (colors/theme-colors colors/white - colors/neutral-90) - :flex 1} + {:style {:background-color (colors/theme-colors colors/white colors/neutral-95) + :flex 1}} [rn/flat-list {:flex 1 :keyboard-should-persist-taps :always diff --git a/src/status_im2/contexts/quo_preview/preview.cljs b/src/status_im2/contexts/quo_preview/preview.cljs index dfca37636a..1ea5c99b1a 100644 --- a/src/status_im2/contexts/quo_preview/preview.cljs +++ b/src/status_im2/contexts/quo_preview/preview.cljs @@ -202,6 +202,20 @@ :select [customizer-select descriptor] nil)]))]) +(defn customization-color-option + ([] + (customization-color-option {})) + ([opts] + (merge {:label "Custom color:" + :key :customization-color + :type :select + :options (->> colors/customization + keys + sort + (map (fn [k] + {:key k :value (string/capitalize (name k))})))} + opts))) + (comment [{:label "Show error:" :key :error diff --git a/src/status_im2/contexts/shell/jump_to/components/switcher_cards/view.cljs b/src/status_im2/contexts/shell/jump_to/components/switcher_cards/view.cljs index d65dcd269f..7209c91e27 100644 --- a/src/status_im2/contexts/shell/jump_to/components/switcher_cards/view.cljs +++ b/src/status_im2/contexts/shell/jump_to/components/switcher_cards/view.cljs @@ -41,8 +41,8 @@ {:style {:flex-direction :row :align-items :center}} [quo/channel-avatar - {:emoji (:emoji community-channel) - :emoji-background-color (colors/alpha color-50 0.1)}] + {:emoji (:emoji community-channel) + :customization-color color-50}] [quo/text {:size :paragraph-2 :weight :medium diff --git a/src/utils/string.cljs b/src/utils/string.cljs index f0750648bd..f11bcd5db1 100644 --- a/src/utils/string.cljs +++ b/src/utils/string.cljs @@ -49,3 +49,12 @@ [s m r] (when (string? s) (string/replace s m r))) + +(defn get-initials + "Returns `n` number of initial letters from `s`, all uppercased." + [s n] + (let [words (-> s str string/trim (string/split #"\s+"))] + (->> words + (take n) + (map (comp string/upper-case str first)) + string/join))) diff --git a/src/utils/string_test.cljs b/src/utils/string_test.cljs new file mode 100644 index 0000000000..75fc7d9b48 --- /dev/null +++ b/src/utils/string_test.cljs @@ -0,0 +1,19 @@ +(ns utils.string-test + (:require [cljs.test :refer [are deftest]] + utils.string)) + +(deftest get-initials-test + (are [expected input amount-initials] + (= expected (utils.string/get-initials input amount-initials)) + "" nil 0 + "" nil 1 + "" "" 0 + "" "ab" 0 + "" "" 1 + "A" "ab" 1 + "A" " ab " 1 + "A" "a b" 1 + "A" "ab" 2 + "AB" "a b" 2 + "ABC" "a b c d" 3 + "ABC" " a b c d" 3))