From d28ae1212d2859d2515b74000711e0ceab181338 Mon Sep 17 00:00:00 2001 From: Gheorghe Pinzaru Date: Thu, 21 May 2020 10:22:44 +0300 Subject: [PATCH] Add animated list item into Quo Change press animation ns Add list item component Fix pressable does not reset animation state Improve long-press handling Signed-off-by: Gheorghe Pinzaru --- .../pressable.cljs} | 23 +-- src/quo/components/button/view.cljs | 2 +- src/quo/components/list/footer.cljs | 11 ++ src/quo/components/list/header.cljs | 11 ++ src/quo/components/list/item.cljs | 163 ++++++++++++++++++ src/quo/core.cljs | 8 +- src/quo/previews/lists.cljs | 110 ++++++++++++ src/quo/previews/main.cljs | 10 +- 8 files changed, 323 insertions(+), 15 deletions(-) rename src/quo/components/{button/animation.cljs => animated/pressable.cljs} (87%) create mode 100644 src/quo/components/list/footer.cljs create mode 100644 src/quo/components/list/header.cljs create mode 100644 src/quo/components/list/item.cljs create mode 100644 src/quo/previews/lists.cljs diff --git a/src/quo/components/button/animation.cljs b/src/quo/components/animated/pressable.cljs similarity index 87% rename from src/quo/components/button/animation.cljs rename to src/quo/components/animated/pressable.cljs index 1410945e0e..9b6a57b199 100644 --- a/src/quo/components/button/animation.cljs +++ b/src/quo/components/animated/pressable.cljs @@ -1,4 +1,4 @@ -(ns quo.components.button.animation +(ns quo.components.animated.pressable (:require [quo.animated :as animated] [quo.gesture-handler :as gesture-handler])) @@ -50,8 +50,8 @@ active (animated/eq state (:began gesture-handler/states)) gesture-handler (animated/on-gesture {:state state}) duration (animated/cond* active time-in time-out) - long-duration (animated/cond* active long-press-duration 0) long-pressed (animated/value 0) + long-duration (animated/cond* active long-press-duration 0) long-timing (animated/with-timing-transition active {:duration long-duration}) animation (animated/with-timing-transition active @@ -70,9 +70,15 @@ handle-press-start (fn [] (when on-press-start (on-press-start))) handle-long-press (fn [] (when on-long-press (on-long-press)))] [:<> - [animated/code - {:exec (animated/cond* (animated/eq long-timing 1) - (animated/set long-pressed 1))}] + (when on-long-press + [animated/code + {:exec (animated/block + [(animated/cond* (animated/eq long-timing 1) + (animated/set long-pressed 1)) + (animated/cond* long-pressed + [(animated/set long-pressed 0) + (animated/call* [] handle-long-press) + (animated/set state (:undetermined gesture-handler/states))])])}]) [animated/code {:key (str on-press on-long-press on-press-start) :exec (animated/on-change state @@ -80,11 +86,8 @@ (animated/call* [] handle-press-start)) (animated/cond* (animated/and* (animated/eq state (:end gesture-handler/states)) (animated/not* long-pressed)) - (animated/call* [] handle-press)) - (animated/cond* (animated/and* (animated/eq state (:end gesture-handler/states)) - long-pressed) - [(animated/set long-pressed 0) - (animated/call* [] handle-long-press)])])}] + [(animated/call* [] handle-press) + (animated/set state (:undetermined gesture-handler/states))])])}] [gesture-handler/tap-gesture-handler (merge gesture-handler {:shouldCancelWhenOutside true diff --git a/src/quo/components/button/view.cljs b/src/quo/components/button/view.cljs index 91ec8323cf..64989512a7 100644 --- a/src/quo/components/button/view.cljs +++ b/src/quo/components/button/view.cljs @@ -1,5 +1,5 @@ (ns quo.components.button.view - (:require [quo.components.button.animation :as animation] + (:require [quo.components.animated.pressable :as animation] [quo.react-native :as rn] [quo.haptic :as haptic] [quo.design-system.colors :as colors] diff --git a/src/quo/components/list/footer.cljs b/src/quo/components/list/footer.cljs new file mode 100644 index 0000000000..e3ba4a30c5 --- /dev/null +++ b/src/quo/components/list/footer.cljs @@ -0,0 +1,11 @@ +(ns quo.components.list.footer + (:require [quo.react-native :as rn] + [quo.design-system.spacing :as spacing] + [quo.components.text :as text])) + +(defn footer [& children] + [rn/view {:style (merge (:base spacing/padding-horizontal) + (:small spacing/padding-vertical))} + (into [text/text {:color :secondary}] + children)]) + diff --git a/src/quo/components/list/header.cljs b/src/quo/components/list/header.cljs new file mode 100644 index 0000000000..522fc3d4c7 --- /dev/null +++ b/src/quo/components/list/header.cljs @@ -0,0 +1,11 @@ +(ns quo.components.list.header + (:require [quo.react-native :as rn] + [quo.design-system.spacing :as spacing] + [quo.components.text :as text])) + +(defn header [& children] + [rn/view {:style (merge (:base spacing/padding-horizontal) + (:x-tiny spacing/padding-vertical))} + (into [text/text {:color :secondary + :style {:margin-top 10}}] + children)]) diff --git a/src/quo/components/list/item.cljs b/src/quo/components/list/item.cljs new file mode 100644 index 0000000000..11ea86de3c --- /dev/null +++ b/src/quo/components/list/item.cljs @@ -0,0 +1,163 @@ +(ns quo.components.list.item + (:require [quo.react-native :as rn] + [quo.platform :as platform] + [quo.design-system.spacing :as spacing] + [quo.design-system.colors :as colors] + [quo.components.text :as text] + ;; FIXME: + [status-im.ui.components.radio :as radio] + [status-im.ui.components.checkbox.view :as checkbox] + [status-im.ui.components.icons.vector-icons :as icons] + [quo.components.animated.pressable :as animated])) + +(defn themes [theme] + (case theme + :main {:icon-color (:icon-04 @colors/theme) + :icon-bg-color (:interactive-02 @colors/theme) + :active-background (:interactive-02 @colors/theme) + :passive-background (:ui-background @colors/theme) + :text-color (:text-01 @colors/theme)} + :accent {:icon-color (:icon-04 @colors/theme) + :icon-bg-color (:interactive-02 @colors/theme) + :active-background (:interactive-02 @colors/theme) + :passive-background (:ui-background @colors/theme) + :text-color (:text-04 @colors/theme)} + :negative {:icon-color (:negative-01 @colors/theme) + :icon-bg-color (:negative-02 @colors/theme) + :active-background (:negative-02 @colors/theme) + :passive-background (:ui-background @colors/theme) + :text-color (:negative-01 @colors/theme)} + :positive {:icon-color (:positive-01 @colors/theme) + :icon-bg-color (:positive-02 @colors/theme) + :active-background (:positive-02 @colors/theme) + :passive-background (:ui-background @colors/theme) + :text-color (:positive-01 @colors/theme)} + :disabled {:icon-color (:icon-02 @colors/theme) + :icon-bg-color (:ui-01 @colors/theme) + :active-background (:ui-01 @colors/theme) + :passive-background (:ui-background @colors/theme) + :text-color (:text-02 @colors/theme)})) + +(defn size->icon-size [size] + (case size + :small 36 + 40)) + +(defn size->container-size [size] + (case size + :small 52 + 64)) + +(defn container [{:keys [size]} & children] + (into [rn/view {:style (merge (:tiny spacing/padding-horizontal) + {:min-height (size->container-size size) + :padding-vertical 8 + :flex-direction :row + :flex 1 + :align-items :center + :justify-content :space-between})}] + children)) + +(defn- icon-column [{:keys [icon icon-bg-color icon-color size]}] + (when icon + (let [icon-size (if (= size :default) + 40 + 36)] + [rn/view {:style (:tiny spacing/padding-horizontal)} + (cond + (vector? icon) + icon + + (keyword? icon) + [rn/view {:style {:width icon-size + :height icon-size + :align-items :center + :justify-content :center + :border-radius (/ icon-size 2) + :background-color icon-bg-color}} + [icons/icon icon {:color icon-color}]])]))) + +(defn title-column [{:keys [title text-color subtitle subtitle-max-lines]}] + [rn/view {:style (merge (:tiny spacing/padding-horizontal) + {:justify-content :center})} + (cond + + (and title subtitle) + [:<> + [text/text {:weight :medium + :style {:color text-color} + :ellipsize-mode :tail + :number-of-lines 1} + title] + [text/text {:weight :regular + :color :secondary + :ellipsize-mode :tail + :number-of-lines subtitle-max-lines} + subtitle]] + + title [text/text {:number-of-lines 1 + :style {:color text-color} + :ellipsize-mode :tail + :size :large} + title])]) + +(defn left-side [props] + [rn/view {:style {:flex-direction :row + :flex 1 + :align-items :center}} + [icon-column props] + [title-column props]]) + +(defn right-side [{:keys [chevron on-press active accessory accessory-text]}] + [rn/view {:style {:align-items :center + :flex-direction :row}} + [rn/view {:style (:tiny spacing/padding-horizontal)} + (case accessory + :radio [radio/radio active] + :checkbox [checkbox/checkbox {:checked? active}] + :switch [rn/switch {:value active + :track-color #js {:true (:interactive-01 @colors/theme) + :false nil} + :on-value-change on-press}] + :text [text/text {:color :secondary + :number-of-lines 1} + accessory-text] + nil)] + (when (and chevron platform/ios?) + [rn/view {:style {:padding-right (:tiny spacing/spacing)}} + [icons/icon :main-icons/next {:container-style {:opacity 0.4 + :align-items :center + :justify-content :center} + :resize-mode :center + :color (:icon-02 @colors/theme)}]])]) + +(defn list-item + [{:keys [theme accessory disabled subtitle-max-lines icon title + subtitle active on-press on-long-press chevron size + accessory-text] + :or {subtitle-max-lines 1 + theme :main}}] + (let [theme (if disabled :disabled theme) + {:keys [icon-color text-color icon-bg-color + active-background passive-background]} + (themes theme)] + [rn/view {:background-color (if active active-background passive-background)} + [animated/pressable {:type :list-item + :disabled disabled + :background-color active-background + :on-press on-press + :on-long-press on-long-press} + [container {:size size} + [left-side {:icon-color icon-color + :text-color text-color + :icon-bg-color icon-bg-color + :icon icon + :title title + :size size + :subtitle subtitle + :subtitle-max-lines subtitle-max-lines}] + [right-side {:chevron chevron + :active active + :on-press on-press + :accessory-text accessory-text + :accessory accessory}]]]])) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index 4ada595ab3..d52295e478 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -5,7 +5,10 @@ [quo.components.text-input :as text-input] [quo.components.tooltip :as tooltip] [quo.components.text :as text] - [quo.components.button.view :as button])) + [quo.components.button.view :as button] + [quo.components.list.header :as list-header] + [quo.components.list.footer :as list-footer] + [quo.components.list.item :as list-item])) (def text text/text) (def header header/header) @@ -13,6 +16,9 @@ (def text-input text-input/text-input) (def tooltip tooltip/tooltip) (def button button/button) +(def list-header list-header/header) +(def list-footer list-footer/footer) +(def list-item list-item/list-item) (def safe-area-provider safe-area/provider) (def safe-area-consumer safe-area/consumer) (def safe-area-view safe-area/view) diff --git a/src/quo/previews/lists.cljs b/src/quo/previews/lists.cljs new file mode 100644 index 0000000000..8b05387882 --- /dev/null +++ b/src/quo/previews/lists.cljs @@ -0,0 +1,110 @@ +(ns quo.previews.lists + (:require [reagent.core :as reagent] + [quo.core :as quo] + [quo.react-native :as rn] + [quo.design-system.colors :as colors] + [quo.previews.preview :as preview])) + +(def all-props (preview/list-comp [] {})) + +(defn avatar [] + [rn/view {:border-radius 20 + :width 40 + :height 40 + :justify-content :center + :align-items :center + :background-color :red} + [quo/text {:weight :bold + :size :large} + "T"]]) + +(defn icon-element [type] + (case type + :icon :main-icons/add-contact + :component [avatar] + nil)) + +(def descriptor [{:label "Accessory:" + :key :accessory + :type :select + :options [{:key :radio + :value "Radio"} + {:key :checkbox + :value "Checkbox"} + {:key :switch + :value "Switch"} + {:key :text + :value "Text"} + {:key :default + :value "Default"}]} + {:label "Size:" + :key :size + :type :select + :options [{:key :small + :value "Small"} + {:key :default + :value "Default"}]} + {:label "Icon:" + :key :icon + :type :select + :options [{:key :icon + :value "Icon"} + {:key :component + :value "Component"}]} + {:label "Theme:" + :key :theme + :type :select + :options [{:key :main + :value "Main"} + {:key :accent + :value "Accent"} + {:key :negative + :value "Negative"} + {:key :positive + :value "Positive"}]} + {:label "Selectable" + :key :selectable + :type :boolean} + {:label "Chevron" + :key :chevron + :type :boolean} + {:label "Disabled:" + :key :disabled + :type :boolean} + {:label "Title" + :key :title + :type :text} + {:label "Subtitle" + :key :subtitle + :type :text}]) + +(defn render-item [_] + [rn/view {:style {:padding-vertical 24}}]) + +(defn cool-preview [] + (let [state (reagent/atom {:title "Title" + :active false}) + icon (reagent/cursor state [:icon]) + active (reagent/cursor state [:active]) + selectable (reagent/cursor state [:selectable])] + (fn [] + [rn/view {:margin-bottom 50} + [rn/view {:padding-horizontal 16} + [preview/customizer state descriptor]] + [rn/view {:padding-vertical 16} + [quo/list-item (merge (dissoc @state :active :selectable) + (when @selectable + {:active @active + :on-press #(swap! active not)}) + {:accessory-text "Accessory" + :icon (icon-element @icon)})]]]))) + +(defn preview [] + [rn/view {:background-color (:ui-background @colors/theme) + :flex 1} + [rn/flat-list {:flex 1 + :keyboardShouldPersistTaps :always + :header [cool-preview] + :data all-props + :render-fn render-item + :key-fn str}]]) diff --git a/src/quo/previews/main.cljs b/src/quo/previews/main.cljs index 5563b82d3a..115cfc46e7 100644 --- a/src/quo/previews/main.cljs +++ b/src/quo/previews/main.cljs @@ -4,6 +4,7 @@ [quo.previews.text-input :as text-input] [quo.previews.tooltip :as tooltip] [quo.previews.button :as button] + [quo.previews.lists :as lists] [quo.react-native :as rn] [quo.core :as quo] [reagent.core :as reagent] @@ -23,9 +24,12 @@ {:name :headers :insets {:top false} :component header/preview-header} - {:name :button - :insets {:top false} - :component button/preview-button}]) + {:name :button + :insets {:top false} + :component button/preview-button} + {:name :lists + :instes {:top false} + :component lists/preview}]) (defn theme-switcher [] [rn/view {:style {:flex-direction :row