Implement switcher group messaging card (#16802)

This commit is contained in:
codemaster 2023-09-29 09:11:21 -04:00 committed by GitHub
parent 695bbcbe81
commit ab868a6ae5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 581 additions and 10 deletions

View File

@ -707,7 +707,7 @@ SPEC CHECKSUMS:
FBLazyVector: a8af91c2b5a0029d12ff6b32e428863d63c48991
FBReactNativeSpec: 1b2309b096448a1dc9d0c43999216f8fda809ae8
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: d93527a855523adb8c113837db4be68fb00e230d
glog: 166d178815c300e8126de9a7900101814eb16253
HMSegmentedControl: 34c1f54d822d8308e7b24f5d901ec674dfa31352
Keycard: ac6df4d91525c3c82635ac24d4ddd9a80aca5fc8
libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef

View File

@ -28,9 +28,10 @@
(let [container-size (get-in sizes [size :container])
icon-size (get-in sizes [size :icon])]
[rn/view
{:style (style/container {:container-size container-size
:customization-color customization-color
:theme theme})}
{:accessibility-label :group-avatar
:style (style/container {:container-size container-size
:customization-color customization-color
:theme theme})}
(if picture
[fast-image/fast-image
{:source picture

View File

@ -4,7 +4,9 @@
(defn get-color
[type customization-color theme]
(case type
:default (colors/custom-color customization-color 50)
:default (colors/theme-colors (colors/custom-color customization-color 50)
(colors/custom-color customization-color 60)
theme)
:secondary (colors/theme-colors colors/neutral-80-opa-5 colors/white-opa-5 theme)
:grey (colors/theme-colors colors/neutral-10 colors/neutral-80 theme)
:outline (colors/theme-colors colors/neutral-20 colors/neutral-80 theme)

View File

@ -0,0 +1,18 @@
(ns quo2.components.switchers.base-card.component-spec
(:require [test-helpers.component :as h]
[quo2.components.switchers.base-card.view :as base-card]
[quo2.foundations.colors :as colors]))
(h/describe "Switcher: Base card"
(h/test "Default render"
(h/render [base-card/base-card {}])
(h/is-truthy (h/query-by-label-text :base-card)))
(h/test "Banner render"
(h/render [base-card/base-card {:banner {:source "banner"}}])
(h/is-truthy (h/query-by-label-text :base-card))
(h/is-truthy (h/query-by-label-text :banner)))
(h/test "Customization color"
(h/render [base-card/base-card {:customization-color :blue}])
(h/has-style
(h/query-by-label-text :base-card)
{:backgroundColor (colors/custom-color :blue 50 40)})))

View File

@ -0,0 +1,23 @@
(ns quo2.components.switchers.base-card.style
(:require [quo2.foundations.colors :as colors]))
(defn base-container
[customization-color]
{:width 160
:height 160
:border-radius 16
:background-color (colors/custom-color customization-color 50 40)})
(def thumb-card
{:width 160
:height 120
:border-radius 16
:bottom 0
:position :absolute
:background-color colors/neutral-95
:padding 12})
(def close-button
{:position :absolute
:right 8
:top 8})

View File

@ -0,0 +1,31 @@
(ns quo2.components.switchers.base-card.view
(:require [quo.react-native :as rn]
[quo2.components.buttons.button.view :as button]
[quo2.components.switchers.base-card.style :as style]))
(defn base-card
[]
(let [card-ref (atom nil)
set-ref #(reset! card-ref %)]
(fn [{:keys [banner on-press on-close customization-color]} & children]
[rn/touchable-opacity
{:on-press on-press
:ref set-ref
:active-opacity 1}
[rn/view
{:accessibility-label :base-card
:style (style/base-container customization-color)}
(when banner
[rn/image
{:accessibility-label :banner
:source (:source banner)
:style {:width 160}}])
[button/button
{:size 24
:type :grey
:icon-only? true
:on-press on-close
:background :photo
:container-style style/close-button}
:i/close]
(into [rn/view {:style style/thumb-card}] children)]])))

View File

@ -0,0 +1,27 @@
(ns quo2.components.switchers.card-content.style
(:require [quo2.foundations.colors :as colors]))
(defn content-container
[status]
{:flex-direction :row
:justify-content :space-between
:align-items (if (= status :mention) :center :flex-end)})
(def notification-container
{:width 20
:height 20
:justify-content :center
:align-items :center
:margin-left 8})
(def message-text
{:color colors/white})
(def sticker
{:width 24
:height 24})
(def gif
{:width 24
:height 24
:border-radius 8})

View File

@ -0,0 +1,98 @@
(ns quo2.components.switchers.card-content.view
(:require [react-native.core :as rn]
[react-native.fast-image :as fast-image]
[quo2.components.switchers.card-content.style :as style]
[quo2.components.common.notification-dot.view :as notification-dot]
[quo2.components.counter.counter.view :as counter]
[quo2.components.markdown.text :as text]
[quo2.components.list-items.preview-list.view :as preview-list]
[quo2.components.tags.context-tag.view :as tag]
[quo2.components.code.snippet-preview.view :as snippet-preview]
[utils.i18n :as i18n]))
(defn content-view
[{:keys [type content customization-color]
{:keys [text duration photos community-avatar
community-name source]} :content}]
[rn/view {:style {:max-width 108}}
(case type
:message
[text/text
{:size :paragraph-2
:weight :regular
:number-of-lines 1
:ellipsize-mode :tail
:style style/message-text}
text]
:photo
[preview-list/view
{:type :collectibles
:more-than-99-label (i18n/label :t/counter-99-plus)
:size :size/s-24}
photos]
:sticker
[fast-image/fast-image
{:accessibility-label :sticker
:source source
:style style/sticker}]
:gif
[fast-image/fast-image
{:accessibility-label :gif
:source source
:style style/gif}]
:audio
[tag/view
{:type :audio
:duration duration
:customization-color customization-color}]
:community
[tag/view
{:type :community
:community-name community-name
:community-logo community-avatar
:size 24}]
:link
[tag/view
{:type :icon
:icon (:icon content)
:context (:text content)
:size 24}]
:code
[snippet-preview/view
{:language (:language content)}
content]
nil)])
(defn notification-indicator
[{:keys [status mention-count customization-color]}]
(when (not= status :read)
[rn/view {:style style/notification-container}
(case status
:unread
[notification-dot/view
{:customization-color customization-color}]
:mention
[counter/view
{:outline false
:customization-color customization-color}
mention-count]
nil)]))
(defn view
[type status customization-color content]
[rn/view {:style (style/content-container status)}
[content-view {:type type :content content :customization-color customization-color}]
[notification-indicator
{:status status
:customization-color customization-color
:mention-count (:mention-count content)}]])

View File

@ -0,0 +1,5 @@
(ns quo2.components.switchers.card-main-info.style
(:require [quo2.foundations.colors :as colors]))
(def subtitle
{:color colors/neutral-40})

View File

@ -0,0 +1,21 @@
(ns quo2.components.switchers.card-main-info.view
(:require [quo2.components.markdown.text :as text]
[quo2.components.switchers.card-main-info.style :as style]
[quo.react-native :as rn]))
(defn view
[{:keys [title subtitle]}]
[rn/view
[text/text
{:accessibility-label :title
:size :paragraph-1
:weight :semi-bold
:number-of-lines 1
:ellipsize-mode :tail}
title]
[text/text
{:accessibility-label :subtitle
:size :paragraph-2
:weight :medium
:style style/subtitle}
subtitle]])

View File

@ -0,0 +1,116 @@
(ns quo2.components.switchers.group-messaging-card.component-spec
(:require [test-helpers.component :as h]
[quo2.components.switchers.group-messaging-card.view :as group-messaging-card]
[quo2.components.switchers.utils :as utils]))
(def photos-list
[{:source (js/require "../resources/images/mock2/photo1.png")}
{:source (js/require "../resources/images/mock2/photo2.png")}
{:source (js/require "../resources/images/mock2/photo3.png")}
{:source (js/require "../resources/images/mock2/photo1.png")}
{:source (js/require "../resources/images/mock2/photo2.png")}
{:source (js/require "../resources/images/mock2/photo3.png")}])
(def sticker {:source (js/require "../resources/images/mock2/sticker.png")})
(def gif {:source (js/require "../resources/images/mock2/gif.png")})
(def coinbase-community (js/require "../resources/images/mock2/coinbase.png"))
(def link-icon (js/require "../resources/images/mock2/status-logo.png"))
(h/describe "Switcher: Group Messaging Card"
(h/test "Default render"
(h/render [group-messaging-card/view {}])
(h/is-truthy (h/query-by-label-text :base-card)))
(h/test "Avatar render"
(h/render [group-messaging-card/view {:avatar true}])
(h/is-truthy (h/query-by-label-text :group-avatar)))
(h/test "Status: Read, Type: Message, Avatar: true"
(h/render [group-messaging-card/view
{:avatar true
:status :read
:type :message
:title "Title"
:content {:text "Last message"}}])
(h/is-truthy (h/get-by-text "Title"))
(h/is-truthy (h/get-by-text (utils/subtitle :message nil)))
(h/is-truthy (h/get-by-text "Last message")))
(h/test "Status: Unread, Type: Message, Avatar: true"
(h/render [group-messaging-card/view
{:avatar true
:status :unread
:type :message
:title "Title"}])
(h/is-truthy (h/query-by-label-text :notification-dot)))
(h/test "Status: Mention, Type: Message, Avatar: true"
(h/render [group-messaging-card/view
{:avatar true
:status :mention
:type :message
:title "Title"
:content {:mention-count 5}}])
(h/is-truthy (h/get-by-test-id :counter-component)))
(h/test "Status: Read, Type: Photo, Avatar: true"
(h/render [group-messaging-card/view
{:avatar true
:status :read
:type :photo
:title "Title"
:content {:photos photos-list}}])
(h/is-truthy (h/get-by-text (utils/subtitle :photo {:photos photos-list}))))
(h/test "Status: Read, Type: Stciker, Avatar: true"
(h/render [group-messaging-card/view
{:avatar true
:status :read
:type :sticker
:title "Title"
:content sticker}])
(h/is-truthy (h/get-by-text (utils/subtitle :sticker nil)))
(h/is-truthy (h/get-by-label-text :sticker)))
(h/test "Status: Read, Type: Gif, Avatar: true"
(h/render [group-messaging-card/view
{:avatar true
:status :read
:type :gif
:title "Title"
:content gif}])
(h/is-truthy (h/get-by-text (utils/subtitle :gif nil)))
(h/is-truthy (h/get-by-label-text :gif)))
(h/test "Status: Read, Type: Audio, Avatar: true"
(h/render [group-messaging-card/view
{:avatar true
:status :read
:type :audio
:title "Title"
:content {:duration "00:32"}}])
(h/is-truthy (h/get-by-text (utils/subtitle :audio nil)))
(h/is-truthy (h/get-by-text "00:32")))
(h/test "Status: Read, Type: Community, Avatar: true"
(h/render [group-messaging-card/view
{:avatar true
:status :read
:type :community
:title "Title"
:content {:community-avatar coinbase-community
:community-name "Coinbase"}}])
(h/is-truthy (h/get-by-text (utils/subtitle :community nil)))
(h/is-truthy (h/get-by-label-text :group-avatar))
(h/is-truthy (h/get-by-text "Coinbase")))
(h/test "Status: Read, Type: Link, Avatar: true"
(h/render [group-messaging-card/view
{:avatar true
:status :read
:type :link
:title "Title"
:content {:icon :placeholder
:text "Rolling St..."}}])
(h/is-truthy (h/get-by-text (utils/subtitle :link nil)))
(h/is-truthy (h/get-by-label-text :group-avatar))
(h/is-truthy (h/get-by-text "Rolling St..."))))

View File

@ -0,0 +1,16 @@
(ns quo2.components.switchers.group-messaging-card.style
(:require [quo2.foundations.colors :as colors]))
(def avatar-container
{:left 10
:top -30
:border-radius 48
:border-width 2
:border-color colors/neutral-95
:position :absolute})
(def content-container
{:flex 1
:flex-direction :column
:justify-content :space-between
:margin-top 16})

View File

@ -0,0 +1,32 @@
(ns quo2.components.switchers.group-messaging-card.view
(:require
[react-native.core :as rn]
[quo2.components.avatars.group-avatar.view :as group-avatar]
[quo2.components.switchers.base-card.view :as base-card]
[quo2.components.switchers.card-main-info.view :as card-main-info]
[quo2.components.switchers.card-content.view :as card-content]
[quo2.components.switchers.group-messaging-card.style :as style]
[quo2.components.switchers.utils :as utils]))
(defn view
"Opts:
:type - keyword -> :message/:photo/:sticker/:gif/:audio/:community/:link/:code
:status - keyword -> :read/:unread/:mention
:profile-color -> keyword or hexstring -> :blue/:army/... or #ABCEDF
:customization-color -> keyword or hexstring -> :blue/:army/... or #ABCEDF"
[{:keys [avatar type status title profile-color customization-color on-close content]
:or {profile-color :blue avatar nil}}]
[base-card/base-card
{:customization-color customization-color
:on-close on-close}
[rn/view {:style style/avatar-container}
[group-avatar/view
{:customization-color customization-color
:picture avatar
:icon-name :i/members
:size :size-48}]]
[rn/view {:style style/content-container}
[card-main-info/view
{:title title
:subtitle (utils/subtitle type content)}]
[card-content/view type status profile-color content]]])

View File

@ -0,0 +1,35 @@
(ns quo2.components.switchers.utils
(:require [utils.i18n :as i18n]))
(defn subtitle
[type {:keys [photos]}]
(case type
:message
(i18n/label :t/message)
:photo
(i18n/label
(if (= (count photos) 1)
:t/one-photo
:t/n-photos)
{:count (count photos)})
:sticker
(i18n/label :t/sticker)
:gif
(i18n/label :t/gif)
:audio
(i18n/label :t/audio-message)
:community
(i18n/label :t/link-to-community)
:link
(i18n/label :t/external-link)
:code
(i18n/label :t/code-snippet)
""))

View File

@ -7,13 +7,17 @@
(colors/theme-colors colors/neutral-80-opa-40 colors/white-opa-40 theme)
(colors/theme-colors colors/neutral-50 colors/neutral-40 theme)))
(def audio-tag-icon-container
(defn audio-tag-icon-container
[customization-color theme]
{:width 20
:height 20
:border-radius 10
:align-items :center
:justify-content :center
:background-color colors/primary-50})
:background-color (colors/theme-colors
(colors/custom-color customization-color 50)
(colors/custom-color customization-color 60)
theme)})
(def audio-tag-icon-color colors/white)

View File

@ -108,7 +108,7 @@
:audio
[tag-skeleton {:theme theme :text (str duration)}
[rn/view {:style style/audio-tag-icon-container}
[rn/view {:style (style/audio-tag-icon-container customization-color theme)}
[icons/icon :i/play {:color style/audio-tag-icon-color :size 12}]]]
:group

View File

@ -112,6 +112,7 @@
quo2.components.settings.section-label.view
quo2.components.share.qr-code.view
quo2.components.share.share-qr-code.view
quo2.components.switchers.group-messaging-card.view
quo2.components.tabs.account-selector
quo2.components.tabs.segmented-tab
quo2.components.tabs.tabs.view
@ -328,6 +329,9 @@
(def qr-code quo2.components.share.qr-code.view/qr-code)
(def share-qr-code quo2.components.share.share-qr-code.view/view)
;;;; SWITCHER
(def group-messaging-card quo2.components.switchers.group-messaging-card.view/view)
;;;; Tabs
(def tabs quo2.components.tabs.tabs.view/view)
(def segmented-control quo2.components.tabs.segmented-tab/segmented-control)

View File

@ -65,6 +65,8 @@
[quo2.components.settings.category.component-spec]
[quo2.components.settings.data-item.component-spec]
[quo2.components.share.share-qr-code.component-spec]
[quo2.components.switchers.base-card.component-spec]
[quo2.components.switchers.group-messaging-card.component-spec]
[quo2.components.tags.network-tags.component-spec]
[quo2.components.tags.status-tags-component-spec]
[quo2.components.wallet.account-card.component-spec]

View File

@ -131,6 +131,7 @@
[status-im2.contexts.quo-preview.settings.section-label :as section-label]
[status-im2.contexts.quo-preview.share.qr-code :as qr-code]
[status-im2.contexts.quo-preview.share.share-qr-code :as share-qr-code]
[status-im2.contexts.quo-preview.switcher.group-messaging-card :as group-messaging-card]
[status-im2.contexts.quo-preview.switcher.switcher-cards :as switcher-cards]
[status-im2.contexts.quo-preview.tabs.account-selector :as account-selector]
[status-im2.contexts.quo-preview.tabs.segmented-tab :as segmented]
@ -362,8 +363,6 @@
:component react/preview-react}]
:record-audio [{:name :record-audio
:component record-audio/preview-record-audio}]
:switcher [{:name :switcher-cards
:component switcher-cards/preview-switcher-cards}]
:selectors [{:name :disclaimer
:component disclaimer/preview-disclaimer}
{:name :filter
@ -390,6 +389,10 @@
:component qr-code/preview-qr-code}
{:name :share-qr-code
:component share-qr-code/preview-share-qr-code}]
:switchers [{:name :group-messaging-card
:component group-messaging-card/view}
{:name :switcher-cards
:component switcher-cards/preview-switcher-cards}]
:tabs [{:name :segmented
:component segmented/preview-segmented}
{:name :tabs

View File

@ -0,0 +1,132 @@
(ns status-im2.contexts.quo-preview.switcher.group-messaging-card
(:require [quo2.core :as quo]
[reagent.core :as reagent]
[status-im2.contexts.quo-preview.preview :as preview]
[status-im2.common.resources :as resources]))
(def descriptor
[{:label "Title"
:key :title
:type :text}
{:label "Status"
:key :status
:type :select
:options [{:key :read
:value :read}
{:key :unread
:value :unread}
{:key :mention
:value :mention}]}
{:label "Counter Label"
:key :counter-label
:type :text}
{:label "Type"
:key :type
:type :select
:options [{:key :message
:value :text}
{:key :photo
:value :photo}
{:key :sticker
:value :sticker}
{:key :gif
:value :gif}
{:key :audio
:value :audio}
{:key :community
:value :community}
{:key :link
:value :link}
{:key :code
:value :code-snippet}]}
{:label "Last Message"
:key :last-message
:type :text}
{:label "Avatar:"
:key :avatar?
:type :boolean}
(preview/customization-color-option)])
;; Mock data
(def sticker {:source (resources/get-mock-image :sticker)})
(def community-avatar (resources/get-mock-image :community-logo))
(def gif {:source (resources/get-mock-image :gif)})
(def coinbase-community (resources/get-mock-image :coinbase))
(def photos-list
[{:source (resources/get-mock-image :photo1)}
{:source (resources/get-mock-image :photo2)}
{:source (resources/get-mock-image :photo3)}
{:source (resources/get-mock-image :photo1)}
{:source (resources/get-mock-image :photo2)}
{:source (resources/get-mock-image :photo3)}])
(def clojure-example
"(defn request->xhrio-options
[{:as request
:keys [on-success on-failure]
:or {on-success [:http-no-on-success]
on-failure [:http-no-on-failure]}}]
; wrap events in cljs-ajax callback
(let [api (new goog.net.XhrIo)]
(-> request
(assoc
:api api
:handler (partial ajax-xhrio-handler
#(dispatch (conj on-success %))
#(dispatch (conj on-failure %))
api))
(dissoc :on-success :on-failure :on-request))))")
(defn get-mock-content
[data]
(case (:type data)
:message
{:text (:last-message data)}
:photo
{:photos photos-list}
:sticker
sticker
:gif
gif
:audio
{:duration "00:32"}
:community
{:community-avatar coinbase-community
:community-name "Coinbase"}
:link
{:icon :placeholder
:text "Rolling St..."}
:code
{:language :clojure
:text clojure-example}
nil))
(defn get-mock-data
[data]
(merge
data
{:content (merge (get-mock-content data)
{:mention-count (when (= (:status data) :mention) (:counter-label data))})}))
(defn view
[]
(let [state (reagent/atom {:title "Hester, John, Steven, and 2 others"
:type :message
:status :read
:last-message "Hello there, there is a new message"
:customization-color :camel
:avatar? false
:counter-label 5})]
(fn []
[preview/preview-container {:state state :descriptor descriptor}
[quo/group-messaging-card
(cond-> (get-mock-data @state)
(:avatar? @state)
(assoc :avatar community-avatar))]])))

View File

@ -2307,6 +2307,7 @@
"mint": "Mint",
"via": "via",
"x-counter": "x{{counter}}",
"code-snippet": "Code snippet",
"name-ens-or-address": "Name, ENS, or address",
"emoji-search-placeholder": "Search emojis",
"emoji-recent": "Recent",