migrate reagent record audio component part5 (#19212)

This commit is contained in:
flexsurfer 2024-03-13 14:27:22 +01:00 committed by GitHub
parent 296d868797
commit 1a8bca56e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1064 additions and 927 deletions

View File

@ -4,10 +4,10 @@
[quo.components.record-audio.record-audio.helpers :as helpers]
[quo.components.record-audio.record-audio.style :as style]
[quo.foundations.colors :as colors]
[react-native.core :refer [use-effect]]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]))
(defn f-delete-button
(defn delete-button
[recording? ready-to-delete? reviewing-audio? force-show-controls?]
(let [opacity (reanimated/use-shared-value (if force-show-controls? 1 0))
translate-x (reanimated/use-shared-value (if force-show-controls? 35 20))
@ -17,62 +17,68 @@
connector-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 []
(helpers/animate-linear-with-delay translate-x 12 50 133.33)
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(helpers/animate-easing-with-delay connector-width 56 83.33 80)
(helpers/animate-easing-with-delay connector-height 56 83.33 80)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80))
reset-x-animation (fn []
(helpers/animate-linear translate-x 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-in-animation (fn []
(helpers/animate-linear translate-x 0 200)
(helpers/animate-linear opacity 1 200))
fade-out-animation (fn []
(helpers/animate-linear
translate-x
(if @reviewing-audio? 35 20)
200)
(if @reviewing-audio?
(helpers/animate-linear scale 0.75 200)
(helpers/animate-linear opacity 0 200))
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-out-reset-animation (fn []
(helpers/animate-linear opacity 0 200)
(helpers/animate-linear-with-delay translate-x 20 0 200)
(helpers/animate-linear-with-delay scale 1 0 200))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(when-not @reviewing-audio?
(fade-out-reset-animation)))
[@reviewing-audio?])
(use-effect (fn []
(cond
@ready-to-delete?
(start-x-animation)
@recording?
(reset-x-animation)))
[@ready-to-delete?])
start-x-animation (rn/use-callback
(fn []
(helpers/animate-linear-with-delay translate-x 12 50 133.33)
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(helpers/animate-easing-with-delay connector-width 56 83.33 80)
(helpers/animate-easing-with-delay connector-height 56 83.33 80)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80)))
reset-x-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-x 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16)))
fade-in-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-x 0 200)
(helpers/animate-linear opacity 1 200)))
fade-out-animation (rn/use-callback
(fn []
(helpers/animate-linear
translate-x
(if reviewing-audio? 35 20)
200)
(if reviewing-audio?
(helpers/animate-linear scale 0.75 200)
(helpers/animate-linear opacity 0 200))
(helpers/set-value connector-opacity 0)
(helpers/set-value connector-width 24)
(helpers/set-value connector-height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
[reviewing-audio?])
fade-out-reset-animation (rn/use-callback
(fn []
(helpers/animate-linear opacity 0 200)
(helpers/animate-linear-with-delay translate-x 20 0 200)
(helpers/animate-linear-with-delay scale 1 0 200)))]
(rn/use-effect (fn []
(if recording?
(fade-in-animation)
(fade-out-animation)))
[recording?])
(rn/use-effect (fn []
(when-not reviewing-audio?
(fade-out-reset-animation)))
[reviewing-audio?])
(rn/use-effect (fn []
(cond
ready-to-delete?
(start-x-animation)
recording?
(reset-x-animation)))
[ready-to-delete?])
[:<>
[reanimated/view {:style (style/delete-button-container opacity)}
[reanimated/view

View File

@ -5,10 +5,10 @@
[quo.components.record-audio.record-audio.style :as style]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :refer [use-effect]]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]))
(defn f-lock-button
(defn lock-button
[recording? ready-to-lock? locked?]
(let [theme (quo.theme/use-theme-value)
translate-x-y (reanimated/use-shared-value 20)
@ -18,54 +18,58 @@
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 []
(helpers/animate-linear-with-delay translate-x-y 8 50 116.66)
(helpers/animate-easing-with-delay connector-opacity 1 0 80)
(helpers/animate-easing-with-delay width 56 83.33 63.33)
(helpers/animate-easing-with-delay height 56 83.33 63.33)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
63.33)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
63.33))
reset-x-y-animation (fn []
(helpers/animate-linear translate-x-y 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-in-animation (fn []
(helpers/animate-linear translate-x-y 0 220)
(helpers/animate-linear opacity 1 220))
fade-out-animation (fn []
(helpers/animate-linear translate-x-y 20 200)
(helpers/animate-linear opacity 0 200)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(cond
@ready-to-lock?
(start-x-y-animation)
(and @recording? (not @locked?))
(reset-x-y-animation)))
[@ready-to-lock?])
(use-effect (fn []
(if @locked?
(fade-out-animation)
(reset-x-y-animation)))
[@locked?])
start-x-y-animation (rn/use-callback
(fn []
(helpers/animate-linear-with-delay translate-x-y 8 50 116.66)
(helpers/animate-easing-with-delay connector-opacity 1 0 80)
(helpers/animate-easing-with-delay width 56 83.33 63.33)
(helpers/animate-easing-with-delay height 56 83.33 63.33)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
63.33)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
63.33)))
reset-x-y-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-x-y 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16)))
fade-in-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-x-y 0 220)
(helpers/animate-linear opacity 1 220)))
fade-out-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-x-y 20 200)
(helpers/animate-linear opacity 0 200)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16)))]
(rn/use-effect (fn []
(if recording?
(fade-in-animation)
(fade-out-animation)))
[recording?])
(rn/use-effect (fn []
(cond
ready-to-lock?
(start-x-y-animation)
(and recording? (not locked?))
(reset-x-y-animation)))
[ready-to-lock?])
(rn/use-effect (fn []
(if locked?
(fade-out-animation)
(reset-x-y-animation)))
[locked?])
[:<>
[reanimated/view {:style (style/lock-button-container opacity)}
[reanimated/view
@ -77,6 +81,6 @@
[reanimated/view
{:style (style/lock-button translate-x-y opacity)
:pointer-events :none}
[icons/icon (if @ready-to-lock? :i/locked :i/unlocked)
[icons/icon (if ready-to-lock? :i/locked :i/unlocked)
{:color (colors/theme-colors colors/black colors/white theme)
:size 20}]]]))

View File

@ -3,19 +3,17 @@
[quo.components.buttons.button.view :as button]
[quo.components.record-audio.record-audio.helpers :as helpers]
[quo.components.record-audio.record-audio.style :as style]
[react-native.core :as rn :refer [use-effect]]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]))
(defn f-record-button
(defn record-button
[recording? reviewing-audio?]
(let [opacity (reanimated/use-shared-value 1)
show-animation #(helpers/set-value opacity 1)
hide-animation #(helpers/set-value opacity 0)]
(use-effect (fn []
(if (or @recording? @reviewing-audio?)
(hide-animation)
(show-animation)))
[@recording? @reviewing-audio?])
(let [opacity (reanimated/use-shared-value 1)]
(rn/use-effect (fn []
(if (or recording? reviewing-audio?)
(helpers/set-value opacity 0)
(helpers/set-value opacity 1)))
[recording? reviewing-audio?])
[reanimated/view {:style (style/record-button-container opacity)}
[button/button
{:type :outline

View File

@ -5,8 +5,9 @@
[quo.components.record-audio.record-audio.helpers :as helpers]
[quo.components.record-audio.record-audio.style :as style]
[quo.foundations.colors :as colors]
[quo.theme]
[react-native.audio-toolkit :as audio]
[react-native.core :as rn :refer [use-effect]]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[reagent.core :as reagent]
[taoensso.timbre :as log]
@ -28,13 +29,16 @@
(reagent/as-element
[reanimated/view {:style (style/animated-circle scale opacity color)}]))))))
(defn f-record-button-big
[{:keys [recording? ready-to-send? ready-to-lock? ready-to-delete? record-button-is-animating?
record-button-at-initial-position? locked? reviewing-audio? recording-length-ms
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel theme]}]
(let [scale (reanimated/use-shared-value 1)
(defn record-button-big
[{:keys [recording? set-recording ready-to-send? set-ready-to-send ready-to-lock? set-ready-to-lock
ready-to-delete? set-ready-to-delete record-button-is-animating?
record-button-at-initial-position? locked? set-locked reviewing-audio? recording-length-ms
set-recording-length-ms
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel]}]
(let [theme (quo.theme/use-theme-value)
scale (reanimated/use-shared-value 1)
opacity (reanimated/use-shared-value 0)
opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default)
opacity-from (if ready-to-lock? opacity-from-lock opacity-from-default)
animations (map
(fn [index]
(let [ring-scale (worklets.record-audio/ring-scale scale
@ -46,141 +50,155 @@
[opacity-from 0])}))
(range 0 5))
rings-color (cond
@ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
colors/neutral-80
theme)
@ready-to-delete? colors/danger-50
:else colors/primary-50)
ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
colors/neutral-80
theme)
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 (= :dark theme)) @ready-to-lock?) colors/black colors/white)
icon-color (if (and (not (= :dark theme)) 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?))
(audio/stop-recording
@recorder-ref
(fn []
(cond
@ready-to-send?
(when on-send
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
:duration @recording-length-ms}))
@ready-to-delete?
(when on-cancel
(on-cancel)))
(reload-recorder-fn)
(reset! recording? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(reset! ready-to-lock? false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(reset! recording-length-ms 0)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))))
start-animation (fn []
(helpers/set-value opacity 1)
(helpers/animate-linear scale 2.6 signal-anim-duration)
;; 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
(js/setTimeout
(rn/use-callback
(fn []
(cond
(and ready-to-lock? (not @record-button-is-animating?))
(do
(set-locked true)
(set-ready-to-lock false))
(and (not locked?) (not reviewing-audio?))
(audio/stop-recording
@recorder-ref
(fn []
(cond
ready-to-send?
(when on-send
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
:duration recording-length-ms}))
ready-to-delete?
(when on-cancel
(on-cancel)))
(reload-recorder-fn)
(set-recording false)
(set-ready-to-send false)
(set-ready-to-delete false)
(set-ready-to-lock false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(set-recording-length-ms 0)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))))
[ready-to-lock? locked? reviewing-audio? ready-to-send? recording-length-ms ready-to-delete?
idle?])
start-animation (rn/use-callback
(fn []
(helpers/set-value opacity 1)
(helpers/animate-linear scale 2.6 signal-anim-duration)
;; 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
(js/setTimeout
(fn []
(helpers/set-value scale scale-to-each)
(helpers/animate-linear-with-delay-loop scale
scale-to-total
signal-anim-duration-2
0))
signal-anim-duration))))
stop-animation (rn/use-callback
(fn []
(helpers/set-value opacity 0)
(reanimated/cancel-animation scale)
(helpers/set-value scale 1)
(when @clear-timeout (js/clearTimeout @clear-timeout))))
start-y-animation (rn/use-callback
(fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-y -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
[complete-animation])
reset-y-animation (rn/use-callback
(fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-linear icon-opacity 1 500)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500)))
start-x-animation (rn/use-callback
(fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-x -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(helpers/animate-linear red-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
[complete-animation])
reset-x-animation (rn/use-callback
(fn []
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear red-overlay-opacity 0 100)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
500)))
start-x-y-animation (rn/use-callback
(fn []
(helpers/set-value scale scale-to-each)
(helpers/animate-linear-with-delay-loop scale
scale-to-total
signal-anim-duration-2
0))
signal-anim-duration)))
stop-animation (fn []
(helpers/set-value opacity 0)
(reanimated/cancel-animation scale)
(helpers/set-value scale 1)
(when @clear-timeout (js/clearTimeout @clear-timeout)))
start-y-animation (fn []
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-y -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-y-animation (fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-linear icon-opacity 1 500)
(js/setTimeout (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)
(helpers/animate-easing translate-x -64 250)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 76.66)
(helpers/animate-linear red-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
250))
reset-x-animation (fn []
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear red-overlay-opacity 0 100)
(js/setTimeout (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)
(helpers/animate-easing translate-y -44 200)
(helpers/animate-easing translate-x -44 200)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 33.33)
(helpers/animate-linear gray-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
200))
reset-x-y-animation (fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear gray-overlay-opacity 0 800)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
800))]
(use-effect (fn []
(cond
@recording?
(start-animation)
(not @ready-to-lock?)
(stop-animation)))
[@recording?])
(use-effect (fn []
(if @ready-to-lock?
(start-x-y-animation)
(reset-x-y-animation)))
[@ready-to-lock?])
(use-effect (fn []
(if @ready-to-send?
(start-y-animation)
(reset-y-animation)))
[@ready-to-send?])
(use-effect (fn []
(if @ready-to-delete?
(start-x-animation)
(reset-x-animation)))
[@ready-to-delete?])
(reset! record-button-at-initial-position? false)
(reset! record-button-is-animating? true)
(helpers/animate-easing translate-y -44 200)
(helpers/animate-easing translate-x -44 200)
(helpers/animate-linear-with-delay icon-opacity 0 33.33 33.33)
(helpers/animate-linear gray-overlay-opacity 1 33.33)
(js/setTimeout (fn []
(reset! record-button-is-animating? false)
(when-not @touch-active? (complete-animation)))
200))
[complete-animation])
reset-x-y-animation (rn/use-callback
(fn []
(helpers/animate-easing translate-y 0 300)
(helpers/animate-easing translate-x 0 300)
(helpers/animate-linear icon-opacity 1 500)
(helpers/animate-linear gray-overlay-opacity 0 800)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
800)))]
(rn/use-effect (fn []
(cond
recording?
(start-animation)
(not ready-to-lock?)
(stop-animation)))
[recording?])
(rn/use-effect (fn []
(if ready-to-lock?
(start-x-y-animation)
(reset-x-y-animation)))
[ready-to-lock?])
(rn/use-effect (fn []
(if ready-to-send?
(start-y-animation)
(reset-y-animation)))
[ready-to-send?])
(rn/use-effect (fn []
(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}
@ -197,6 +215,6 @@
[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?
(if locked?
[rn/view {:style style/stop-icon}]
[icons/icon :i/audio {:color icon-color}])]]]))

View File

@ -4,10 +4,10 @@
[quo.components.record-audio.record-audio.helpers :as helpers]
[quo.components.record-audio.record-audio.style :as style]
[quo.foundations.colors :as colors]
[react-native.core :refer [use-effect]]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]))
(defn f-send-button
(defn send-button
[recording? ready-to-send? reviewing-audio? force-show-controls?]
(let [opacity (reanimated/use-shared-value (if force-show-controls? 1 0))
translate-y (reanimated/use-shared-value (if force-show-controls? 76 20))
@ -16,65 +16,71 @@
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 []
(helpers/animate-linear-with-delay translate-y 12 50 133.33)
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(helpers/animate-easing-with-delay width 56 83.33 80)
(helpers/animate-easing-with-delay height 56 83.33 80)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80))
reset-y-animation (fn []
(helpers/animate-linear translate-y 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 12)
(helpers/set-value height 24)
(helpers/set-value border-radius-first-half 16)
(helpers/set-value border-radius-second-half 8))
fade-in-animation (fn []
(helpers/animate-linear translate-y 0 200)
(helpers/animate-linear opacity 1 200))
fade-out-animation (fn []
(when-not force-show-controls?
(helpers/animate-linear
translate-y
(if @reviewing-audio? 76 20)
200))
(when-not @reviewing-audio?
(helpers/animate-linear opacity 0 200))
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
fade-out-reset-animation (fn []
(helpers/animate-linear opacity 0 200)
(helpers/animate-linear-with-delay translate-y 20 0 200)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))]
(use-effect (fn []
(if @recording?
(fade-in-animation)
(fade-out-animation)))
[@recording?])
(use-effect (fn []
(when-not @reviewing-audio?
(fade-out-reset-animation)))
[@reviewing-audio?])
(use-effect (fn []
(cond
@ready-to-send?
(start-y-animation)
@recording? (reset-y-animation)))
[@ready-to-send?])
start-y-animation (rn/use-callback
(fn []
(helpers/animate-linear-with-delay translate-y 12 50 133.33)
(helpers/animate-easing-with-delay connector-opacity 1 0 93.33)
(helpers/animate-easing-with-delay width 56 83.33 80)
(helpers/animate-easing-with-delay height 56 83.33 80)
(helpers/animate-easing-with-delay border-radius-first-half
28
83.33
80)
(helpers/animate-easing-with-delay border-radius-second-half
28
83.33
80)))
reset-y-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-y 0 100)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 12)
(helpers/set-value height 24)
(helpers/set-value border-radius-first-half 16)
(helpers/set-value border-radius-second-half 8)))
fade-in-animation (rn/use-callback
(fn []
(helpers/animate-linear translate-y 0 200)
(helpers/animate-linear opacity 1 200)))
fade-out-animation (rn/use-callback
(fn []
(when-not force-show-controls?
(helpers/animate-linear
translate-y
(if reviewing-audio? 76 20)
200))
(when-not reviewing-audio?
(helpers/animate-linear opacity 0 200))
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16))
[reviewing-audio? force-show-controls?])
fade-out-reset-animation (rn/use-callback
(fn []
(helpers/animate-linear opacity 0 200)
(helpers/animate-linear-with-delay translate-y 20 0 200)
(helpers/set-value connector-opacity 0)
(helpers/set-value width 24)
(helpers/set-value height 12)
(helpers/set-value border-radius-first-half 8)
(helpers/set-value border-radius-second-half 16)))]
(rn/use-effect (fn []
(if recording?
(fade-in-animation)
(fade-out-animation)))
[recording?])
(rn/use-effect (fn []
(when-not reviewing-audio?
(fade-out-reset-animation)))
[reviewing-audio?])
(rn/use-effect (fn []
(cond
ready-to-send?
(start-y-animation)
recording? (reset-y-animation)))
[ready-to-send?])
[:<>
[reanimated/view {:style (style/send-button-container opacity)}
[reanimated/view

View File

@ -2,7 +2,6 @@
(:require
[quo.components.record-audio.record-audio.view :as record-audio]
[react-native.audio-toolkit :as audio]
[reagent.core :as reagent]
[test-helpers.component :as h]
[utils.datetime :as datetime]))
@ -11,8 +10,10 @@
(h/test "renders record-audio"
(h/render [record-audio/record-audio])
(-> (h/expect (h/get-by-test-id "record-audio"))
(.toBeTruthy)))
(-> (h/wait-for #(h/get-by-test-id "record-audio"))
(.then (fn []
(-> (h/expect (h/get-by-test-id "record-audio"))
(.toBeTruthy))))))
(h/test "record-audio on-start-recording works"
(let [event (js/jest.fn)]
@ -31,7 +32,7 @@
(h/test "record-audio on-reviewing-audio works"
(let [event (js/jest.fn)
on-meter (reagent/atom nil)]
on-meter (atom nil)]
(h/render [record-audio/record-audio
{:on-reviewing-audio event
:record-audio-permission-granted true}])
@ -39,7 +40,7 @@
(reset! on-meter on-meter-fn))
audio/start-recording (fn [_ on-start _]
(on-start)
(js/setInterval #(@on-meter) 100))
(js/setInterval @on-meter 100))
audio/get-recorder-file-path (fn [] "file-path")]
(h/fire-event
:on-start-should-set-responder
@ -48,6 +49,7 @@
:locationY 70
:timestamp 0
:identifier 0}})
(with-redefs [datetime/timestamp (fn [] (+ (.now js/Date) 1000))]
(h/advance-timers-by-time 100)
(h/fire-event
@ -63,7 +65,7 @@
(h/test "record-audio on-send works after reviewing audio"
(let [event (js/jest.fn)
on-meter (reagent/atom nil)]
on-meter (atom nil)]
(h/render [record-audio/record-audio
{:on-send event
:record-audio-permission-granted true}])
@ -114,14 +116,15 @@
(h/test "record-audio on-send works after sliding to the send button"
(let [event (js/jest.fn)
on-meter (reagent/atom nil)
on-meter (atom nil)
last-now-ms (atom nil)
duration-ms (atom nil)]
(h/render [record-audio/record-audio
{:on-send event
:record-audio-permission-granted true}])
(with-redefs [audio/new-recorder (fn [_ on-meter-fn _]
(reset! on-meter on-meter-fn))
(reset! on-meter on-meter-fn)
#js {:destroy #()})
audio/start-recording (fn [_ on-start _]
(on-start)
(js/setInterval #(@on-meter) 100))
@ -170,15 +173,16 @@
(h/test "record-audio on-cancel works after reviewing audio"
(let [event (js/jest.fn)
on-meter (reagent/atom nil)]
on-meter (atom nil)]
(h/render [record-audio/record-audio
{:on-cancel event
:record-audio-permission-granted true}])
(with-redefs [audio/new-recorder (fn [_ on-meter-fn _]
(reset! on-meter on-meter-fn))
(reset! on-meter on-meter-fn)
#js {:destroy #()})
audio/start-recording (fn [_ on-start _]
(on-start)
(js/setInterval #(@on-meter) 100))]
(js/setInterval @on-meter 100))]
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
@ -195,6 +199,7 @@
:locationY 70
:timestamp 200
:identifier 0}})
(h/advance-timers-by-time 250)
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
@ -202,13 +207,12 @@
:locationY 80
:timestamp 200
:identifier 0}})
(h/advance-timers-by-time 250)
(-> (js/expect event)
(.toHaveBeenCalledTimes 1))))))
(h/test "record-audio on-cancel works after sliding to the cancel button"
(let [event (js/jest.fn)
on-meter (reagent/atom nil)]
on-meter (atom nil)]
(h/render [record-audio/record-audio
{:on-cancel event
:record-audio-permission-granted true}])

View File

@ -0,0 +1,81 @@
(ns quo.components.record-audio.record-audio.constants
(:require [react-native.audio-toolkit :as audio]
[react-native.platform :as platform]
[utils.datetime :as datetime]))
(def min-audio-duration-ms 1000)
(def max-audio-duration-ms (if platform/ios? 120800 120500))
(def metering-interval 25)
(def base-filename "am")
(def default-format ".aac")
(def min-touch-duration 150)
(def record-button-area-big
{:width 56
:height 56
:x 64
:y 64})
(def record-button-area
{:width 48
:height 48
:x 68
:y 68})
(defn delete-button-area
[{:keys [active? reviewing-audio?]}]
{:width (cond
active? 72
reviewing-audio? 32
:else 82)
:height (if reviewing-audio? 32 56)
:x (cond
active? -16
reviewing-audio? 36
:else -32)
:y (cond
active? 64
reviewing-audio? 76
:else 70)})
(defn lock-button-area
[{:keys [active?]}]
{:width (if active? 72 100)
:height (if active? 72 102)
:x -32
:y -32})
(defn send-button-area
[{:keys [active? reviewing-audio?]}]
{:width (if reviewing-audio? 32 56)
:height (cond
active? 72
reviewing-audio? 47
:else 92)
:x (if reviewing-audio? 76 32)
:y (cond
active? -16
reviewing-audio? 76
:else -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 rec-options
(merge
audio/default-recorder-options
{:filename (str base-filename
(datetime/timestamp)
default-format)
:meteringInterval metering-interval}))

View File

@ -0,0 +1,345 @@
(ns quo.components.record-audio.record-audio.handlers
(:require [oops.core :as oops]
[quo.components.record-audio.record-audio.constants :as record-audio.constants]
[react-native.audio-toolkit :as audio]
[taoensso.timbre :as log]
[utils.datetime :as datetime]))
(def get-reload-player
(memoize
(fn
[[player-ref destroy-player set-playing-audio playing-timer set-audio-current-time-ms
set-seeking-audio]]
(fn [audio-file]
(when @player-ref
(destroy-player))
(reset! player-ref
(audio/new-player
(or audio-file (:filename record-audio.constants/rec-options))
{:autoDestroy false
:continuesToPlayInBackground false
:category audio/PLAYBACK}
(fn []
(set-playing-audio false)
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil)
(set-audio-current-time-ms 0)
(set-seeking-audio false)))))
(audio/prepare-player
@player-ref
#(log/debug "[record-audio] prepare player - success")
#(log/error "[record-audio] prepare player - error: " %))))))
(def get-recorder-on-meter
(memoize
(fn
[[recording-start-ms set-recording-length-ms reached-max-duration? locked? set-locked
set-reviewing-audio
idle? set-recording
set-ready-to-lock set-ready-to-send set-ready-to-delete recorder-ref output-file reload-player
on-reviewing-audio]]
(fn []
(when @recording-start-ms
(let [now-ms (datetime/timestamp)
recording-duration (- now-ms @recording-start-ms)]
(set-recording-length-ms recording-duration)
(when (>= recording-duration record-audio.constants/max-audio-duration-ms)
(reset! reached-max-duration? (not locked?))
(set-reviewing-audio true)
(reset! idle? false)
(set-locked false)
(set-recording false)
(set-ready-to-lock false)
(set-ready-to-send false)
(set-ready-to-delete false)
(audio/stop-recording
@recorder-ref
(fn []
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
(reload-player nil)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(set-recording-length-ms 0)
(reset! recording-start-ms nil)
(when on-reviewing-audio
(on-reviewing-audio (audio/get-recorder-file-path @recorder-ref))))
(log/debug "[record-audio] new recorder - on meter")))))))
(def get-reload-recorder
(memoize
(fn
[[recorder-ref recorder-on-meter]]
(fn []
(when @recorder-ref
(audio/destroy-recorder @recorder-ref)
(reset! recorder-ref nil))
(reset! recorder-ref (audio/new-recorder
record-audio.constants/rec-options
recorder-on-meter
#(log/debug "[record-audio] new recorder - on ended")))))))
(def get-on-start-should-set-responder
(memoize
(fn
[[locked? idle? disabled? recorder-on-meter touch-timestamp touch-identifier
reviewing-audio? record-audio-permission-granted set-recording set-playing-audio output-file
recorder-ref recording-start-ms set-audio-current-time-ms on-start-recording
on-request-record-audio-permission touch-active?]]
(fn [e]
(when-not (or locked? @idle? (nil? e) @disabled?)
(let [pressed-record-button? (record-audio.constants/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-audio.constants/record-button-area)
new-recorder (audio/new-recorder
record-audio.constants/rec-options
recorder-on-meter
#(log/debug "[record-audio] new recorder - on ended"))]
(reset! touch-timestamp (oops/oget e "nativeEvent.timestamp"))
(reset! touch-identifier (oops/oget e "nativeEvent.identifier"))
(when-not reviewing-audio?
(if record-audio-permission-granted
(do
(when (not @idle?)
(set-recording pressed-record-button?))
(when pressed-record-button?
(set-playing-audio false)
(reset! output-file nil)
(reset! recorder-ref new-recorder)
(audio/start-recording
new-recorder
(fn []
(reset! recording-start-ms (datetime/timestamp))
(set-audio-current-time-ms 0)
(log/debug "[record-audio] start recording - success"))
#(log/error "[record-audio] start recording - error: " %))
(when on-start-recording
(on-start-recording))))
(when on-request-record-audio-permission
(on-request-record-audio-permission))))
(when record-audio-permission-granted
(reset! touch-active? true))))
(and (not @idle?) (not @disabled?))))))
(def get-on-responder-move
(memoize
(fn
[[locked? ready-to-send? set-ready-to-send ready-to-delete? set-ready-to-delete ready-to-lock?
set-ready-to-lock touch-identifier
record-button-at-initial-position? recording?]]
(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")
identifier (oops/oget e "nativeEvent.identifier")
moved-to-send-button? (record-audio.constants/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}
(record-audio.constants/send-button-area
{:active? ready-to-send?
:reviewing-audio? false}))
moved-to-delete-button? (record-audio.constants/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}
(record-audio.constants/delete-button-area
{:active? ready-to-delete?
:reviewing-audio? false}))
moved-to-lock-button? (record-audio.constants/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-audio.constants/lock-button-area {:active?
ready-to-lock?}))
moved-to-record-button? (and
(record-audio.constants/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-audio.constants/record-button-area-big)
(not= location-x page-x)
(not= location-y page-y))]
(when (= identifier @touch-identifier)
(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?)
(set-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?)
(set-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?)
(set-ready-to-send moved-to-send-button?)))))))))
(def get-on-responder-release
(memoize
(fn
[[idle? reached-max-duration? touch-timestamp recording-length-ms set-recording-length-ms
reviewing-audio? set-reviewing-audio
set-audio-current-time-ms set-force-show-controls on-send output-file player-ref
destroy-player on-cancel record-button-is-animating? ready-to-lock? set-ready-to-lock
locked? set-locked on-lock ready-to-delete? set-ready-to-delete ready-to-send? set-ready-to-send
disabled? on-reviewing-audio recorder-ref set-recording reload-player recording-start-ms
touch-active?]]
(fn [^js e]
(when (and
(not @idle?)
(not @reached-max-duration?))
(let [touch-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}
on-record-button? (record-audio.constants/touch-inside-area?
touch-area
record-audio.constants/record-button-area-big)
on-send-button? (record-audio.constants/touch-inside-area?
touch-area
(record-audio.constants/send-button-area
{:active? false
:reviewing-audio? true}))
on-delete-button? (record-audio.constants/touch-inside-area?
touch-area
(record-audio.constants/delete-button-area
{:active? false
:reviewing-audio? true}))
release-touch-timestamp (oops/oget e "nativeEvent.timestamp")
touch-timestamp-diff (- release-touch-timestamp @touch-timestamp)
audio-length recording-length-ms]
(cond
(and reviewing-audio? on-send-button?)
(do
(set-reviewing-audio false)
(set-audio-current-time-ms 0)
(set-force-show-controls false)
(when on-send
(on-send {:file-path @output-file
:duration (min record-audio.constants/max-audio-duration-ms
(int (audio/get-player-duration @player-ref)))}))
(when @player-ref
(audio/stop-playing
@player-ref
(fn []
(destroy-player)
(log/debug "[record-audio] stop playing - success"))
#(log/error "[record-audio] stop playing - error: " %))))
(and reviewing-audio? on-delete-button?)
(do
(set-reviewing-audio false)
(set-audio-current-time-ms 0)
(set-force-show-controls false)
(destroy-player)
(when on-cancel
(on-cancel)))
(and ready-to-lock? (not @record-button-is-animating?))
(do
(set-locked true)
(set-ready-to-lock false)
(when on-lock
(on-lock)))
(and (not reviewing-audio?)
(or on-record-button?
(and (not ready-to-delete?)
(not ready-to-lock?)
(not ready-to-send?))))
(do
(reset! disabled? (<= touch-timestamp-diff record-audio.constants/min-touch-duration))
(js/setTimeout
(fn []
(if (>= recording-length-ms record-audio.constants/min-audio-duration-ms)
(do (set-reviewing-audio true)
(reset! idle? false)
(when on-reviewing-audio
(on-reviewing-audio (audio/get-recorder-file-path @recorder-ref))))
(do (when on-cancel
(on-cancel))
(reset! idle? true)))
(set-locked false)
(set-recording false)
(set-ready-to-lock false)
(audio/stop-recording
@recorder-ref
(fn []
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
(when (>= audio-length record-audio.constants/min-audio-duration-ms)
(reload-player nil))
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(set-recording-length-ms 0)
(reset! recording-start-ms nil)
(reset! disabled? false))
(if (> touch-timestamp-diff record-audio.constants/min-touch-duration) 0 250)))
(and (not locked?) (not reviewing-audio?) (not @record-button-is-animating?))
(do
(reset! disabled? (<= touch-timestamp-diff record-audio.constants/min-touch-duration))
(js/setTimeout
(fn []
(audio/stop-recording
@recorder-ref
(fn []
(cond
ready-to-send?
(when on-send
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
:duration recording-length-ms}))
ready-to-delete?
(when on-cancel
(on-cancel)))
(set-recording false)
(set-ready-to-send false)
(set-ready-to-delete false)
(set-ready-to-lock false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(set-recording-length-ms 0)
(reset! recording-start-ms nil)
(reset! disabled? false)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %)))
(if (> touch-timestamp-diff record-audio.constants/min-touch-duration) 0 250)))))
(reset! touch-active? false))
(when @reached-max-duration?
(reset! reached-max-duration? false))
(reset! touch-timestamp nil)))))

View File

@ -2,7 +2,6 @@
(:require
[clojure.string :as string]
[goog.string :as gstring]
[oops.core :as oops]
[quo.components.icon :as icons]
[quo.components.markdown.text :as text]
[quo.components.record-audio.record-audio.buttons.delete-button :as delete-button]
@ -10,93 +9,23 @@
[quo.components.record-audio.record-audio.buttons.record-button :as record-button]
[quo.components.record-audio.record-audio.buttons.record-button-big :as record-button-big]
[quo.components.record-audio.record-audio.buttons.send-button :as send-button]
[quo.components.record-audio.record-audio.constants :as record-audio.constants]
[quo.components.record-audio.record-audio.handlers :as handlers]
[quo.components.record-audio.record-audio.style :as style]
[quo.components.record-audio.soundtrack.view :as soundtrack]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.audio-toolkit :as audio]
[react-native.core :as rn]
[react-native.platform :as platform]
[reagent.core :as reagent]
[taoensso.timbre :as log]
[utils.datetime :as datetime]))
(def ^:private min-audio-duration-ms 1000)
(def ^:private max-audio-duration-ms (if platform/ios? 120800 120500))
(def ^:private metering-interval 25)
(def ^:private base-filename "am")
(def ^:private default-format ".aac")
(def min-touch-duration 150)
(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
[{:keys [active? reviewing-audio?]}]
{:width (cond
active? 72
reviewing-audio? 32
:else 82)
:height (if reviewing-audio? 32 56)
:x (cond
active? -16
reviewing-audio? 36
:else -32)
:y (cond
active? 64
reviewing-audio? 76
:else 70)})
(defn- lock-button-area
[{:keys [active?]}]
{:width (if active? 72 100)
:height (if active? 72 102)
:x -32
:y -32})
(defn- send-button-area
[{:keys [active? reviewing-audio?]}]
{:width (if reviewing-audio? 32 56)
:height (cond
active? 72
reviewing-audio? 47
:else 92)
:x (if reviewing-audio? 76 32)
:y (cond
active? -16
reviewing-audio? 76
:else -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))))))
(defn- f-recording-bar
(defn- recording-bar
[recording-length-ms ready-to-delete?]
(let [fill-percentage (/ (* recording-length-ms 100) max-audio-duration-ms)]
(let [fill-percentage (/ (* recording-length-ms 100) record-audio.constants/max-audio-duration-ms)]
[rn/view {:style (style/recording-bar-container)}
[rn/view {:style (style/recording-bar fill-percentage ready-to-delete?)}]]))
(defn- f-time-counter
(defn- time-counter
[recording? recording-length-ms ready-to-delete? reviewing-audio? audio-current-time-ms]
(let [s (quot (if recording? recording-length-ms audio-current-time-ms) 1000)
time-str (gstring/format "%02d:%02d" (quot s 60) (mod s 60))]
@ -111,10 +40,12 @@
{:style (style/timer-text)}))
time-str]]))
(defn- f-play-button
[playing-audio? player-ref playing-timer audio-current-time-ms seeking-audio? max-duration-ms]
(defn- play-button
[playing-audio? set-playing-audio player-ref playing-timer set-audio-current-time-ms seeking-audio?
set-seeking-audio
max-duration-ms]
(let [on-play (fn []
(reset! playing-audio? true)
(set-playing-audio true)
(reset! playing-timer
(js/setInterval
(fn []
@ -122,24 +53,24 @@
player-state (audio/get-state @player-ref)
playing? (= player-state audio/PLAYING)]
(when (and playing?
(not @seeking-audio?)
(not seeking-audio?)
(> current-time 0)
(< current-time max-duration-ms))
(reset! audio-current-time-ms current-time))
(set-audio-current-time-ms current-time))
(when (>= current-time max-duration-ms)
(audio/stop-playing
@player-ref
(fn []
(reset! playing-audio? false)
(set-playing-audio false)
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil)
(reset! audio-current-time-ms 0)
(reset! seeking-audio? false)))
(set-audio-current-time-ms 0)
(set-seeking-audio false)))
#(log/error "[record-audio] stop player - error: " %)))))
100)))
on-pause (fn []
(reset! playing-audio? false)
(set-playing-audio false)
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil))
@ -154,445 +85,186 @@
{:style (style/play-button)
:on-press on-press}
[icons/icon
(if @playing-audio? :i/pause :i/play)
(if playing-audio? :i/pause :i/play)
{:color (colors/theme-colors colors/neutral-100 colors/white)}]]))
(defn- record-audio-internal
[{:keys [on-init on-start-recording on-send on-cancel on-reviewing-audio
record-audio-permission-granted
on-request-record-audio-permission on-check-audio-permissions
audio-file on-lock max-duration-ms theme]}]
[:f>
;; TODO we need to refactor this, and use :f> with defined function, currenly state is reseted each
;; time parent component
;; is re-rendered
(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 (some? audio-file))
playing-audio? (reagent/atom false)
recording-length-ms (reagent/atom 0)
audio-current-time-ms (reagent/atom 0)
seeking-audio? (reagent/atom false)
force-show-controls? (reagent/atom (some? audio-file))
recording-start-ms (atom (datetime/timestamp))
clear-timeout (atom nil)
record-button-at-initial-position? (atom true)
record-button-is-animating? (atom false)
idle? (atom false)
recorder-ref (atom nil)
player-ref (atom nil)
touch-active? (atom false)
playing-timer (atom nil)
output-file (atom audio-file)
reached-max-duration? (atom false)
touch-timestamp (atom nil)
touch-identifier (atom nil)
disabled? (atom false)
app-state-listener (atom nil)
rec-options
(merge
audio/default-recorder-options
{:filename (str base-filename (datetime/timestamp) default-format)
:meteringInterval metering-interval})
destroy-player
(fn []
(audio/destroy-player @player-ref)
(reset! player-ref nil))
reload-player
(fn [audio-file]
(when @player-ref
(destroy-player))
(reset! player-ref
(audio/new-player
(or audio-file (:filename rec-options))
{:autoDestroy false
:continuesToPlayInBackground false
:category audio/PLAYBACK}
(fn []
(reset! playing-audio? false)
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil)
(reset! audio-current-time-ms 0)
(reset! seeking-audio? false)))))
(audio/prepare-player
@player-ref
#(log/debug "[record-audio] prepare player - success")
#(log/error "[record-audio] prepare player - error: " %)))
destroy-recorder
(fn []
(audio/destroy-recorder @recorder-ref)
(reset! recorder-ref nil))
recorder-on-meter
(fn []
(when @recording-start-ms
(let [now-ms (datetime/timestamp)
recording-duration (- now-ms
@recording-start-ms)]
(reset! recording-length-ms recording-duration)
(when (>= recording-duration max-audio-duration-ms)
(reset! reached-max-duration? (not @locked?))
(reset! reviewing-audio? true)
(reset! idle? false)
(reset! locked? false)
(reset! recording? false)
(reset! ready-to-lock? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(audio/stop-recording
@recorder-ref
(fn []
(reset! output-file (audio/get-recorder-file-path
@recorder-ref))
(reload-player nil)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(reset! recording-length-ms 0)
(reset! recording-start-ms nil)
(when on-reviewing-audio
(on-reviewing-audio (audio/get-recorder-file-path
@recorder-ref))))
(log/debug "[record-audio] new recorder - on meter"))))
reload-recorder
(fn []
(when @recorder-ref
(destroy-recorder))
(reset! recorder-ref (audio/new-recorder
rec-options
recorder-on-meter
#(log/debug "[record-audio] new recorder - on ended"))))
reset-recorder
(fn []
(reset! recording? false)
(reset! reviewing-audio? false)
(reset! locked? false)
(reset! ready-to-send? false)
(reset! ready-to-lock? false)
(reset! ready-to-delete? false)
(reset! audio-current-time-ms 0)
(reset! recording-length-ms 0)
(reset! seeking-audio? false)
(reset! playing-audio? false)
(reset! touch-active? false)
(reset! reached-max-duration? false)
(reset! output-file nil)
(reset! idle? false)
(reset! record-button-is-animating? false)
(reset! record-button-at-initial-position? true)
(when @clear-timeout
(js/clearTimeout @clear-timeout)
(reset! clear-timeout nil))
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil))
(reload-recorder))
on-start-should-set-responder
(fn [^js e]
(when-not (or @locked? @idle? (nil? e) @disabled?)
(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)
new-recorder (audio/new-recorder
rec-options
recorder-on-meter
#(log/debug "[record-audio] new recorder - on ended"))]
(reset! touch-timestamp (oops/oget e "nativeEvent.timestamp"))
(reset! touch-identifier (oops/oget e "nativeEvent.identifier"))
(when-not @reviewing-audio?
(if record-audio-permission-granted
(do
(when (not @idle?)
(reset! recording? pressed-record-button?))
(when pressed-record-button?
(reset! playing-audio? false)
(reset! output-file nil)
(reset! recorder-ref new-recorder)
(audio/start-recording
new-recorder
(fn []
(reset! recording-start-ms (datetime/timestamp))
(reset! audio-current-time-ms 0)
(log/debug "[record-audio] start recording - success"))
#(log/error "[record-audio] start recording - error: " %))
(when on-start-recording
(on-start-recording))))
(when on-request-record-audio-permission
(on-request-record-audio-permission))))
(when record-audio-permission-granted
(reset! touch-active? true))))
(and (not @idle?) (not @disabled?)))
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")
identifier (oops/oget e "nativeEvent.identifier")
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
{:active? @ready-to-send?
:reviewing-audio? false}))
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
{:active? @ready-to-delete?
:reviewing-audio? false}))
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 {:active? @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))]
(when (= identifier @touch-identifier)
(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]
(when (and
(not @idle?)
(not @reached-max-duration?))
(let [touch-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}
on-record-button? (touch-inside-area?
touch-area
record-button-area-big)
on-send-button? (touch-inside-area?
touch-area
(send-button-area
{:active? false
:reviewing-audio? true}))
on-delete-button? (touch-inside-area?
touch-area
(delete-button-area
{:active? false
:reviewing-audio? true}))
release-touch-timestamp (oops/oget e "nativeEvent.timestamp")
touch-timestamp-diff (- release-touch-timestamp @touch-timestamp)
audio-length @recording-length-ms]
(cond
(and @reviewing-audio? on-send-button?)
(do
(reset! reviewing-audio? false)
(reset! audio-current-time-ms 0)
(reset! force-show-controls? false)
(when on-send
(on-send {:file-path @output-file
:duration (min max-audio-duration-ms
(int (audio/get-player-duration @player-ref)))}))
(when @player-ref
(audio/stop-playing
@player-ref
(fn []
(destroy-player)
(log/debug "[record-audio] stop playing - success"))
#(log/error "[record-audio] stop playing - error: " %))))
(and @reviewing-audio? on-delete-button?)
(do
(reset! reviewing-audio? false)
(reset! audio-current-time-ms 0)
(reset! force-show-controls? false)
(destroy-player)
(when on-cancel
(on-cancel)))
(and @ready-to-lock? (not @record-button-is-animating?))
(do
(reset! locked? true)
(reset! ready-to-lock? false)
(when on-lock
(on-lock)))
(and (not @reviewing-audio?)
(or on-record-button?
(and (not @ready-to-delete?)
(not @ready-to-lock?)
(not @ready-to-send?))))
(do
(reset! disabled? (<= touch-timestamp-diff min-touch-duration))
(js/setTimeout
(fn []
(if (>= @recording-length-ms min-audio-duration-ms)
(do (reset! reviewing-audio? true)
(reset! idle? false)
(when on-reviewing-audio
(on-reviewing-audio (audio/get-recorder-file-path @recorder-ref))))
(do (when on-cancel
(on-cancel))
(reset! idle? true)))
(reset! locked? false)
(reset! recording? false)
(reset! ready-to-lock? false)
(audio/stop-recording
@recorder-ref
(fn []
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
(when (>= audio-length min-audio-duration-ms)
(reload-player nil))
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(reset! recording-length-ms 0)
(reset! recording-start-ms nil)
(reset! disabled? false))
(if (> touch-timestamp-diff min-touch-duration) 0 250)))
(and (not @locked?) (not @reviewing-audio?) (not @record-button-is-animating?))
(do
(reset! disabled? (<= touch-timestamp-diff min-touch-duration))
(js/setTimeout
(fn []
(audio/stop-recording
@recorder-ref
(fn []
(cond
@ready-to-send?
(when on-send
(on-send {:file-path (audio/get-recorder-file-path @recorder-ref)
:duration @recording-length-ms}))
@ready-to-delete?
(when on-cancel
(on-cancel)))
(reset! recording? false)
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(reset! ready-to-lock? false)
(reset! idle? true)
(js/setTimeout #(reset! idle? false) 1000)
(reset! recording-length-ms 0)
(reset! recording-start-ms nil)
(reset! disabled? false)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %)))
(if (> touch-timestamp-diff min-touch-duration) 0 250)))))
(reset! touch-active? false))
(when @reached-max-duration?
(reset! reached-max-duration? false))
(reset! touch-timestamp nil))]
(fn []
(rn/use-mount (fn []
(when on-check-audio-permissions
(on-check-audio-permissions))
(when on-init
(on-init reset-recorder))
(when audio-file
(let [filename (last (string/split audio-file "/"))]
(reload-player filename)))
(reset! app-state-listener
(.addEventListener rn/app-state
"change"
#(when (= % "background")
(reset! playing-audio? false))))
#(.remove @app-state-listener)))
[rn/view
{:style style/bar-container
:pointer-events :box-none}
(when @reviewing-audio?
[:<>
[:f> f-play-button playing-audio? player-ref playing-timer audio-current-time-ms
seeking-audio? max-duration-ms]
[:f> soundtrack/f-soundtrack
{:audio-current-time-ms audio-current-time-ms
:player-ref @player-ref
:seeking-audio? seeking-audio?
:max-audio-duration-ms max-duration-ms}]])
(when (or @recording? @reviewing-audio?)
[:f> f-time-counter @recording? @recording-length-ms @ready-to-delete? @reviewing-audio?
@audio-current-time-ms])
(when @recording?
[:f> f-recording-bar @recording-length-ms @ready-to-delete?])
[rn/view
{:test-ID "record-audio"
:style style/button-container
:hit-slop {:top -70
:bottom 0
:left 0
:right 0}
: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}
[:f> delete-button/f-delete-button recording? ready-to-delete? reviewing-audio?
@force-show-controls?]
[:f> lock-button/f-lock-button recording? ready-to-lock? locked?]
[:f> send-button/f-send-button recording? ready-to-send? reviewing-audio?
@force-show-controls?]
[:f> record-button-big/f-record-button-big
{:recording? recording?
:ready-to-send? ready-to-send?
:ready-to-lock? ready-to-lock?
:ready-to-delete? ready-to-delete?
:record-button-is-animating? record-button-is-animating?
:record-button-at-initial-position? record-button-at-initial-position?
:locked? locked?
:reviewing-audio? reviewing-audio?
:recording-length-ms recording-length-ms
:clear-timeout clear-timeout
:touch-active? touch-active?
:recorder-ref recorder-ref
:reload-recorder-fn reload-recorder
:idle? idle?
:on-send on-send
:on-cancel on-cancel
:theme theme}]
[:f> record-button/f-record-button recording? reviewing-audio?]]])))])
(defn record-audio
[{:keys [on-init on-start-recording on-send on-cancel on-reviewing-audio audio-file on-lock
max-duration-ms on-check-audio-permissions record-audio-permission-granted
on-request-record-audio-permission]}]
(let [;;STATE
[recording? set-recording] (rn/use-state false)
[locked? set-locked] (rn/use-state false)
[ready-to-send? set-ready-to-send] (rn/use-state false)
[ready-to-lock? set-ready-to-lock] (rn/use-state false)
[ready-to-delete?
set-ready-to-delete] (rn/use-state false)
[reviewing-audio?
set-reviewing-audio] (rn/use-state (some? audio-file))
[playing-audio?
set-playing-audio] (rn/use-state false)
[recording-length-ms
set-recording-length-ms] (rn/use-state 0)
[audio-current-time-ms
set-audio-current-time-ms] (rn/use-state 0)
[seeking-audio? set-seeking-audio] (rn/use-state false)
[force-show-controls?
set-force-show-controls] (rn/use-state (some? audio-file))
;;ATOMS
recording-start-ms (rn/use-ref-atom (datetime/timestamp))
clear-timeout (rn/use-ref-atom nil)
record-button-at-initial-position? (rn/use-ref-atom true)
record-button-is-animating? (rn/use-ref-atom false)
idle? (rn/use-ref-atom false)
recorder-ref (rn/use-ref-atom nil)
player-ref (rn/use-ref-atom nil)
touch-active? (rn/use-ref-atom false)
playing-timer (rn/use-ref-atom nil)
output-file (rn/use-ref-atom audio-file)
reached-max-duration? (rn/use-ref-atom false)
touch-timestamp (rn/use-ref-atom nil)
touch-identifier (rn/use-ref-atom nil)
disabled? (rn/use-ref-atom false)
app-state-listener (rn/use-ref-atom nil)
;;HANDLERS
destroy-player (rn/use-callback
(fn []
(audio/destroy-player @player-ref)
(reset! player-ref nil)))
reload-player (handlers/get-reload-player
[player-ref destroy-player set-playing-audio playing-timer
set-audio-current-time-ms set-seeking-audio])
recorder-on-meter (handlers/get-recorder-on-meter
[recording-start-ms set-recording-length-ms
reached-max-duration?
locked? set-locked set-reviewing-audio idle? set-recording
set-ready-to-lock set-ready-to-send set-ready-to-delete
recorder-ref
output-file reload-player
on-reviewing-audio])
reload-recorder (handlers/get-reload-recorder [recorder-ref
recorder-on-meter])
on-start-should-set-responder (handlers/get-on-start-should-set-responder
[locked? idle? disabled? recorder-on-meter touch-timestamp
touch-identifier
reviewing-audio? record-audio-permission-granted
set-recording set-playing-audio output-file
recorder-ref recording-start-ms set-audio-current-time-ms
on-start-recording
on-request-record-audio-permission touch-active?])
on-responder-move (handlers/get-on-responder-move
[locked? ready-to-send? set-ready-to-send
ready-to-delete? set-ready-to-delete
ready-to-lock? set-ready-to-lock
touch-identifier
record-button-at-initial-position?
recording?])
on-responder-release (handlers/get-on-responder-release
[idle? reached-max-duration? touch-timestamp
recording-length-ms set-recording-length-ms
reviewing-audio? set-reviewing-audio
set-audio-current-time-ms set-force-show-controls on-send
output-file player-ref
destroy-player on-cancel record-button-is-animating?
ready-to-lock? set-ready-to-lock
locked? set-locked on-lock ready-to-delete?
set-ready-to-delete ready-to-send? set-ready-to-send
disabled? on-reviewing-audio
recorder-ref set-recording reload-player recording-start-ms
touch-active?])]
(def record-audio (quo.theme/with-theme record-audio-internal))
;;ON MOUNT
(rn/use-mount
(fn []
(when on-check-audio-permissions (on-check-audio-permissions))
(when on-init
(on-init
(fn reset-recorder []
(set-recording false)
(set-reviewing-audio false)
(set-locked false)
(set-ready-to-send false)
(set-ready-to-lock false)
(set-ready-to-delete false)
(set-audio-current-time-ms 0)
(set-recording-length-ms 0)
(set-seeking-audio false)
(set-playing-audio false)
(reset! touch-active? false)
(reset! reached-max-duration? false)
(reset! output-file nil)
(reset! idle? false)
(reset! record-button-is-animating? false)
(reset! record-button-at-initial-position? true)
(when @clear-timeout
(js/clearTimeout @clear-timeout)
(reset! clear-timeout nil))
(when @playing-timer
(js/clearInterval @playing-timer)
(reset! playing-timer nil))
(reload-recorder))))
(when audio-file
(let [filename (last (string/split audio-file "/"))]
(reload-player filename)))
(reset! app-state-listener
(.addEventListener rn/app-state
"change"
#(when (= % "background")
(set-playing-audio false))))))
;;ON UNMOUNT
(rn/use-unmount #(.remove @app-state-listener))
[rn/view
{:style style/bar-container
:pointer-events :box-none}
(when reviewing-audio?
[:<>
[play-button playing-audio? set-playing-audio player-ref playing-timer set-audio-current-time-ms
seeking-audio? set-seeking-audio max-duration-ms]
[soundtrack/soundtrack
{:audio-current-time-ms audio-current-time-ms
:set-audio-current-time-ms set-audio-current-time-ms
:player-ref @player-ref
:seeking-audio? seeking-audio?
:set-seeking-audio set-seeking-audio
:max-audio-duration-ms max-duration-ms}]])
(when (or recording? reviewing-audio?)
[time-counter recording? recording-length-ms ready-to-delete? reviewing-audio?
audio-current-time-ms])
(when recording?
[recording-bar recording-length-ms ready-to-delete?])
[rn/view
{:test-ID "record-audio"
:style style/button-container
:hit-slop {:top -70 :bottom 0 :left 0 :right 0}
: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/delete-button recording? ready-to-delete? reviewing-audio? force-show-controls?]
[lock-button/lock-button recording? ready-to-lock? locked?]
[send-button/send-button recording? ready-to-send? reviewing-audio? force-show-controls?]
[record-button-big/record-button-big
{:recording? recording?
:set-recording set-recording
:ready-to-send? ready-to-send?
:set-ready-to-send set-ready-to-send
:ready-to-lock? ready-to-lock?
:set-ready-to-lock set-ready-to-lock
:ready-to-delete? ready-to-delete?
:set-ready-to-delete set-ready-to-delete
:record-button-is-animating? record-button-is-animating?
:record-button-at-initial-position? record-button-at-initial-position?
:locked? locked?
:set-locked set-locked
:reviewing-audio? reviewing-audio?
:recording-length-ms recording-length-ms
:set-recording-length-ms set-recording-length-ms
:clear-timeout clear-timeout
:touch-active? touch-active?
:recorder-ref recorder-ref
:reload-recorder-fn reload-recorder
:idle? idle?
:on-send on-send
:on-cancel on-cancel}]
[record-button/record-button recording? reviewing-audio?]]]))

View File

@ -2,7 +2,6 @@
(:require
[quo.components.record-audio.soundtrack.view :as soundtrack]
[react-native.audio-toolkit :as audio]
[reagent.core :as reagent]
[test-helpers.component :as h]))
(h/describe "soundtrack component"
@ -10,23 +9,21 @@
(h/test "renders soundtrack"
(with-redefs [audio/get-player-duration (fn [] 2000)]
(let [player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [:f> soundtrack/f-soundtrack
{:player-ref @player-ref
:audio-current-time-ms audio-current-time-ms}])
(-> (h/expect (h/get-by-test-id "soundtrack"))
(.toBeTruthy)))))
(h/render [soundtrack/soundtrack
{:player-ref {}
:audio-current-time-ms 0}])
(-> (h/expect (h/get-by-test-id "soundtrack"))
(.toBeTruthy))))
(h/test "soundtrack on-sliding-start works"
(with-redefs [audio/get-player-duration (fn [] 2000)]
(let [seeking-audio? (reagent/atom false)
player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [:f> soundtrack/f-soundtrack
{:seeking-audio? seeking-audio?
:player-ref @player-ref
:audio-current-time-ms audio-current-time-ms}])
(let [seeking-audio? (atom false)]
(h/render [soundtrack/soundtrack
{:seeking-audio? seeking-audio?
:set-seeking-audio #(reset! seeking-audio? %)
:player-ref {}
:audio-current-time-ms 0
:set-audio-current-time-ms #()}])
(h/fire-event
:on-sliding-start
(h/get-by-test-id "soundtrack"))
@ -36,13 +33,13 @@
(h/test "soundtrack on-sliding-complete works"
(with-redefs [audio/get-player-duration (fn [] 2000)
audio/seek-player (js/jest.fn)]
(let [seeking-audio? (reagent/atom false)
player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [:f> soundtrack/f-soundtrack
{:seeking-audio? seeking-audio?
:player-ref @player-ref
:audio-current-time-ms audio-current-time-ms}])
(let [seeking-audio? (atom false)]
(h/render [soundtrack/soundtrack
{:seeking-audio? seeking-audio?
:set-seeking-audio #(reset! seeking-audio? %)
:player-ref {}
:audio-current-time-ms 0
:set-audio-current-time-ms #()}])
(h/fire-event
:on-sliding-start
(h/get-by-test-id "soundtrack"))
@ -58,13 +55,14 @@
(h/test "soundtrack on-value-change when seeking audio works"
(with-redefs [audio/get-player-duration (fn [] 2000)
audio/seek-player (js/jest.fn)]
(let [seeking-audio? (reagent/atom false)
player-ref (reagent/atom {})
audio-current-time-ms (reagent/atom 0)]
(h/render [:f> soundtrack/f-soundtrack
{:seeking-audio? seeking-audio?
:player-ref @player-ref
:audio-current-time-ms audio-current-time-ms}])
(let [seeking-audio? (atom false)
audio-current-time-ms (atom 0)]
(h/render [soundtrack/soundtrack
{:seeking-audio? seeking-audio?
:set-seeking-audio #(reset! seeking-audio? %)
:player-ref {}
:audio-current-time-ms audio-current-time-ms
:set-audio-current-time-ms #(reset! audio-current-time-ms %)}])
(h/fire-event
:on-sliding-start
(h/get-by-test-id "soundtrack"))

View File

@ -1,42 +1,47 @@
(ns quo.components.record-audio.soundtrack.view
(:require
[quo.components.icons.icons :as icons]
[quo.components.record-audio.soundtrack.style :as style]
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.audio-toolkit :as audio]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.slider :as slider]
[taoensso.timbre :as log]))
(def ^:private thumb-light (js/require "../resources/images/icons2/12x12/thumb-light.png"))
(def ^:private thumb-dark (js/require "../resources/images/icons2/12x12/thumb-dark.png"))
(def ^:private thumb-light (icons/icon-source :thumb-light12))
(def ^:private thumb-dark (icons/icon-source :thumb-dark12))
(defn f-soundtrack
[{:keys [audio-current-time-ms player-ref style seeking-audio? max-audio-duration-ms]}]
(let [audio-duration-ms (min max-audio-duration-ms (audio/get-player-duration player-ref))
theme (quo.theme/use-theme-value)]
[:<>
[slider/slider
{:test-ID "soundtrack"
:style (merge
(style/player-slider-container)
(or style {}))
:minimum-value 0
:maximum-value audio-duration-ms
:value @audio-current-time-ms
:on-sliding-start #(reset! seeking-audio? true)
:on-sliding-complete (fn [seek-time]
(reset! seeking-audio? false)
(audio/seek-player
player-ref
seek-time
#(log/debug "[record-audio] on seek - seek time: " seek-time)
#(log/error "[record-audio] on seek - error: " %)))
:on-value-change #(when @seeking-audio?
(reset! audio-current-time-ms %))
:thumb-image (quo.theme/theme-value thumb-light thumb-dark theme)
:minimum-track-tint-color (colors/theme-colors colors/primary-50 colors/primary-60 theme)
:maximum-track-tint-color (colors/theme-colors
(if platform/ios? colors/neutral-20 colors/neutral-40)
(if platform/ios? colors/neutral-80 colors/neutral-60)
theme)}]]))
(defn soundtrack
[{:keys [audio-current-time-ms set-audio-current-time-ms player-ref style
seeking-audio? set-seeking-audio max-audio-duration-ms]}]
(let [audio-duration-ms (min max-audio-duration-ms (audio/get-player-duration player-ref))
theme (quo.theme/use-theme-value)
on-sliding-start (rn/use-callback #(set-seeking-audio true))
on-sliding-complete (rn/use-callback
(fn [seek-time]
(set-seeking-audio false)
(audio/seek-player
player-ref
seek-time
#(log/debug "[record-audio] on seek - seek time: " seek-time)
#(log/error "[record-audio] on seek - error: " %)))
[player-ref])
on-value-change (rn/use-callback #(when seeking-audio? (set-audio-current-time-ms %))
[seeking-audio?])]
[slider/slider
{:test-ID "soundtrack"
:style (merge (style/player-slider-container) style)
:minimum-value 0
:maximum-value audio-duration-ms
:value audio-current-time-ms
:on-sliding-start on-sliding-start
:on-sliding-complete on-sliding-complete
:on-value-change on-value-change
:thumb-image (quo.theme/theme-value thumb-light thumb-dark theme)
:minimum-track-tint-color (colors/theme-colors colors/primary-50 colors/primary-60 theme)
:maximum-track-tint-color (colors/theme-colors
(if platform/ios? colors/neutral-20 colors/neutral-40)
(if platform/ios? colors/neutral-80 colors/neutral-60)
theme)}]))

View File

@ -357,7 +357,7 @@
;;;; Record Audio
(def record-audio quo.components.record-audio.record-audio.view/record-audio)
(def soundtrack quo.components.record-audio.soundtrack.view/f-soundtrack)
(def soundtrack quo.components.record-audio.soundtrack.view/soundtrack)
;;;; Selectors
(def disclaimer quo.components.selectors.disclaimer.view/view)

View File

@ -38,7 +38,8 @@
(when on-meter
(.on ^js recorder "meter" on-meter))
(when on-ended
(.on ^js recorder "ended" on-ended))))
(.on ^js recorder "ended" on-ended))
recorder))
(defn new-player
[audio options on-ended]
@ -46,7 +47,8 @@
audio
(clj->js options))]
(when on-ended
(.on ^js player "ended" on-ended))))
(.on ^js player "ended" on-ended))
player))
(defn prepare-player
[player on-prepared on-error]
@ -157,20 +159,16 @@
(on-error {:error (.-err err) :message (.-message err)})
(on-seek))))))
(defn can-play?
[player]
(and player (.-canPlay ^js player)))
(defn destroy-recorder
[recorder]
(stop-recording recorder
#(when (and recorder (not= (get-state recorder) DESTROYED))
#(when (and recorder (.-destroy ^js recorder) (not= (get-state recorder) DESTROYED))
(.destroy ^js recorder))
#()))
(defn destroy-player
[player]
(stop-playing player
#(when (and player (not= (get-state player) IDLE))
#(when (and player (.-destroy ^js player) (not= (get-state player) IDLE))
(.destroy ^js player))
#()))

View File

@ -210,12 +210,14 @@
:else :i/play-audio)
{:size 20
:color colors/white}]]
[:f> quo/soundtrack
{:style style/slider-container
:audio-current-time-ms progress
:player-ref (@active-players player-key)
:seeking-audio? seeking-audio?
:max-audio-duration-ms constants/audio-max-duration-ms}]
[quo/soundtrack
{:style style/slider-container
:audio-current-time-ms @progress
:set-audio-current-time-ms #(reset! progress %)
:player-ref (@active-players player-key)
:seeking-audio? @seeking-audio?
:set-seeking-audio? #(reset! seeking-audio? %)
:max-audio-duration-ms constants/audio-max-duration-ms}]
[quo/text
{:style style/timestamp
:accessibility-label :audio-duration-label