From 1a8bca56e0ca90c66cce81f640a143b0f47b02df Mon Sep 17 00:00:00 2001 From: flexsurfer Date: Wed, 13 Mar 2024 14:27:22 +0100 Subject: [PATCH] migrate reagent record audio component part5 (#19212) --- .../record_audio/buttons/delete_button.cljs | 122 +-- .../record_audio/buttons/lock_button.cljs | 106 +-- .../record_audio/buttons/record_button.cljs | 18 +- .../buttons/record_button_big.cljs | 288 +++---- .../record_audio/buttons/send_button.cljs | 128 ++-- .../record_audio/component_spec.cljs | 30 +- .../record_audio/record_audio/constants.cljs | 81 ++ .../record_audio/record_audio/handlers.cljs | 345 +++++++++ .../record_audio/record_audio/view.cljs | 720 +++++------------- .../soundtrack/component_spec.cljs | 56 +- .../record_audio/soundtrack/view.cljs | 67 +- src/quo/core.cljs | 2 +- src/react_native/audio_toolkit.cljs | 14 +- .../messages/content/audio/view.cljs | 14 +- 14 files changed, 1064 insertions(+), 927 deletions(-) create mode 100644 src/quo/components/record_audio/record_audio/constants.cljs create mode 100644 src/quo/components/record_audio/record_audio/handlers.cljs diff --git a/src/quo/components/record_audio/record_audio/buttons/delete_button.cljs b/src/quo/components/record_audio/record_audio/buttons/delete_button.cljs index 99b1d20f97..e49e0c5274 100644 --- a/src/quo/components/record_audio/record_audio/buttons/delete_button.cljs +++ b/src/quo/components/record_audio/record_audio/buttons/delete_button.cljs @@ -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 diff --git a/src/quo/components/record_audio/record_audio/buttons/lock_button.cljs b/src/quo/components/record_audio/record_audio/buttons/lock_button.cljs index 01a87e2f26..018b3bb182 100644 --- a/src/quo/components/record_audio/record_audio/buttons/lock_button.cljs +++ b/src/quo/components/record_audio/record_audio/buttons/lock_button.cljs @@ -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}]]])) diff --git a/src/quo/components/record_audio/record_audio/buttons/record_button.cljs b/src/quo/components/record_audio/record_audio/buttons/record_button.cljs index 213efb32e2..48a9d0ecd1 100644 --- a/src/quo/components/record_audio/record_audio/buttons/record_button.cljs +++ b/src/quo/components/record_audio/record_audio/buttons/record_button.cljs @@ -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 diff --git a/src/quo/components/record_audio/record_audio/buttons/record_button_big.cljs b/src/quo/components/record_audio/record_audio/buttons/record_button_big.cljs index 0d348c7bfb..80dd511a97 100644 --- a/src/quo/components/record_audio/record_audio/buttons/record_button_big.cljs +++ b/src/quo/components/record_audio/record_audio/buttons/record_button_big.cljs @@ -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}])]]])) diff --git a/src/quo/components/record_audio/record_audio/buttons/send_button.cljs b/src/quo/components/record_audio/record_audio/buttons/send_button.cljs index 6ccf4322fa..85065e1c21 100644 --- a/src/quo/components/record_audio/record_audio/buttons/send_button.cljs +++ b/src/quo/components/record_audio/record_audio/buttons/send_button.cljs @@ -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 diff --git a/src/quo/components/record_audio/record_audio/component_spec.cljs b/src/quo/components/record_audio/record_audio/component_spec.cljs index 122fa2da99..20f895342a 100644 --- a/src/quo/components/record_audio/record_audio/component_spec.cljs +++ b/src/quo/components/record_audio/record_audio/component_spec.cljs @@ -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}]) diff --git a/src/quo/components/record_audio/record_audio/constants.cljs b/src/quo/components/record_audio/record_audio/constants.cljs new file mode 100644 index 0000000000..36261fb731 --- /dev/null +++ b/src/quo/components/record_audio/record_audio/constants.cljs @@ -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})) diff --git a/src/quo/components/record_audio/record_audio/handlers.cljs b/src/quo/components/record_audio/record_audio/handlers.cljs new file mode 100644 index 0000000000..26b0887ddd --- /dev/null +++ b/src/quo/components/record_audio/record_audio/handlers.cljs @@ -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))))) diff --git a/src/quo/components/record_audio/record_audio/view.cljs b/src/quo/components/record_audio/record_audio/view.cljs index 53371b6242..1729a9f6ca 100644 --- a/src/quo/components/record_audio/record_audio/view.cljs +++ b/src/quo/components/record_audio/record_audio/view.cljs @@ -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?]]])) diff --git a/src/quo/components/record_audio/soundtrack/component_spec.cljs b/src/quo/components/record_audio/soundtrack/component_spec.cljs index 818955e11a..dd851a828a 100644 --- a/src/quo/components/record_audio/soundtrack/component_spec.cljs +++ b/src/quo/components/record_audio/soundtrack/component_spec.cljs @@ -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")) diff --git a/src/quo/components/record_audio/soundtrack/view.cljs b/src/quo/components/record_audio/soundtrack/view.cljs index ba8eb7c725..4cb6e675a6 100644 --- a/src/quo/components/record_audio/soundtrack/view.cljs +++ b/src/quo/components/record_audio/soundtrack/view.cljs @@ -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)}])) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index de7a05d452..5798ae1638 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -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) diff --git a/src/react_native/audio_toolkit.cljs b/src/react_native/audio_toolkit.cljs index d2dbeb8b38..6ac8881c13 100644 --- a/src/react_native/audio_toolkit.cljs +++ b/src/react_native/audio_toolkit.cljs @@ -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)) #())) diff --git a/src/status_im/contexts/chat/messenger/messages/content/audio/view.cljs b/src/status_im/contexts/chat/messenger/messages/content/audio/view.cljs index d31dfc92dd..58ad55061d 100644 --- a/src/status_im/contexts/chat/messenger/messages/content/audio/view.cljs +++ b/src/status_im/contexts/chat/messenger/messages/content/audio/view.cljs @@ -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