feat: record audio button
Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
parent
b074e9c58e
commit
c0c0742687
|
@ -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);
|
||||
}
|
||||
);
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
import { useDerivedValue, interpolate } from 'react-native-reanimated';
|
||||
|
||||
// Generic Worklets
|
||||
|
||||
export function applyAnimationsToStyle(animations, style) {
|
||||
|
@ -27,3 +29,12 @@ export function applyAnimationsToStyle(animations, style) {
|
|||
return Object.assign(animatedStyle, style);
|
||||
};
|
||||
};
|
||||
|
||||
export function interpolateValue(sharedValue, inputRange, outputRange) {
|
||||
return useDerivedValue(
|
||||
function () {
|
||||
'worklet'
|
||||
return interpolate(sharedValue.value, inputRange, outputRange);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -284,6 +284,8 @@
|
|||
|
||||
(def shell-worklets #js {})
|
||||
|
||||
(def record-audio-worklets #js {})
|
||||
|
||||
;; Update i18n_resources.cljs
|
||||
(defn mock [module]
|
||||
(case module
|
||||
|
@ -331,6 +333,7 @@
|
|||
"react-native-svg" react-native-svg
|
||||
"../src/js/worklet_factory.js" worklet-factory
|
||||
"../src/js/shell_worklets.js" shell-worklets
|
||||
"../src/js/record_audio_worklets.js" record-audio-worklets
|
||||
"./fleets.js" default-fleets
|
||||
"./chats.js" default-chats
|
||||
"@walletconnect/client" wallet-connect-client
|
||||
|
|
|
@ -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})
|
|
@ -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?]])))])
|
|
@ -13,6 +13,20 @@
|
|||
(let [rgb (string/split value #",")]
|
||||
(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
|
||||
(memoize
|
||||
(fn
|
||||
|
@ -49,6 +63,7 @@
|
|||
;;80 with transparency
|
||||
(def neutral-80-opa-5 (alpha neutral-80 0.05))
|
||||
(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-30 (alpha neutral-80 0.3))
|
||||
(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-100 (alpha neutral-100 1))
|
||||
|
||||
;;80 with transparency opaque
|
||||
(def neutral-80-opa-5-opaque (alpha-opaque neutral-80 0.05))
|
||||
|
||||
;;;;White
|
||||
|
||||
;;Solid
|
||||
|
@ -93,6 +111,11 @@
|
|||
(def white-opa-90 (alpha white 0.9))
|
||||
(def white-opa-95 (alpha white 0.95))
|
||||
|
||||
;;;;Black
|
||||
|
||||
;;Solid
|
||||
(def black "#000000")
|
||||
|
||||
;;;;Primary
|
||||
|
||||
;;Solid
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
[clojure.string :as string]
|
||||
["react-native-linear-gradient" :default LinearGradient]
|
||||
["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
|
||||
(def create-animated-component (comp reagent/adapt-react-class (.-createAnimatedComponent reanimated)))
|
||||
|
@ -25,6 +25,7 @@
|
|||
(def with-spring withSpring)
|
||||
(def key-frame Keyframe)
|
||||
(def with-repeat withRepeat)
|
||||
(def cancel-animation cancelAnimation)
|
||||
|
||||
;; Easings
|
||||
(def bezier (.-bezier ^js Easing))
|
||||
|
@ -54,6 +55,12 @@
|
|||
;; Worklets
|
||||
(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
|
||||
|
||||
;; 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?]
|
||||
(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?)))))
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
[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.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.selectors :as selectors]
|
||||
[status-im2.contexts.quo-preview.settings.privacy-option :as privacy-option]
|
||||
|
@ -164,6 +165,9 @@
|
|||
:reactions [{:name :react
|
||||
:insets {:top false}
|
||||
:component react/preview-react}]
|
||||
:record-audio [{:name :record-audio
|
||||
:insets {:top false}
|
||||
:component record-audio/preview-record-audio}]
|
||||
:switcher [{:name :switcher-cards
|
||||
:insets {:top false}
|
||||
:component switcher-cards/preview-switcher-cards}]
|
||||
|
|
|
@ -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}]])
|
Loading…
Reference in New Issue