feat: record audio button

Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
Brian Sztamfater 2022-10-05 01:55:24 -03:00
parent b074e9c58e
commit c0c0742687
No known key found for this signature in database
GPG Key ID: 59EB921E0706B48F
9 changed files with 864 additions and 2 deletions

View File

@ -0,0 +1,18 @@
import { useDerivedValue } from 'react-native-reanimated';
const MAX_SCALE = 1.8;
export function ringScale(scale, subtract) {
return useDerivedValue(
function () {
'worklet'
const value = scale.value;
const maxDelta = MAX_SCALE - 1;
const maxDeltaDiff = 1 - maxDelta;
const maxVirtualScale = MAX_SCALE + maxDelta;
const decimals = value - Math.floor(value);
const normalizedValue = value >= maxVirtualScale ? (decimals + ((parseInt(value) - 1) * maxDeltaDiff) + 1) : value;
return (((normalizedValue - subtract) > MAX_SCALE ? normalizedValue - maxDelta : normalizedValue) - subtract);
}
);
}

View File

@ -1,3 +1,5 @@
import { useDerivedValue, interpolate } from 'react-native-reanimated';
// Generic Worklets // Generic Worklets
export function applyAnimationsToStyle(animations, style) { export function applyAnimationsToStyle(animations, style) {
@ -27,3 +29,12 @@ export function applyAnimationsToStyle(animations, style) {
return Object.assign(animatedStyle, style); return Object.assign(animatedStyle, style);
}; };
}; };
export function interpolateValue(sharedValue, inputRange, outputRange) {
return useDerivedValue(
function () {
'worklet'
return interpolate(sharedValue.value, inputRange, outputRange);
}
);
}

View File

@ -284,6 +284,8 @@
(def shell-worklets #js {}) (def shell-worklets #js {})
(def record-audio-worklets #js {})
;; Update i18n_resources.cljs ;; Update i18n_resources.cljs
(defn mock [module] (defn mock [module]
(case module (case module
@ -331,6 +333,7 @@
"react-native-svg" react-native-svg "react-native-svg" react-native-svg
"../src/js/worklet_factory.js" worklet-factory "../src/js/worklet_factory.js" worklet-factory
"../src/js/shell_worklets.js" shell-worklets "../src/js/shell_worklets.js" shell-worklets
"../src/js/record_audio_worklets.js" record-audio-worklets
"./fleets.js" default-fleets "./fleets.js" default-fleets
"./chats.js" default-chats "./chats.js" default-chats
"@walletconnect/client" wallet-connect-client "@walletconnect/client" wallet-connect-client

View File

@ -0,0 +1,212 @@
(ns quo2.components.record-audio.record-audio.style
(:require [quo2.foundations.colors :as colors]
[react-native.reanimated :as reanimated]))
(defn animated-circle [scale opacity color]
(reanimated/apply-animations-to-style
{:transform [{:scale scale}]
:opacity opacity}
{:width 56
:height 56
:border-width 1
:border-color color
:border-radius 28
:position :absolute
:justify-content :center
:align-items :center
:z-index 0}))
(defn record-button-big-container [translate-x translate-y opacity]
(reanimated/apply-animations-to-style
{:transform [{:translateY translate-y}
{:translateX translate-x}]
:opacity opacity}
{:position :absolute
:bottom 0
:right 0
:width 96
:height 96
:align-items :center
:justify-content :center
:z-index 0}))
(defn record-button-big-body [button-color]
{:width 56
:height 56
:border-radius 28
:justify-content :center
:align-items :center
:background-color button-color
:overflow :hidden})
(defn record-button-big-red-overlay [red-overlay-opacity]
(reanimated/apply-animations-to-style
{:opacity red-overlay-opacity}
{:position :absolute
:top 0
:left 0
:right 0
:bottom 0
:background-color colors/danger-50}))
(defn record-button-big-gray-overlay [gray-overlay-opacity]
(reanimated/apply-animations-to-style
{:opacity gray-overlay-opacity}
{:position :absolute
:top 0
:left 0
:right 0
:bottom 0
:background-color (colors/theme-colors colors/neutral-80-opa-5-opaque colors/neutral-80)}))
(defn record-button-big-icon-container [icon-opacity]
(reanimated/apply-animations-to-style
{:opacity icon-opacity}
{}))
(def stop-icon
{:width 13
:height 13
:border-radius 4
:background-color colors/white})
(defn send-button-container [opacity]
(reanimated/apply-animations-to-style
{:opacity opacity}
{:justify-content :center
:align-items :center
:position :absolute
:width 56
:height 56
:top 0
:right 20}))
(defn send-button-connector [opacity width height border-radius-first-half border-radius-second-half]
(reanimated/apply-animations-to-style
{:opacity opacity
:width width
:height height
:border-bottom-left-radius border-radius-second-half
:border-top-left-radius border-radius-first-half
:border-top-right-radius border-radius-first-half
:border-bottom-right-radius border-radius-second-half}
{:justify-content :center
:align-items :center
:align-self :center
:background-color colors/primary-50
:z-index 0}))
(defn send-button [translate-y opacity]
(reanimated/apply-animations-to-style
{:transform [{:translateY translate-y}]
:opacity opacity}
{:justify-content :center
:align-items :center
:background-color colors/primary-50
:width 32
:height 32
:border-radius 16
:position :absolute
:top 0
:right 32
:z-index 10}))
(def send-icon-container
{:z-index 10})
(defn lock-button-container [opacity]
(reanimated/apply-animations-to-style
{:opacity opacity}
{:transform [{:rotate "45deg"}]
:justify-content :center
:align-items :center
:position :absolute
:width 56
:height 56
:top 20
:left 20}))
(defn lock-button-connector [opacity width height border-radius-first-half border-radius-second-half]
(reanimated/apply-animations-to-style
{:opacity opacity
:width width
:height height
:border-bottom-left-radius border-radius-first-half
:border-top-left-radius border-radius-first-half
:border-top-right-radius border-radius-second-half
:border-bottom-right-radius border-radius-second-half}
{:justify-content :center
:align-items :center
:align-self :center
:background-color (colors/theme-colors colors/neutral-80-opa-5-opaque colors/neutral-80)
:overflow :hidden}))
(defn lock-button [translate-x-y opacity]
(reanimated/apply-animations-to-style
{:transform [{:translateX translate-x-y}
{:translateY translate-x-y}]
:opacity opacity}
{:width 32
:height 32
:justify-content :center
:align-items :center
:background-color (colors/theme-colors colors/neutral-80-opa-5-opaque colors/neutral-80)
:border-radius 16
:position :absolute
:top 24
:left 24
:overflow :hidden
:z-index 12}))
(defn delete-button-container [opacity]
(reanimated/apply-animations-to-style
{:opacity opacity}
{:justify-content :center
:align-items :center
:position :absolute
:width 56
:height 56
:bottom 20
:left 0}))
(defn delete-button-connector [opacity width height border-radius-first-half border-radius-second-half]
(reanimated/apply-animations-to-style
{:opacity opacity
:width width
:height height
:border-bottom-left-radius border-radius-first-half
:border-top-left-radius border-radius-first-half
:border-top-right-radius border-radius-second-half
:border-bottom-right-radius border-radius-second-half}
{:justify-content :center
:align-items :center
:align-self :center
:background-color colors/danger-50
:z-index 0}))
(defn delete-button [translate-x opacity]
(reanimated/apply-animations-to-style
{:transform [{:translateX translate-x}]
:opacity opacity}
{:width 32
:height 32
:justify-content :center
:align-items :center
:background-color colors/danger-50
:border-radius 16
:position :absolute
:top 76
:left 0
:z-index 11}))
(defn record-button-container [opacity]
(reanimated/apply-animations-to-style
{:opacity opacity}
{:margin-bottom 32
:margin-right 32}))
(def input-container
{:width 140
:height 140
:align-items :flex-end
:justify-content :flex-end})

View File

@ -0,0 +1,553 @@
(ns quo2.components.record-audio.record-audio.view
(:require [quo.react :refer [memo effect!]]
[react-native.core :as rn]
[quo2.foundations.colors :as colors]
[quo2.components.icon :as icons]
[quo2.components.buttons.button :as button]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[cljs-bean.core :as bean]
[quo2.components.record-audio.record-audio.style :as style]
[status-im.utils.utils :as utils]
[oops.core :as oops]))
(def ^:private scale-to-each 1.8)
(def ^:private scale-to-total 2.6)
(def ^:private scale-padding 0.16)
(def ^:private opacity-from-lock 1)
(def ^:private opacity-from-default 0.5)
(def ^:private signal-anim-duration 3900)
(def ^:private signal-anim-duration-2 1950)
(def ^:private animated-ring
(reagent/adapt-react-class
(memo
(fn [props]
(let [{:keys [scale opacity color]} (bean/bean props)]
(reagent/as-element
[reanimated/view {:style (style/animated-circle scale opacity color)}]))))))
(def ^:private record-button-area-big
{:width 56
:height 56
:x 64
:y 64})
(def ^:private record-button-area
{:width 48
:height 48
:x 68
:y 68})
(defn- delete-button-area [active?]
{:width (if active? 72 82)
:height 56
:x (if active? -16 -32)
:y (if active? 64 70)})
(defn- lock-button-area [active?]
{:width (if active? 72 100)
:height (if active? 72 102)
:x -32
:y -32})
(defn- send-button-area [active?]
{:width 56
:height (if active? 72 92)
:x 68
:y (if active? -16 -32)})
(defn- touch-inside-area? [{:keys [location-x location-y ignore-min-y? ignore-max-y? ignore-min-x? ignore-max-x?]}
{:keys [width height x y]}]
(let [max-x (+ x width)
max-y (+ y height)]
(and
(and
(or ignore-min-x? (>= location-x x))
(or ignore-max-x? (<= location-x max-x)))
(and
(or ignore-min-y? (>= location-y y))
(or ignore-max-y? (<= location-y max-y))))))
(def record-audio-worklets (js/require "../src/js/record_audio_worklets.js"))
(defn- ring-scale [scale substract]
(.ringScale ^js record-audio-worklets
scale
substract))
(defn- record-button-big [recording? ready-to-send? ready-to-lock? ready-to-delete? record-button-is-animating? record-button-at-initial-position? locked? reviewing-audio? recording-timer recording-length-ms clear-timeout touch-active?]
[:f>
(fn []
(let [scale (reanimated/use-shared-value 1)
opacity (reanimated/use-shared-value 0)
opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default)
animations (map
(fn [index]
(let [ring-scale (ring-scale scale (* scale-padding index))]
{:scale ring-scale
:opacity (reanimated/interpolate ring-scale [1 scale-to-each] [opacity-from 0])}))
(range 0 5))
rings-color (cond
@ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque colors/neutral-80)
@ready-to-delete? colors/danger-50
:else colors/primary-50)
translate-y (reanimated/use-shared-value 0)
translate-x (reanimated/use-shared-value 0)
button-color colors/primary-50
icon-color (if (and (not (colors/dark?)) @ready-to-lock?) colors/black colors/white)
icon-opacity (reanimated/use-shared-value 1)
red-overlay-opacity (reanimated/use-shared-value 0)
gray-overlay-opacity (reanimated/use-shared-value 0)
complete-animation (fn []
(cond
(and @ready-to-lock? (not @record-button-is-animating?))
(do
(reset! locked? true)
(reset! ready-to-lock? false))
(and (not @locked?) (not @reviewing-audio?))
(do
(reset! recording? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(reset! ready-to-lock? false)
(utils/clear-interval @recording-timer)
(reset! recording-length-ms 0))))
start-animation (fn []
(reanimated/set-shared-value opacity 1)
(reanimated/animate-shared-value-with-timing scale 2.6 signal-anim-duration :linear)
;; TODO: Research if we can implement this with withSequence method from Reanimated 2
;; GitHub issue [#14561]: https://github.com/status-im/status-mobile/issues/14561
(reset! clear-timeout (utils/set-timeout #(do (reanimated/set-shared-value scale scale-to-each)
(reanimated/animate-shared-value-with-delay-repeat scale scale-to-total signal-anim-duration-2 :linear 0 -1))
signal-anim-duration)))
stop-animation (fn []
(reanimated/set-shared-value opacity 0)
(reanimated/cancel-animation scale)
(reanimated/set-shared-value scale 1)
(when @clear-timeout (utils/clear-timeout @clear-timeout)))
start-y-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(reanimated/animate-shared-value-with-timing translate-y -64 250 :easing1)
(reanimated/animate-shared-value-with-delay icon-opacity 0 33.33 :linear 76.66)
(utils/set-timeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-y-animation (fn []
(reanimated/animate-shared-value-with-timing translate-y 0 300 :easing1)
(reanimated/animate-shared-value-with-timing icon-opacity 1 500 :linear)
(utils/set-timeout (fn [] (reset! record-button-at-initial-position? true)) 500))
start-x-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(reanimated/animate-shared-value-with-timing translate-x -64 250 :easing1)
(reanimated/animate-shared-value-with-delay icon-opacity 0 33.33 :linear 76.66)
(reanimated/animate-shared-value-with-timing red-overlay-opacity 1 33.33 :linear)
(utils/set-timeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-x-animation (fn []
(reanimated/animate-shared-value-with-timing translate-x 0 300 :easing1)
(reanimated/animate-shared-value-with-timing icon-opacity 1 500 :linear)
(reanimated/animate-shared-value-with-timing red-overlay-opacity 0 100 :linear)
(utils/set-timeout (fn [] (reset! record-button-at-initial-position? true)) 500))
start-x-y-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(reanimated/animate-shared-value-with-timing translate-y -44 200 :easing1)
(reanimated/animate-shared-value-with-timing translate-x -44 200 :easing1)
(reanimated/animate-shared-value-with-delay icon-opacity 0 33.33 :linear 33.33)
(reanimated/animate-shared-value-with-timing gray-overlay-opacity 1 33.33 :linear)
(utils/set-timeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
200))
reset-x-y-animation (fn []
(reanimated/animate-shared-value-with-timing translate-y 0 300 :easing1)
(reanimated/animate-shared-value-with-timing translate-x 0 300 :easing1)
(reanimated/animate-shared-value-with-timing icon-opacity 1 500 :linear)
(reanimated/animate-shared-value-with-timing gray-overlay-opacity 0 800 :linear)
(utils/set-timeout (fn [] (reset! record-button-at-initial-position? true)) 800))]
(effect! #(cond
@recording?
(start-animation)
(not @ready-to-lock?)
(stop-animation))
[@recording?])
(effect! #(if @ready-to-lock?
(start-x-y-animation)
(reset-x-y-animation))
[@ready-to-lock?])
(effect! #(if @ready-to-send?
(start-y-animation)
(reset-y-animation))
[@ready-to-send?])
(effect! #(if @ready-to-delete?
(start-x-animation)
(reset-x-animation))
[@ready-to-delete?])
[reanimated/view {:style (style/record-button-big-container translate-x translate-y opacity)
:pointer-events :none}
[:<>
(map-indexed
(fn [id animation]
^{:key id}
[animated-ring {:scale (:scale animation)
:opacity (:opacity animation)
:color rings-color}])
animations)]
[rn/view {:style (style/record-button-big-body button-color)}
[reanimated/view {:style (style/record-button-big-red-overlay red-overlay-opacity)}]
[reanimated/view {:style (style/record-button-big-gray-overlay gray-overlay-opacity)}]
[reanimated/view {:style (style/record-button-big-icon-container icon-opacity)}
(if @locked?
[rn/view {:style style/stop-icon}]
[icons/icon :i/audio {:color icon-color}])]]]))])
(defn- send-button [recording? ready-to-send? reviewing-audio?]
[:f>
(fn []
(let [opacity (reanimated/use-shared-value 0)
translate-y (reanimated/use-shared-value 20)
connector-opacity (reanimated/use-shared-value 0)
width (reanimated/use-shared-value 12)
height (reanimated/use-shared-value 24)
border-radius-first-half (reanimated/use-shared-value 16)
border-radius-second-half (reanimated/use-shared-value 8)
start-y-animation (fn []
(reanimated/animate-shared-value-with-delay translate-y 12 50 :linear 133.33)
(reanimated/animate-shared-value-with-delay connector-opacity 1 0 :easing1 93.33)
(reanimated/animate-shared-value-with-delay width 56 83.33 :easing1 80)
(reanimated/animate-shared-value-with-delay height 56 83.33 :easing1 80)
(reanimated/animate-shared-value-with-delay border-radius-first-half 28 83.33 :easing1 80)
(reanimated/animate-shared-value-with-delay border-radius-second-half 28 83.33 :easing1 80))
reset-y-animation (fn []
(reanimated/animate-shared-value-with-timing translate-y 0 100 :linear)
(reanimated/set-shared-value connector-opacity 0)
(reanimated/set-shared-value width 12)
(reanimated/set-shared-value height 24)
(reanimated/set-shared-value border-radius-first-half 16)
(reanimated/set-shared-value border-radius-second-half 8))
fade-in-animation (fn []
(reanimated/animate-shared-value-with-timing translate-y 0 200 :linear)
(reanimated/animate-shared-value-with-timing opacity 1 200 :linear))
fade-out-animation (fn []
(reanimated/animate-shared-value-with-timing translate-y (if @reviewing-audio? 76 20) 200 :linear)
(when-not @reviewing-audio? (reanimated/animate-shared-value-with-timing opacity 0 200 :linear))
(reanimated/set-shared-value connector-opacity 0)
(reanimated/set-shared-value width 24)
(reanimated/set-shared-value height 12)
(reanimated/set-shared-value border-radius-first-half 8)
(reanimated/set-shared-value border-radius-second-half 16))
fade-out-reset-animation (fn []
(reanimated/animate-shared-value-with-timing opacity 0 200 :linear)
(reanimated/animate-shared-value-with-delay translate-y 20 0 :linear 200)
(reanimated/set-shared-value connector-opacity 0)
(reanimated/set-shared-value width 24)
(reanimated/set-shared-value height 12)
(reanimated/set-shared-value border-radius-first-half 8)
(reanimated/set-shared-value border-radius-second-half 16))]
(effect! #(if @recording?
(fade-in-animation)
(fade-out-animation))
[@recording?])
(effect! #(when-not @reviewing-audio?
(fade-out-reset-animation))
[@reviewing-audio?])
(effect! #(cond
@ready-to-send?
(start-y-animation)
@recording? (reset-y-animation))
[@ready-to-send?])
[:<>
[reanimated/view {:style (style/send-button-container opacity)}
[reanimated/view {:style (style/send-button-connector connector-opacity width height border-radius-first-half border-radius-second-half)}]]
[reanimated/view {:style (style/send-button translate-y opacity)
:pointer-events :none}
[icons/icon :i/arrow-up {:color colors/white
:size 20
:container-style style/send-icon-container}]]]))])
(defn- lock-button [recording? ready-to-lock? locked?]
[:f>
(fn []
(let [translate-x-y (reanimated/use-shared-value 20)
opacity (reanimated/use-shared-value 0)
connector-opacity (reanimated/use-shared-value 0)
width (reanimated/use-shared-value 24)
height (reanimated/use-shared-value 12)
border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8)
start-x-y-animation (fn []
(reanimated/animate-shared-value-with-delay translate-x-y 8 50 :linear 116.66)
(reanimated/animate-shared-value-with-delay connector-opacity 1 0 :easing1 80)
(reanimated/animate-shared-value-with-delay width 56 83.33 :easing1 63.33)
(reanimated/animate-shared-value-with-delay height 56 83.33 :easing1 63.33)
(reanimated/animate-shared-value-with-delay border-radius-first-half 28 83.33 :easing1 63.33)
(reanimated/animate-shared-value-with-delay border-radius-second-half 28 83.33 :easing1 63.33))
reset-x-y-animation (fn []
(reanimated/animate-shared-value-with-timing translate-x-y 0 100 :linear)
(reanimated/set-shared-value connector-opacity 0)
(reanimated/set-shared-value width 24)
(reanimated/set-shared-value height 12)
(reanimated/set-shared-value border-radius-first-half 8)
(reanimated/set-shared-value border-radius-second-half 16))
fade-in-animation (fn []
(reanimated/animate-shared-value-with-timing translate-x-y 0 220 :linear)
(reanimated/animate-shared-value-with-timing opacity 1 220 :linear))
fade-out-animation (fn []
(reanimated/animate-shared-value-with-timing translate-x-y 20 200 :linear)
(reanimated/animate-shared-value-with-timing opacity 0 200 :linear)
(reanimated/set-shared-value connector-opacity 0)
(reanimated/set-shared-value width 24)
(reanimated/set-shared-value height 12)
(reanimated/set-shared-value border-radius-first-half 8)
(reanimated/set-shared-value border-radius-second-half 16))]
(effect! #(if @recording?
(fade-in-animation)
(fade-out-animation))
[@recording?])
(effect! #(cond
@ready-to-lock?
(start-x-y-animation)
(and @recording? (not @locked?))
(reset-x-y-animation))
[@ready-to-lock?])
(effect! #(if @locked?
(fade-out-animation)
(reset-x-y-animation))
[@locked?])
[:<>
[reanimated/view {:style (style/lock-button-container opacity)}
[reanimated/view {:style (style/lock-button-connector connector-opacity width height border-radius-first-half border-radius-second-half)}]]
[reanimated/view {:style (style/lock-button translate-x-y opacity)
:pointer-events :none}
[icons/icon (if @ready-to-lock? :i/locked :i/unlocked)
{:color (colors/theme-colors colors/black colors/white)
:size 20}]]]))])
(defn- delete-button [recording? ready-to-delete?]
[:f>
(fn []
(let [opacity (reanimated/use-shared-value 0)
translate-x (reanimated/use-shared-value 20)
connector-opacity (reanimated/use-shared-value 0)
width (reanimated/use-shared-value 24)
height (reanimated/use-shared-value 12)
border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8)
start-x-animation (fn []
(reanimated/animate-shared-value-with-delay translate-x 12 50 :linear 133.33)
(reanimated/animate-shared-value-with-delay connector-opacity 1 0 :easing1 93.33)
(reanimated/animate-shared-value-with-delay width 56 83.33 :easing1 80)
(reanimated/animate-shared-value-with-delay height 56 83.33 :easing1 80)
(reanimated/animate-shared-value-with-delay border-radius-first-half 28 83.33 :easing1 80)
(reanimated/animate-shared-value-with-delay border-radius-second-half 28 83.33 :easing1 80))
reset-x-animation (fn []
(reanimated/animate-shared-value-with-timing translate-x 0 100 :linear)
(reanimated/set-shared-value connector-opacity 0)
(reanimated/set-shared-value width 24)
(reanimated/set-shared-value height 12)
(reanimated/set-shared-value border-radius-first-half 8)
(reanimated/set-shared-value border-radius-second-half 16))
fade-in-animation (fn []
(reanimated/animate-shared-value-with-timing translate-x 0 200 :linear)
(reanimated/animate-shared-value-with-timing opacity 1 200 :linear))
fade-out-animation (fn []
(reanimated/animate-shared-value-with-timing translate-x 20 200 :linear)
(reanimated/animate-shared-value-with-timing opacity 0 200 :linear)
(reanimated/set-shared-value connector-opacity 0)
(reanimated/set-shared-value width 24)
(reanimated/set-shared-value height 12)
(reanimated/set-shared-value border-radius-first-half 8)
(reanimated/set-shared-value border-radius-second-half 16))]
(effect! #(if @recording?
(fade-in-animation)
(fade-out-animation))
[@recording?])
(effect! #(cond
@ready-to-delete?
(start-x-animation)
@recording?
(reset-x-animation))
[@ready-to-delete?])
[:<>
[reanimated/view {:style (style/delete-button-container opacity)}
[reanimated/view {:style (style/delete-button-connector connector-opacity width height border-radius-first-half border-radius-second-half)}]]
[reanimated/view {:style (style/delete-button translate-x opacity)
:pointer-events :none}
[icons/icon :i/delete {:color colors/white
:size 20}]]]))])
(defn- record-button [recording? reviewing-audio?]
[:f>
(fn []
(let [opacity (reanimated/use-shared-value 1)
show-animation #(reanimated/set-shared-value opacity 1)
hide-animation #(reanimated/set-shared-value opacity 0)]
(effect! #(if (or @recording? @reviewing-audio?)
(hide-animation)
(show-animation))
[@recording? @reviewing-audio?])
[reanimated/view {:style (style/record-button-container opacity)}
[button/button {:type :outline
:size 32
:width 32
:accessibility-label :mic-button}
[icons/icon :i/audio {:color (colors/theme-colors colors/neutral-100 colors/white)}]]]))])
(defn input-view []
[:f>
(fn []
(let [recording? (reagent/atom false)
locked? (reagent/atom false)
ready-to-send? (reagent/atom false)
ready-to-lock? (reagent/atom false)
ready-to-delete? (reagent/atom false)
reviewing-audio? (reagent/atom false)
clear-timeout (atom nil)
record-button-at-initial-position? (atom true)
record-button-is-animating? (atom false)
touch-active? (atom false)
recording-timer (atom nil)
recording-length-ms (atom 0)
on-start-should-set-responder (fn [^js e]
(when-not @locked?
(let [pressed-record-button? (touch-inside-area?
{:location-x (oops/oget e "nativeEvent.locationX")
:location-y (oops/oget e "nativeEvent.locationY")
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
record-button-area)]
(when-not @reviewing-audio?
(reset! recording? pressed-record-button?)
(when pressed-record-button?
;; TODO: By now we just track recording length, we need to add actual audio recording logic
;; GitHub issue [#14558]: https://github.com/status-im/status-mobile/issues/14558
(reset! recording-timer (utils/set-interval #(reset! recording-length-ms (+ @recording-length-ms 500)) 500))))
(reset! touch-active? true)))
true)
on-responder-move (fn [^js e]
(when-not @locked?
(let [location-x (oops/oget e "nativeEvent.locationX")
location-y (oops/oget e "nativeEvent.locationY")
page-x (oops/oget e "nativeEvent.pageX")
page-y (oops/oget e "nativeEvent.pageY")
moved-to-send-button? (touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? true
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? true}
(send-button-area @ready-to-send?))
moved-to-delete-button? (touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? false
:ignore-max-y? true
:ignore-min-x? true
:ignore-max-x? false}
(delete-button-area @ready-to-delete?))
moved-to-lock-button? (touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
(lock-button-area @ready-to-lock?))
moved-to-record-button? (and
(touch-inside-area?
{:location-x location-x
:location-y location-y
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
record-button-area-big)
(not= location-x page-x)
(not= location-y page-y))]
(cond
(and
(or
(and moved-to-record-button? @ready-to-lock?)
(and (not @locked?) moved-to-lock-button? @record-button-at-initial-position?))
(not @ready-to-delete?)
(not @ready-to-send?)
@recording?) (reset! ready-to-lock? moved-to-lock-button?)
(and
(or
(and moved-to-record-button? @ready-to-delete?)
(and moved-to-delete-button? @record-button-at-initial-position?))
(not @ready-to-lock?)
(not @ready-to-send?)
@recording?) (reset! ready-to-delete? moved-to-delete-button?)
(and
(or
(and moved-to-record-button? @ready-to-send?)
(and moved-to-send-button? @record-button-at-initial-position?))
(not @ready-to-lock?)
(not @ready-to-delete?)
@recording?) (reset! ready-to-send? moved-to-send-button?)))))
on-responder-release (fn [^js e]
(let [on-record-button? (touch-inside-area?
{:location-x (oops/oget e "nativeEvent.locationX")
:location-y (oops/oget e "nativeEvent.locationY")
:ignore-min-y? false
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
(if @reviewing-audio? record-button-area record-button-area-big))]
(cond
(and @reviewing-audio? on-record-button?)
(reset! reviewing-audio? false)
(and @ready-to-lock? (not @record-button-is-animating?))
(do
(reset! locked? true)
(reset! ready-to-lock? false))
(and (not @reviewing-audio?) on-record-button?)
(do
(when (>= @recording-length-ms 500) (reset! reviewing-audio? true))
(reset! locked? false)
(reset! recording? false)
(reset! ready-to-lock? false)
(utils/clear-interval @recording-timer)
(reset! recording-length-ms 0))
(and (not @locked?) (not @reviewing-audio?) (not @record-button-is-animating?))
(do
(reset! recording? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(reset! ready-to-lock? false)
(utils/clear-interval @recording-timer)
(reset! recording-length-ms 0))))
(reset! touch-active? false))]
(fn []
[rn/view {:style style/input-container
:pointer-events :box-only
:on-start-should-set-responder on-start-should-set-responder
:on-responder-move on-responder-move
:on-responder-release on-responder-release}
[delete-button recording? ready-to-delete?]
[lock-button recording? ready-to-lock? locked?]
[send-button recording? ready-to-send? reviewing-audio?]
[record-button-big
recording?
ready-to-send?
ready-to-lock?
ready-to-delete?
record-button-is-animating?
record-button-at-initial-position?
locked?
reviewing-audio?
recording-timer
recording-length-ms
clear-timeout
touch-active?]
[record-button recording? reviewing-audio?]])))])

View File

@ -13,6 +13,20 @@
(let [rgb (string/split value #",")] (let [rgb (string/split value #",")]
(str (string/join "," (butlast rgb)) "," opacity ")"))))) (str (string/join "," (butlast rgb)) "," opacity ")")))))
(defn- alpha-opaque [value opacity]
(when value
(if (string/starts-with? value "#")
(let [hex (string/replace value #"#" "")
r (- 255 (* opacity (- 255 (js/parseInt (subs hex 0 2) 16))))
g (- 255 (* opacity (- 255 (js/parseInt (subs hex 2 4) 16))))
b (- 255 (* opacity (- 255 (js/parseInt (subs hex 4 6) 16))))]
(str "rgb(" r "," g "," b ")"))
(let [rgb (string/split value #",")
r (- 255 (* opacity (- 255 (get rgb 0))))
g (- 255 (* opacity (- 255 (get rgb 1))))
b (- 255 (* opacity (- 255 (get rgb 2))))]
(str "rgb(" r "," g "," b ")")))))
(def theme-alpha (def theme-alpha
(memoize (memoize
(fn (fn
@ -49,6 +63,7 @@
;;80 with transparency ;;80 with transparency
(def neutral-80-opa-5 (alpha neutral-80 0.05)) (def neutral-80-opa-5 (alpha neutral-80 0.05))
(def neutral-80-opa-10 (alpha neutral-80 0.1)) (def neutral-80-opa-10 (alpha neutral-80 0.1))
(def neutral-80-opa-15 (alpha neutral-80 0.15))
(def neutral-80-opa-20 (alpha neutral-80 0.2)) (def neutral-80-opa-20 (alpha neutral-80 0.2))
(def neutral-80-opa-30 (alpha neutral-80 0.3)) (def neutral-80-opa-30 (alpha neutral-80 0.3))
(def neutral-80-opa-40 (alpha neutral-80 0.4)) (def neutral-80-opa-40 (alpha neutral-80 0.4))
@ -75,6 +90,9 @@
(def neutral-100-opa-95 (alpha neutral-100 0.95)) (def neutral-100-opa-95 (alpha neutral-100 0.95))
(def neutral-100-opa-100 (alpha neutral-100 1)) (def neutral-100-opa-100 (alpha neutral-100 1))
;;80 with transparency opaque
(def neutral-80-opa-5-opaque (alpha-opaque neutral-80 0.05))
;;;;White ;;;;White
;;Solid ;;Solid
@ -93,6 +111,11 @@
(def white-opa-90 (alpha white 0.9)) (def white-opa-90 (alpha white 0.9))
(def white-opa-95 (alpha white 0.95)) (def white-opa-95 (alpha white 0.95))
;;;;Black
;;Solid
(def black "#000000")
;;;;Primary ;;;;Primary
;;Solid ;;Solid

View File

@ -4,7 +4,7 @@
[clojure.string :as string] [clojure.string :as string]
["react-native-linear-gradient" :default LinearGradient] ["react-native-linear-gradient" :default LinearGradient]
["react-native-reanimated" :default reanimated ["react-native-reanimated" :default reanimated
:refer (useSharedValue useAnimatedStyle withTiming withDelay withSpring withRepeat Easing Keyframe)])) :refer (useSharedValue useAnimatedStyle withTiming withDelay withSpring withRepeat Easing Keyframe cancelAnimation)]))
;; Animated Components ;; Animated Components
(def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated))) (def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated)))
@ -25,6 +25,7 @@
(def with-spring withSpring) (def with-spring withSpring)
(def key-frame Keyframe) (def key-frame Keyframe)
(def with-repeat withRepeat) (def with-repeat withRepeat)
(def cancel-animation cancelAnimation)
;; Easings ;; Easings
(def bezier (.-bezier ^js Easing)) (def bezier (.-bezier ^js Easing))
@ -54,6 +55,12 @@
;; Worklets ;; Worklets
(def worklet-factory (js/require "../src/js/worklet_factory.js")) (def worklet-factory (js/require "../src/js/worklet_factory.js"))
(defn interpolate [shared-value input-range output-range]
(.interpolateValue ^js worklet-factory
shared-value
(clj->js input-range)
(clj->js output-range)))
;;;; Component Animations ;;;; Component Animations
;; kebab-case styles are not working for worklets ;; kebab-case styles are not working for worklets
@ -75,4 +82,16 @@
(defn animate-shared-value-with-repeat [anim val duration easing number-of-repetitions reverse?] (defn animate-shared-value-with-repeat [anim val duration easing number-of-repetitions reverse?]
(set-shared-value anim (with-repeat (with-timing val (js-obj "duration" duration (set-shared-value anim (with-repeat (with-timing val (js-obj "duration" duration
"easing" (get easings easing))) number-of-repetitions reverse?))) "easing" (get easings easing))) number-of-repetitions reverse?)))
(defn animate-shared-value-with-delay-repeat
([anim val duration easing delay number-of-repetitions]
(animate-shared-value-with-delay-repeat anim val duration easing delay number-of-repetitions false))
([anim val duration easing delay number-of-repetitions reverse?]
(set-shared-value anim
(with-delay delay
(with-repeat
(with-timing val
#js {:duration duration
:easing (get easings easing)})
number-of-repetitions reverse?)))))

View File

@ -35,6 +35,7 @@
[status-im2.contexts.quo-preview.notifications.activity-logs :as activity-logs] [status-im2.contexts.quo-preview.notifications.activity-logs :as activity-logs]
[status-im2.contexts.quo-preview.posts-and-attachments.messages-skeleton :as messages-skeleton] [status-im2.contexts.quo-preview.posts-and-attachments.messages-skeleton :as messages-skeleton]
[status-im2.contexts.quo-preview.reactions.react :as react] [status-im2.contexts.quo-preview.reactions.react :as react]
[status-im2.contexts.quo-preview.record-audio.record-audio :as record-audio]
[status-im2.contexts.quo-preview.selectors.disclaimer :as disclaimer] [status-im2.contexts.quo-preview.selectors.disclaimer :as disclaimer]
[status-im2.contexts.quo-preview.selectors.selectors :as selectors] [status-im2.contexts.quo-preview.selectors.selectors :as selectors]
[status-im2.contexts.quo-preview.settings.privacy-option :as privacy-option] [status-im2.contexts.quo-preview.settings.privacy-option :as privacy-option]
@ -164,6 +165,9 @@
:reactions [{:name :react :reactions [{:name :react
:insets {:top false} :insets {:top false}
:component react/preview-react}] :component react/preview-react}]
:record-audio [{:name :record-audio
:insets {:top false}
:component record-audio/preview-record-audio}]
:switcher [{:name :switcher-cards :switcher [{:name :switcher-cards
:insets {:top false} :insets {:top false}
:component switcher-cards/preview-switcher-cards}] :component switcher-cards/preview-switcher-cards}]

View File

@ -0,0 +1,19 @@
(ns status-im2.contexts.quo-preview.record-audio.record-audio
(:require [react-native.core :as rn]
[quo2.components.record-audio.record-audio.view :as record-audio]))
(defn cool-preview []
[rn/touchable-without-feedback {:on-press rn/dismiss-keyboard!}
[rn/view
[rn/view {:padding-top 150
:align-items :center
:background-color :transparent}
[record-audio/input-view]]]])
(defn preview-record-audio []
[rn/view {:flex 1}
[rn/flat-list {:flex 1
:keyboard-should-persist-taps :never
:scroll-enabled false
:header [cool-preview]
:key-fn str}]])