Add animated buttons to Quo

Move haptic

Upgrade redash

Add button component

lint

Add button icon type

Add disabled theme

Add accessibility-label

Fix android color

Fix preview modal

Remove android ripple effect

test android

Signed-off-by: Gheorghe Pinzaru <feross95@gmail.com>
This commit is contained in:
Gheorghe Pinzaru 2020-05-18 10:35:22 +03:00
parent 3481ab3c47
commit 0ff6579fa3
No known key found for this signature in database
GPG Key ID: C9A094959935A952
17 changed files with 345 additions and 56 deletions

View File

@ -204,10 +204,12 @@ android {
exclude '/lib/mips64/**'
exclude '/lib/armeabi/**'
pickFirst "lib/armeabi-v7a/libc++_shared.so"
pickFirst "lib/arm64-v8a/libc++_shared.so"
pickFirst "lib/x86/libc++_shared.so"
pickFirst "lib/x86_64/libc++_shared.so"
pickFirst '**/armeabi-v7a/libc++_shared.so'
pickFirst '**/x86/libc++_shared.so'
pickFirst '**/arm64-v8a/libc++_shared.so'
pickFirst '**/x86_64/libc++_shared.so'
pickFirst '**/x86/libjsc.so'
pickFirst '**/armeabi-v7a/libjsc.so'
/** Fix for: Execution failed for task ':app:transformNativeLibsWithStripDebugSymbolForDebug'.
* with recent version of ndk (17.0.4754217)

View File

@ -45,7 +45,7 @@
"react-native-mail": "git+https://github.com/status-im/react-native-mail.git#v4.0.0-status",
"react-native-navigation-twopane": "git+https://github.com/status-im/react-native-navigation-twopane.git#v0.0.2-status",
"react-native-reanimated": "^1.7.0",
"react-native-redash": "^10.0.1",
"react-native-redash": "^14.0.3",
"react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.3.0",
"react-native-shake": "^3.3.1",

View File

@ -6543,15 +6543,14 @@ react-native-reanimated@^1.7.0:
dependencies:
fbjs "^1.0.0"
react-native-redash@^10.0.1:
version "10.1.1"
resolved "https://registry.yarnpkg.com/react-native-redash/-/react-native-redash-10.1.1.tgz#228564f2a9cee2406b8f87b1c90c2fdf7d81cdfb"
integrity sha512-VxyKygf//IzDgJfK5oFK0KYSaxxpuXwwVE2rldtINRByuCIZIUx8QVE3pmxG7XYc15T4OHC3l7wug+tLsLI44w==
react-native-redash@^14.0.3:
version "14.0.3"
resolved "https://registry.yarnpkg.com/react-native-redash/-/react-native-redash-14.0.3.tgz#7f62644f110ceb61962a5aa181e2c8ae05e04c9d"
integrity sha512-ExSP77re4QEGEtjVenfwMTo4RGI1togE6NGsblbqVGtRMTD0IDGovpeC6YQt37jHdaYLRM4VYjVNqjgDjcCt5Q==
dependencies:
abs-svg-path "^0.1.1"
normalize-svg-path "^1.0.1"
parse-svg-path "^0.1.2"
use-memo-one "^1.1.1"
react-native-safe-area-context@^0.7.3:
version "0.7.3"
@ -8018,11 +8017,6 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
use-memo-one@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.1.tgz#39e6f08fe27e422a7d7b234b5f9056af313bd22c"
integrity sha512-oFfsyun+bP7RX8X2AskHNTxu+R3QdE/RC5IefMbqptmACAA/gfol1KDD5KRzPsGMa62sWxGZw+Ui43u6x4ddoQ==
use-subscription@^1.0.0, use-subscription@^1.4.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.4.1.tgz#edcbcc220f1adb2dd4fa0b2f61b6cc308e620069"

View File

@ -82,21 +82,23 @@
else-node))))
(defn block [opts]
(ocall animated "block" (clj->js opts)))
(.block ^js animated (clj->js opts)))
(defn interpolate [anim-value config]
(ocall anim-value "interpolate" (clj->js config)))
(.interpolate ^js animated anim-value (clj->js config)))
(defn call* [args callback]
(ocall animated "call" (clj->js args) callback))
(.call ^js animated (clj->js args) callback))
(defn timing [clock-value opts config]
(ocall animated "timing" clock-value
(clj->js opts) (clj->js config)))
(.timing ^js animated
clock-value
(clj->js opts)
(clj->js config)))
(defn spring [clock-value opts config]
(ocall animated "spring" clock-value
(clj->js opts) (clj->js config)))
(.spring ^js animated clock-value
(clj->js opts) (clj->js config)))
(def extrapolate {:clamp (oget animated "Extrapolate" "CLAMP")})
@ -107,8 +109,11 @@
(defn with-spring [config]
(ocall redash "withSpring" (clj->js config)))
(defn with-timing [val config]
(ocall redash "withTimingTransition" val (clj->js config)))
(defn with-spring-transition [val config]
(.withSpringTransition ^js redash val (clj->js config)))
(defn with-timing-transition [val config]
(.withTimingTransition ^js redash val (clj->js config)))
(defn re-timing [config]
(ocall redash "timing" (clj->js config)))
@ -116,8 +121,13 @@
(defn on-scroll [opts]
(ocall redash "onScrollEvent" (clj->js opts)))
(defn b-interpolate [anim-value a b]
(ocall redash "bInterpolate" anim-value a b))
(defn on-gesture [opts]
(let [gesture-event (event #js [#js {:nativeEvent (clj->js opts)}])]
{:onHandlerStateChange gesture-event
:onGestureEvent gesture-event}))
(defn mix [anim-value a b]
(.mix ^js redash anim-value a b))
(defn loop* [opts]
(ocall redash "loop" (clj->js opts)))

View File

@ -35,7 +35,7 @@
(defn header-container []
(let [y (animated/value 0)
animation-value (animated/value 0)
animation (animated/with-timing
animation (animated/with-timing-transition
animation-value
{:duration 250
:easing (:ease-in animated/easings)})

View File

@ -0,0 +1,99 @@
(ns quo.components.button.animation
(:require [quo.animated :as animated]
[quo.gesture-handler :as gesture-handler]))
(def long-press-duration 500)
(def scale-down-small 0.95)
(def scale-down-large 0.9)
(def opactiy 0.75)
(def time-in 100)
(def time-out 200)
(defmulti type->animation :type)
(defmethod type->animation :primary
[{:keys [animation]}]
{:background {:transform [{:scale (animated/mix animation 1 scale-down-small)}]
:opacity (animated/mix animation 1 opactiy)}
:foreground {:transform [{:scale (animated/mix animation 1 scale-down-small)}]
:opacity (animated/mix animation 1 opactiy)}})
(defmethod type->animation :secondary
[{:keys [animation]}]
{:background {:transform [{:scale (animated/mix animation scale-down-small 1)}]
:opacity (animated/mix animation 0 opactiy)}
:foreground {:transform [{:scale (animated/mix animation 1 scale-down-small)}]
:opacity (animated/mix animation 1 opactiy)}})
(defmethod type->animation :icon
[{:keys [animation]}]
{:background {:transform [{:scale (animated/mix animation scale-down-large 1)}]
:opacity (animated/mix animation 0 opactiy)}
:foreground {:transform [{:scale (animated/mix animation 1 scale-down-large)}]
:opacity (animated/mix animation 1 opactiy)}})
(defmethod type->animation :list-item
[{:keys [animation]}]
{:background {:opacity (animated/mix animation 0 opactiy)}
:foreground {:transform [{:scale (animated/mix animation 1 scale-down-small)}]
:opacity (animated/mix animation 1 opactiy)}})
(def absolute-fill
{:top 0
:bottom 0
:left 0
:right 0
:position :absolute})
(defn pressable []
(let [state (animated/value (:undetermined gesture-handler/states))
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-timing (animated/with-timing-transition active
{:duration long-duration})
animation (animated/with-timing-transition active
{:duration duration
:easing (:ease-in animated/easings)})]
(fn [{:keys [background-color border-radius type disabled
on-press on-long-press on-press-start
accessibility-label]
:or {border-radius 0
type :primary}}
& children]
(let [{:keys [background foreground]}
(type->animation {:type type
:animation animation})
handle-press (fn [] (when on-press (on-press)))
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))}]
[animated/code
{:key (str on-press on-long-press on-press-start)
:exec (animated/on-change state
[(animated/cond* (animated/eq state (:began gesture-handler/states))
(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)])])}]
[gesture-handler/tap-gesture-handler
(merge gesture-handler
{:shouldCancelWhenOutside true
:enabled (not disabled)})
[animated/view {:accessible true
:accessibility-label accessibility-label}
[animated/view {:style (merge absolute-fill
background
{:background-color background-color
:border-radius border-radius})}]
(into [animated/view {:style foreground}]
children)]]]))))

View File

@ -0,0 +1,99 @@
(ns quo.components.button.view
(:require [quo.components.button.animation :as animation]
[quo.react-native :as rn]
[quo.haptic :as haptic]
[quo.design-system.colors :as colors]
[quo.design-system.spacing :as spacing]
[quo.components.text :as text]
;; FIXME:
[status-im.ui.components.icons.vector-icons :as icons]))
(defn style-container [type]
(merge {:height 44
:align-items :center
:justify-content :center
:flex-direction :row}
(case type
:primary (:base spacing/padding-horizontal)
:secondary (:tiny spacing/padding-horizontal)
:icon {:padding-horizontal 2}
nil)))
(defn content-style [type]
(case type
:primary (:base spacing/padding-horizontal)
:secondary (:x-tiny spacing/padding-horizontal)
:icon (:tiny spacing/padding-horizontal)
nil))
(defn themes [theme]
(case theme
:main {:icon-color (:icon-04 @colors/theme)
:background-color (:interactive-02 @colors/theme)
:text-color (:text-04 @colors/theme)}
:negative {:icon-color (:negative-01 @colors/theme)
:background-color (:negative-02 @colors/theme)
:text-color (:negative-01 @colors/theme)}
:positive {:icon-color (:positive-01 @colors/theme)
:background-color (:positive-02 @colors/theme)
:text-color (:positive-01 @colors/theme)}
:accent {:icon-color (:icon-05 @colors/theme)
:background-color (:interactive-01 @colors/theme)
:text-color (:text-05 @colors/theme)}
:disabled {:icon-color (:icon-02 @colors/theme)
:background-color (:ui-01 @colors/theme)
:text-color (:text-02 @colors/theme)}))
(defn button [{:keys [on-press disabled type theme before after icon
haptic-feedback haptic-type on-long-press on-press-start
accessibility-label]
:or {theme :main
type :primary
haptic-feedback true
haptic-type :selection}}
children]
(let [theme' (if disabled :disabled theme)
{:keys [icon-color background-color text-color]}
(themes theme')
optional-haptic (fn []
(when haptic-feedback
(haptic/trigger haptic-type)))]
[animation/pressable (merge {:background-color background-color
:border-radius 8
:type type
:disabled disabled
:accessibility-label accessibility-label}
(when on-press
{:on-press (fn []
(optional-haptic)
(on-press))})
(when on-long-press
{:on-long-press (fn []
(optional-haptic)
(on-long-press))})
(when on-press-start
{:on-press-start (fn []
(optional-haptic)
(on-press-start))}))
[rn/view {:style (style-container type)}
(when before
[rn/view
[icons/icon before {:color icon-color}]])
[rn/view {:style (content-style type)}
(cond
(= type :icon)
[icons/icon icon {:color icon-color}]
(string? children)
[text/text {:weight :medium
:number-of-lines 1
:style {:color text-color}}
children]
(vector? children)
children)]
(when after
[rn/view
[icons/icon after {:color icon-color}]])]]))

View File

@ -92,6 +92,7 @@
:margin 0
:text-align-vertical :center
:flex 1
:color (:text-01 @colors/theme)
:height height}
(when-not before
{:padding-left (:base spacing/spacing)})
@ -164,7 +165,6 @@
(merge {:style (text-input-style multiline input-style before after)
:ref ref
:placeholder-text-color (:text-02 @colors/theme)
:color (:text-01 @colors/theme)
:underline-color-android :transparent
:auto-capitalize :none
:secure-text-entry secure

View File

@ -18,7 +18,7 @@
:right 0
:top (- bottom-value)
:opacity animation
:transform [{:translateY (animated/b-interpolate animation 10 0)}]}))
:transform [{:translateY (animated/mix animation 10 0)}]}))
(defn container-style []
{:z-index 2
@ -38,7 +38,7 @@
(defn tooltip []
(let [layout (reagent/atom {:height initial-height})
animation-v (animated/value 0)
animation (animated/with-timing
animation (animated/with-timing-transition
animation-v
{:easing (:ease-in animated/easings)})
on-layout (fn [evt]

View File

@ -4,13 +4,15 @@
[quo.components.safe-area :as safe-area]
[quo.components.text-input :as text-input]
[quo.components.tooltip :as tooltip]
[quo.components.text :as text]))
[quo.components.text :as text]
[quo.components.button.view :as button]))
(def text text/text)
(def header header/header)
(def animated-header animated-header/header)
(def text-input text-input/text-input)
(def tooltip tooltip/tooltip)
(def button button/button)
(def safe-area-provider safe-area/provider)
(def safe-area-consumer safe-area/consumer)
(def safe-area-view safe-area/view)

View File

@ -34,10 +34,12 @@
:text-02 "rgba(147,155,161,1)" ; Secondary text
:text-03 "rgba(255,255,255,0.7)" ; Secondary on accent
:text-04 "rgba(67,96,223,1)" ; Links text color
:text-05 "rgba(255,255,255,1)" ; Text inverse on accent
:icon-01 "rgba(0,0,0,1)" ; Primary icons
:icon-02 "rgba(147,155,161,1)" ; Secondary icons
:icon-03 "rgba(255,255,255,0.4)" ; Secondary icons on accent bg
:icon-04 "rgba(255,255,255,1)" ; Icons inverse on accent background
:icon-04 "rgba(67,96,223,1)" ; Interactive icon
:icon-05 "rgba(255,255,255,1)" ; Icons inverse on accent background
:shadow-01 "rgba(0,9,26,0.12)" ; Main shadow color
})
@ -56,10 +58,12 @@
:text-02 "rgba(131,140,145,1)"
:text-03 "rgba(255,255,255,0.7)"
:text-04 "rgba(97,119,229,1)"
:text-05 "rgba(20,20,20,1)"
:icon-01 "rgba(255,255,255,1)"
:icon-02 "rgba(131,140,145,1)"
:icon-03 "rgba(255,255,255,0.4)"
:icon-04 "rgba(20,20,20,1)"
:icon-04 "rgba(97,119,229,1)"
:icon-05 "rgba(20,20,20,1)"
:shadow-01 "rgba(0,0,0,0.75)"})
(def theme (reagent/atom light-theme))

View File

@ -23,16 +23,15 @@
(def touchable-without-feedback
(reagent/adapt-react-class touchable-without-feedback-class))
(def animated-raw-button
(def raw-button
(reagent/adapt-react-class
(createNativeWrapper
(.createAnimatedComponent animated touchable-without-feedback-class))))
(createNativeWrapper (.createAnimatedComponent animated PureNativeButton)
#js {:shouldActivateOnStart true
:shouldCancelWhenOutside true})))
(def state State)
(def states {:began (oget state "BEGAN")
:active (oget state "ACTIVE")
:cancelled (oget state "CANCELLED")
:end (oget state "END")
:failed (oget state "FAILED")
:undetermined (oget state "UNDETERMINED")})
(def states {:began (oget State "BEGAN")
:active (oget State "ACTIVE")
:cancelled (oget State "CANCELLED")
:end (oget State "END")
:failed (oget State "FAILED")
:undetermined (oget State "UNDETERMINED")})

View File

@ -1,6 +1,5 @@
(ns status-im.ui.components.button.haptic
(:require [oops.core :refer [ocall]]
[react-native-haptic-feedback :default react-native-haptic-feedback]))
(ns quo.haptic
(:require [react-native-haptic-feedback :default react-native-haptic-feedback]))
(def haptic-methods
{:selection "selection"
@ -22,4 +21,4 @@
})
(defn trigger [method]
(ocall react-native-haptic-feedback "trigger" (get haptic-methods method)))
(.trigger ^js react-native-haptic-feedback (get haptic-methods method)))

View File

@ -0,0 +1,75 @@
(ns quo.previews.button
(: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 descriptor [{:label "Type:"
:key :type
:type :select
:options [{:key :primary
:value "Primary"}
{:key :secondary
:value "Secondary"}
{:key :icon
:value "Icon"}]}
{:label "Theme:"
:key :theme
:type :select
:options [{:key :main
:value "Main"}
{:key :negative
:value "Negative"}
{:key :positive
:value "Positive"}
{:key :accent
:value "Accent"}]}
{:label "After icon:"
:key :after
:type :boolean}
{:label "Before icon:"
:key :before
:type :boolean}
{:label "Disabled:"
:key :disabled
:type :boolean}
{:label "Label"
:key :label
:type :text}])
(defn cool-preview []
(let [state (reagent/atom {:label "Press Me"
:type :primary
:theme :main
:icon :main-icons/share})
theme (reagent/cursor state [:theme])
label (reagent/cursor state [:label])
before (reagent/cursor state [:before])
after (reagent/cursor state [:after])]
(fn []
[rn/view {:margin-bottom 50
:padding 16}
[rn/view {:flex 1}
[preview/customizer state descriptor]]
[rn/view {:padding-vertical 16
:flex-direction :row
:justify-content :center}
[quo/button (merge (dissoc @state
:theme :before :after)
{:on-press #(println "Hello world!")}
(when @theme
{:theme @theme})
(when @before
{:before :main-icons/back})
(when @after
{:after :main-icons/next}))
@label]]])))
(defn preview-button []
[rn/view {:background-color (:ui-background @colors/theme)
:flex 1}
[rn/flat-list {:flex 1
:keyboardShouldPersistTaps :always
:header [cool-preview]
:key-fn str}]])

View File

@ -3,6 +3,7 @@
[quo.previews.text :as text]
[quo.previews.text-input :as text-input]
[quo.previews.tooltip :as tooltip]
[quo.previews.button :as button]
[quo.react-native :as rn]
[quo.core :as quo]
[reagent.core :as reagent]
@ -21,7 +22,10 @@
:component text-input/preview-text}
{:name :headers
:insets {:top false}
:component header/preview-header}])
:component header/preview-header}
{:name :button
:insets {:top false}
:component button/preview-button}])
(defn theme-switcher []
[rn/view {:style {:flex-direction :row

View File

@ -7,6 +7,7 @@
(def container {:flex-direction :row
:padding-vertical 8
:flex 1
:align-items :center})
(defn touchable-style []
@ -104,10 +105,11 @@
[rn/view {:style label-style}
[quo/text label]]
[rn/view {:style {:flex 0.6}}
[rn/modal {:visible @open
:on-request-close #(reset! open false)
:transparent :true
:animation :slide}
[rn/modal {:visible @open
:on-request-close #(reset! open false)
:statusBarTranslucent true
:transparent true
:animation :slide}
[rn/view {:style (modal-container)}
[rn/view {:style (modal-view)}
[rn/scroll-view
@ -144,14 +146,14 @@
[quo/text "↓"]]]]]))))
(defn customizer [state descriptors]
[rn/view
[rn/view {:style {:flex 1}}
(doall
(for [{:keys [key type]
:as desc} descriptors
:let [descriptor (merge desc
{:state state})]]
^{:key key}
[rn/view
[:<>
(case type
:boolean [customizer-boolean descriptor]
:text [customizer-text descriptor]

View File

@ -17,7 +17,7 @@
:color colors/black})
(defn accounts-mnemonic [{:keys [animation]}]
{:opacity (animated/b-interpolate animation 1 0)
{:opacity (animated/mix animation 1 0)
:flex 1
:justify-content :center
:position :absolute