feat: integrate record audio component into composer

Signed-off-by: Brian Sztamfater <brian@status.im>
This commit is contained in:
Brian Sztamfater 2023-01-25 14:06:27 -03:00
parent f67f205fa9
commit 7332f483a4
No known key found for this signature in database
GPG Key ID: 59EB921E0706B48F
22 changed files with 704 additions and 429 deletions

View File

@ -147,7 +147,10 @@ globalThis.__STATUS_MOBILE_JS_IDENTITY_PROXY__ = new Proxy({}, {get() { return (
:SvgXml #js {:render identity}
:default #js {:render identity}})
(def react-native-webview #js {:default {}})
(def react-native-audio-toolkit #js {:MediaStates {}})
(def react-native-audio-toolkit
#js
{:MediaStates {}
:PlaybackCategories {}})
(def net-info #js {})
(def touchid #js {})
(def react-native-image-viewing #js {:default {}})

View File

@ -27,7 +27,8 @@
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
:locationY 70
:timestamp 0}})
(-> (h/expect event)
(.toHaveBeenCalledTimes 1))))
@ -36,19 +37,23 @@
(h/render [record-audio/record-audio
{:on-reviewing-audio event
:record-audio-permission-granted true}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))]
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))
audio/get-recorder-file-path (fn [] "file-path")]
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
:locationY 70
:timestamp 0}})
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
:locationY 70
:timestamp 200}})
(h/advance-timers-by-time 250)
(-> (h/expect event)
(.toHaveBeenCalledTimes 1)))))
@ -59,27 +64,40 @@
:record-audio-permission-granted true}])
(with-redefs [audio/start-recording (fn [_ on-start _]
(on-start))
audio/get-recorder-file-path (fn [] "audio-file-path")]
audio/get-recorder-file-path (fn [] "audio-file-path")
audio/get-player-duration (fn [] 5000)]
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
:locationY 70
:timestamp 0}})
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
:locationY 70
:timestamp 200}})
(h/fire-event
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70
:timestamp 0}})
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 80
:locationY 80}})
:locationY 80
:timestamp 200}})
(h/advance-timers-by-time 250)
(-> (js/expect event)
(.toHaveBeenCalledTimes 1))
(-> (js/expect event)
(.toHaveBeenCalledWith "audio-file-path")))))
(.toHaveBeenCalledWith {:file-path "audio-file-path"
:duration 5000})))))
(h/test "record-audio on-send works after sliding to the send button"
(let [event (js/jest.fn)]
@ -95,7 +113,8 @@
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
:locationY 70
:timestamp 0}})
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-move
@ -108,11 +127,14 @@
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 40
:locationY 80}})
:locationY 80
:timestamp 200}})
(h/advance-timers-by-time 250)
(-> (js/expect event)
(.toHaveBeenCalledTimes 1))
(-> (js/expect event)
(.toHaveBeenCalledWith "audio-file-path")))))
(.toHaveBeenCalledWith {:file-path "audio-file-path"
:duration 500})))))
(h/test "record-audio on-cancel works after reviewing audio"
(let [event (js/jest.fn)]
@ -125,18 +147,22 @@
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
:locationY 70
:timestamp 0}})
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
:locationY 70
:timestamp 200}})
(h/fire-event
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 40
:locationY 80}})
:locationY 80
:timestamp 200}})
(h/advance-timers-by-time 250)
(-> (js/expect event)
(.toHaveBeenCalledTimes 1)))))
@ -153,7 +179,8 @@
:on-start-should-set-responder
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX 70
:locationY 70}})
:locationY 70
:timestamp 0}})
(h/advance-timers-by-time 500)
(h/fire-event
:on-responder-move
@ -166,6 +193,8 @@
:on-responder-release
(h/get-by-test-id "record-audio")
{:nativeEvent {:locationX -10
:locationY 70}})
:locationY 70
:timestamp 200}})
(h/advance-timers-by-time 250)
(-> (js/expect event)
(.toHaveBeenCalledTimes 1))))))

View File

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

View File

@ -4,11 +4,7 @@
[quo2.foundations.colors :as colors]
[react-native.reanimated :as reanimated]
[react-native.core :refer [use-effect]]
[quo2.components.record-audio.record-audio.helpers :refer
[animate-linear-with-delay
animate-easing-with-delay
animate-linear
set-value]]))
[quo2.components.record-audio.record-audio.helpers :as helpers]))
(defn lock-button
[recording? ready-to-lock? locked?]
@ -22,36 +18,36 @@
border-radius-first-half (reanimated/use-shared-value 8)
border-radius-second-half (reanimated/use-shared-value 8)
start-x-y-animation (fn []
(animate-linear-with-delay translate-x-y 8 50 116.66)
(animate-easing-with-delay connector-opacity 1 0 80)
(animate-easing-with-delay width 56 83.33 63.33)
(animate-easing-with-delay height 56 83.33 63.33)
(animate-easing-with-delay border-radius-first-half
28
83.33
63.33)
(animate-easing-with-delay border-radius-second-half
28
83.33
63.33))
(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 []
(animate-linear translate-x-y 0 100)
(set-value connector-opacity 0)
(set-value width 24)
(set-value height 12)
(set-value border-radius-first-half 8)
(set-value border-radius-second-half 16))
(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 []
(animate-linear translate-x-y 0 220)
(animate-linear opacity 1 220))
(helpers/animate-linear translate-x-y 0 220)
(helpers/animate-linear opacity 1 220))
fade-out-animation (fn []
(animate-linear translate-x-y 20 200)
(animate-linear opacity 0 200)
(set-value connector-opacity 0)
(set-value width 24)
(set-value height 12)
(set-value border-radius-first-half 8)
(set-value border-radius-second-half 16))]
(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)

View File

@ -5,15 +5,15 @@
[react-native.core :as rn :refer [use-effect]]
[react-native.reanimated :as reanimated]
[quo2.components.buttons.button :as button]
[quo2.components.record-audio.record-audio.helpers :refer [set-value]]))
[quo2.components.record-audio.record-audio.helpers :as helpers]))
(defn record-button
[recording? reviewing-audio?]
[:f>
(fn []
(let [opacity (reanimated/use-shared-value 1)
show-animation #(set-value opacity 1)
hide-animation #(set-value opacity 0)]
show-animation #(helpers/set-value opacity 1)
hide-animation #(helpers/set-value opacity 0)]
(use-effect (fn []
(if (or @recording? @reviewing-audio?)
(hide-animation)
@ -25,4 +25,4 @@
:size 32
:width 32
:accessibility-label :mic-button}
[icons/icon :i/audio {:color (colors/theme-colors colors/neutral-100 colors/white)}]]]))])
[icons/icon :i/audio {:color (colors/theme-colors colors/neutral-50 colors/neutral-40)}]]]))])

View File

@ -8,12 +8,7 @@
[taoensso.timbre :as log]
[cljs-bean.core :as bean]
[reagent.core :as reagent]
[quo2.components.record-audio.record-audio.helpers :refer
[animate-linear
animate-linear-with-delay
animate-linear-with-delay-loop
animate-easing
set-value]]))
[quo2.components.record-audio.record-audio.helpers :as helpers]))
(def ^:private scale-to-each 1.8)
(def ^:private scale-to-total 2.6)
@ -45,129 +40,131 @@
clear-timeout touch-active? recorder-ref reload-recorder-fn idle? on-send on-cancel]
[:f>
(fn []
(let [scale (reanimated/use-shared-value 1)
opacity (reanimated/use-shared-value 0)
opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default)
animations (map
(fn [index]
(let [ring-scale (ring-scale scale (* scale-padding index))]
{:scale ring-scale
:opacity (reanimated/interpolate ring-scale
[1 scale-to-each]
[opacity-from 0])}))
(range 0 5))
rings-color (cond
@ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
colors/neutral-80)
@ready-to-delete? colors/danger-50
:else colors/primary-50)
translate-y (reanimated/use-shared-value 0)
translate-x (reanimated/use-shared-value 0)
button-color colors/primary-50
icon-color (if (and (not (colors/dark?)) @ready-to-lock?) colors/black colors/white)
icon-opacity (reanimated/use-shared-value 1)
red-overlay-opacity (reanimated/use-shared-value 0)
(let [scale (reanimated/use-shared-value 1)
opacity (reanimated/use-shared-value 0)
opacity-from (if @ready-to-lock? opacity-from-lock opacity-from-default)
animations (map
(fn [index]
(let [ring-scale (ring-scale scale (* scale-padding index))]
{:scale ring-scale
:opacity (reanimated/interpolate ring-scale
[1 scale-to-each]
[opacity-from 0])}))
(range 0 5))
rings-color (cond
@ready-to-lock? (colors/theme-colors colors/neutral-80-opa-5-opaque
colors/neutral-80)
@ready-to-delete? colors/danger-50
:else colors/primary-50)
translate-y (reanimated/use-shared-value 0)
translate-x (reanimated/use-shared-value 0)
button-color colors/primary-50
icon-color (if (and (not (colors/dark?)) @ready-to-lock?) colors/black colors/white)
icon-opacity (reanimated/use-shared-value 1)
red-overlay-opacity (reanimated/use-shared-value 0)
gray-overlay-opacity (reanimated/use-shared-value 0)
complete-animation (fn []
(cond
(and @ready-to-lock? (not @record-button-is-animating?))
(do
(reset! locked? true)
(reset! ready-to-lock? false))
(and (not @locked?) (not @reviewing-audio?))
(audio/stop-recording
@recorder-ref
(fn []
(cond
@ready-to-send?
(when on-send
(on-send (audio/get-recorder-file-path @recorder-ref)))
@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)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))))
start-animation (fn []
(set-value opacity 1)
(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 []
(set-value scale scale-to-each)
(animate-linear-with-delay-loop scale
scale-to-total
signal-anim-duration-2
0))
signal-anim-duration)))
stop-animation (fn []
(set-value opacity 0)
(reanimated/cancel-animation scale)
(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)
(animate-easing translate-y -64 250)
(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 []
(animate-easing translate-y 0 300)
(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)
(animate-easing translate-x -64 250)
(animate-linear-with-delay icon-opacity 0 33.33 76.66)
(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 []
(animate-easing translate-x 0 300)
(animate-linear icon-opacity 1 500)
(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)
(animate-easing translate-y -44 200)
(animate-easing translate-x -44 200)
(animate-linear-with-delay icon-opacity 0 33.33 33.33)
(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 []
(animate-easing translate-y 0 300)
(animate-easing translate-x 0 300)
(animate-linear icon-opacity 1 500)
(animate-linear gray-overlay-opacity 0 800)
(js/setTimeout (fn []
(reset! record-button-at-initial-position? true))
800))]
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)
(js/clearInterval @recording-timer)
(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
(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?

View File

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

View File

@ -14,19 +14,17 @@
[quo2.components.record-audio.record-audio.buttons.send-button :as send-button]
[quo2.components.record-audio.record-audio.buttons.lock-button :as lock-button]
[quo2.components.record-audio.record-audio.buttons.delete-button :as delete-button]
[quo2.components.record-audio.record-audio.buttons.record-button :as record-button]))
[quo2.components.record-audio.record-audio.buttons.record-button :as record-button]
[react-native.platform :as platform]
[clojure.string :as string]))
(def ^:private min-audio-duration-ms 500)
(def ^:private max-audio-duration-ms 120000)
(def ^:private metering-interval 100)
(def ^:private base-filename "am.")
(def ^:private default-format "aac")
(def ^:private base-filename "am")
(def ^:private default-format ".aac")
(def ^:private rec-options
(merge
audio/default-recorder-options
{:filename (str base-filename default-format)
:meteringInterval metering-interval}))
(def min-touch-duration 150)
(def ^:private record-button-area-big
{:width 56
@ -149,8 +147,10 @@
{:color (colors/theme-colors colors/neutral-100 colors/white)}]]))])
(defn view
[{:keys [on-start-recording on-send on-cancel on-reviewing-audio record-audio-permission-granted
on-request-record-audio-permission on-check-audio-permissions]}]
[{: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]}]
[:f>
(fn []
(let [recording? (reagent/atom false)
@ -163,30 +163,39 @@
recording-length-ms (reagent/atom 0)
audio-current-time-ms (reagent/atom 0)
seeking-audio? (reagent/atom false)
force-show-controls? (reagent/atom false)
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)
recording-timer (atom nil)
playing-timer (atom nil)
recorder-ref (atom nil)
player-ref (atom nil)
output-file (atom nil)
reached-max-duration? (atom false)
touch-timestamp (atom nil)
disabled? (atom false)
rec-options
(merge
audio/default-recorder-options
{:filename (str base-filename (.now js/Date) default-format)
:meteringInterval metering-interval})
destroy-player
(fn []
(audio/destroy-player @player-ref)
(reset! player-ref nil))
reload-player
(fn []
(fn [audio-file]
(when @player-ref
(destroy-player))
(reset! player-ref
(audio/new-player
(:filename rec-options)
(or audio-file (:filename rec-options))
{:autoDestroy false
:continuesToPlayInBackground false}
:continuesToPlayInBackground false
:category audio/PLAYBACK}
(fn []
(reset! playing-audio? false)
(when @playing-timer
@ -210,9 +219,37 @@
rec-options
#(log/debug "[record-audio] new 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 @recording-timer
(js/clearInterval @recording-timer)
(reset! recording-timer 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))
(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")
@ -220,7 +257,12 @@
:ignore-max-y? false
:ignore-min-x? false
:ignore-max-x? false}
record-button-area)]
record-button-area)
new-recorder (audio/new-recorder
rec-options
#(log/debug "[record-audio] new recorder - on meter")
#(log/debug "[record-audio] new recorder - on ended"))]
(reset! touch-timestamp (oops/oget e "nativeEvent.timestamp"))
(when-not @reviewing-audio?
(if record-audio-permission-granted
(do
@ -231,8 +273,9 @@
(when @recording-timer
(js/clearInterval @recording-timer))
(reset! output-file nil)
(reset! recorder-ref new-recorder)
(audio/start-recording
@recorder-ref
new-recorder
(fn []
(reset! audio-current-time-ms 0)
(reset! recording-timer
@ -251,28 +294,29 @@
(reset! ready-to-send? false)
(reset! ready-to-delete? false)
(audio/stop-recording
@recorder-ref
new-recorder
(fn []
(reset! output-file (audio/get-recorder-file-path
@recorder-ref))
(reload-recorder)
(reload-player)
new-recorder))
(reload-player nil)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(when on-reviewing-audio
(on-reviewing-audio)))))
(on-reviewing-audio (audio/get-recorder-file-path
new-recorder))))))
metering-interval))
(log/debug "[record-audio] start recording - success"))
#(log/error "[record-audio] start recording - error: " %))
(when on-start-recording
(on-start-recording))))
(some-> on-request-record-audio-permission)))
(when on-request-record-audio-permission
(on-request-record-audio-permission))))
(when record-audio-permission-granted
(reset! touch-active? true))))
(not @idle?))
(and (not @idle?) (not @disabled?)))
on-responder-move
(fn [^js e]
(when-not @locked?
@ -349,43 +393,49 @@
(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}))]
(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 (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: " %)))
(when on-send
(on-send @output-file)))
#(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)))
@ -393,61 +443,86 @@
(do
(reset! locked? true)
(reset! ready-to-lock? false))
(and (not @reviewing-audio?) on-record-button?)
(and (not @reviewing-audio?)
(or on-record-button?
(and (not @ready-to-delete?)
(not @ready-to-lock?)
(not @ready-to-send?))))
(do
(if (>= @recording-length-ms min-audio-duration-ms)
(do (reset! reviewing-audio? true)
(reset! idle? false)
(when on-reviewing-audio
(on-reviewing-audio)))
(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
(reset! disabled? (<= touch-timestamp-diff min-touch-duration))
(js/setTimeout
(fn []
(reset! output-file (audio/get-recorder-file-path @recorder-ref))
(reload-recorder)
(reload-player)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))
(js/setTimeout #(reset! idle? false) 1000)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0))
(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)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(reset! disabled? false))
(if (> touch-timestamp-diff min-touch-duration) 0 250)))
(and (not @locked?) (not @reviewing-audio?) (not @record-button-is-animating?))
(audio/stop-recording
@recorder-ref
(fn []
(cond
@ready-to-send?
(when on-send
(on-send (audio/get-recorder-file-path @recorder-ref)))
@ready-to-delete?
(when on-cancel
(on-cancel)))
(reload-recorder)
(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)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(log/debug "[record-audio] stop recording - success"))
#(log/error "[record-audio] stop recording - error: " %))))
(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)
(js/clearInterval @recording-timer)
(reset! recording-length-ms 0)
(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! reached-max-duration? false))
(reset! touch-timestamp nil))]
(fn []
(use-effect (fn []
(some-> on-check-audio-permissions)
(reload-recorder)))
(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! output-file audio-file)
(reset! reviewing-audio? true)
(reset! force-show-controls? true)))))
[rn/view
{:style style/bar-container}
{:style style/bar-container
:pointer-events :box-none}
(when @reviewing-audio?
[:<>
[play-button playing-audio? player-ref playing-timer audio-current-time-ms seeking-audio?]
@ -463,13 +538,18 @@
[rn/view
{:test-ID "record-audio"
:style style/button-container
:hit-slop {:top (if platform/ios? -70 0)
: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?]
[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?]
[send-button/send-button recording? ready-to-send? reviewing-audio? @force-show-controls?]
[record-button-big/record-button-big
recording?
ready-to-send?

View File

@ -50,6 +50,7 @@
quo2.components.profile.profile-card.view
quo2.components.profile.select-profile.view
quo2.components.reactions.reaction
quo2.components.record-audio.record-audio.view
quo2.components.selectors.disclaimer.view
quo2.components.selectors.filter.view
quo2.components.selectors.selectors
@ -162,6 +163,9 @@
(def profile-card quo2.components.profile.profile-card.view/profile-card)
(def select-profile quo2.components.profile.select-profile.view/view)
;;;; RECORD AUDIO
(def record-audio quo2.components.record-audio.record-audio.view/record-audio)
;;;; SETTINGS
(def privacy-option quo2.components.settings.privacy-option/card)
(def account quo2.components.settings.accounts.view/account)

View File

@ -72,6 +72,9 @@
(def neutral-80-opa-90 (alpha neutral-80 0.9))
(def neutral-80-opa-95 (alpha neutral-80 0.95))
;;90 with transparency
(def neutral-90-opa-0 (alpha neutral-90 0))
;;95 with transparency
(def neutral-95-opa-60 (alpha neutral-95 0.6))
(def neutral-95-opa-70 (alpha neutral-95 0.7))
@ -98,6 +101,7 @@
(def white "#ffffff")
;; with transparency
(def white-opa-0 (alpha white 0))
(def white-opa-5 (alpha white 0.05))
(def white-opa-10 (alpha white 0.1))
(def white-opa-20 (alpha white 0.2))

View File

@ -1,5 +1,6 @@
(ns react-native.audio-toolkit
(:require ["@react-native-community/audio-toolkit" :refer (Player Recorder MediaStates)]))
(:require ["@react-native-community/audio-toolkit" :refer
(Player Recorder MediaStates PlaybackCategories)]))
;; get mediastates from react module
(def PLAYING (.-PLAYING ^js MediaStates))
@ -11,13 +12,17 @@
(def DESTROYED (.-DESTROYED ^js MediaStates))
(def SEEKING (.-SEEKING ^js MediaStates))
;; get PlaybackCategories from react module
(def PLAYBACK (.-Playback ^js PlaybackCategories))
(def default-recorder-options
{:filename "recording.aac"
:bitrate 32000
:channels 1
:sampleRate 22050
:quality "medium" ; ios only
:meteringInterval 50})
:meteringInterval 50
:category PLAYBACK})
(defn get-state
[player-recorder]

View File

@ -195,15 +195,21 @@
(chat.message/send-messages messages)))))
(rf/defn send-audio-message
[cofx audio-path duration current-chat-id]
(when-not (string/blank? audio-path)
(chat.message/send-message
cofx
{:chat-id current-chat-id
:content-type constants/content-type-audio
:audio-path audio-path
:audio-duration-ms duration
:text (i18n/label :t/update-to-listen-audio {"locale" "en"})})))
[{:keys [db] :as cofx} audio-path duration current-chat-id]
(let [{:keys [message-id]}
(get-in db [:chat/inputs current-chat-id :metadata :responding-to-message])]
(when-not (string/blank? audio-path)
(rf/merge
{:db (assoc-in db [:chat/inputs current-chat-id :metadata :responding-to-message] nil)}
(chat.message/send-message
(merge
{:chat-id current-chat-id
:content-type constants/content-type-audio
:audio-path audio-path
:audio-duration-ms duration
:text (i18n/label :t/update-to-listen-audio {"locale" "en"})}
(when message-id
{:response-to message-id})))))))
(rf/defn send-sticker-message
[cofx {:keys [hash packID pack]} current-chat-id]
@ -293,5 +299,5 @@
(rf/defn chat-send-audio
{:events [:chat/send-audio]}
[{{:keys [current-chat-id]} :db :as cofx} audio-path duration]
[{{:keys [current-chat-id] :as db} :db :as cofx} audio-path duration]
(send-audio-message cofx audio-path duration current-chat-id))

View File

@ -7,11 +7,18 @@
:flex-direction :row})
(defn quoted-message
[pin?]
[pin? in-chat-input?]
(merge {:flex-direction :row
:align-items :center
:width "45%"}
:width (if in-chat-input? "80%" "45%")}
(when-not pin?
{:position :absolute
:left 34
:top 3})))
(def gradient
{:position :absolute
:right 0
:top 0
:bottom 0
:width "50%"})

View File

@ -11,7 +11,8 @@
[status-im.ui.components.icons.icons :as icons]
[status-im.ui.screens.chat.photos :as photos]
[utils.re-frame :as rf]
[status-im.ui2.screens.chat.components.reply.style :as style]))
[status-im.ui2.screens.chat.components.reply.style :as style]
[react-native.linear-gradient :as linear-gradient]))
(defn get-quoted-text-with-mentions
[parsed-text]
@ -65,7 +66,7 @@
(defn reply-message
[{:keys [from identicon content-type contentType parsed-text content deleted? deleted-for-me?]}
in-chat-input? pin?]
in-chat-input? pin? recording-audio?]
(let [contact-name (rf/sub [:contacts/contact-name-by-identity from])
current-public-key (rf/sub [:multiaccount/public-key])
content-type (or content-type contentType)]
@ -80,9 +81,9 @@
{:color (colors/theme-colors colors/neutral-40 colors/neutral-60)
:container-style {:position :absolute :left 10 :bottom -4 :width 16 :height 16}}])
(if (or deleted? deleted-for-me?)
[rn/view {:style (style/quoted-message pin?)}
[rn/view {:style (style/quoted-message pin? in-chat-input?)}
[reply-deleted-message]]
[rn/view {:style (style/quoted-message pin?)}
[rn/view {:style (style/quoted-message pin? in-chat-input?)}
[photos/member-photo from identicon 16]
[quo2.text/text
{:weight :semi-bold
@ -109,7 +110,7 @@
constants/content-type-sticker "Sticker"
constants/content-type-audio "Audio"
(get-quoted-text-with-mentions (or parsed-text (:parsed-text content))))]])]
(when in-chat-input?
(when (and in-chat-input? (not recording-audio?))
[quo2.button/button
{:width 24
:size 24
@ -120,4 +121,11 @@
[icons/icon :main-icons/close
{:width 16
:height 16
:color (colors/theme-colors colors/neutral-100 colors/neutral-40)}]])]))
:color (colors/theme-colors colors/neutral-100 colors/neutral-40)}]])
(when (and in-chat-input? recording-audio?)
[linear-gradient/linear-gradient
{:colors [(colors/theme-colors colors/white-opa-0 colors/neutral-90-opa-0)
(colors/theme-colors colors/white colors/neutral-90)]
:start {:x 0 :y 0}
:end {:x 0.7 :y 0}
:style style/gradient}])]))

View File

@ -21,6 +21,11 @@
(defonce mentions-enabled? (reagent/atom {}))
(defonce chat-input-key (reagent/atom 1))
(defonce text-input-ref (reagent/atom nil))
(defonce recording-audio? (reagent/atom false))
(defonce reviewing-audio? (reagent/atom false))
(defonce reviewing-audio-filepath (atom {}))
(defonce record-audio-permission-granted (reagent/atom false))
(defonce record-audio-reset-fn (atom nil))
(declare selectable-text-input)
@ -38,18 +43,25 @@
.focus))
(defn show-send
[{:keys [actions-ref send-ref sticker-ref]}]
[{:keys [actions-ref send-ref sticker-ref record-ref]} chat-id]
(when (and (or @recording-audio?
(get @reviewing-audio-filepath chat-id))
@record-audio-reset-fn)
(@record-audio-reset-fn)
(swap! reviewing-audio-filepath dissoc chat-id))
(when actions-ref
(quo.react/set-native-props actions-ref #js {:width 0 :left -88}))
(quo.react/set-native-props send-ref #js {:width nil :right nil})
(quo.react/set-native-props record-ref #js {:right nil :left -1000})
(when sticker-ref
(quo.react/set-native-props sticker-ref #js {:width 0 :right -100})))
(defn hide-send
[{:keys [actions-ref send-ref sticker-ref]}]
[{:keys [actions-ref send-ref sticker-ref record-ref]}]
(when actions-ref
(quo.react/set-native-props actions-ref #js {:width nil :left nil}))
(quo.react/set-native-props send-ref #js {:width 0 :right -100})
(quo.react/set-native-props record-ref #js {:left 0 :right 0})
(when sticker-ref
(quo.react/set-native-props sticker-ref #js {:width nil :right nil})))
@ -116,7 +128,9 @@
(when (and (seq prev-text) (empty? text) (not sending-image))
(hide-send refs))
(when (and (empty? prev-text) (or (seq text) sending-image))
(show-send refs))
(show-send refs chat-id)
(reset! recording-audio? false)
(swap! reviewing-audio-filepath dissoc chat-id))
(when (and (not (get @mentions-enabled? chat-id)) (string/index-of text "@"))
(swap! mentions-enabled? assoc chat-id true))
@ -159,7 +173,7 @@
(rf/dispatch [::mentions/calculate-suggestions mentionable-users]))))
(defn text-input-style
[]
[chat-id]
(merge typography/font-regular
typography/paragraph-1
{:flex 1
@ -172,7 +186,9 @@
{:padding-vertical 8
:text-align-vertical :top}
{:margin-top 8
:margin-bottom 8})))
:margin-bottom 8})
(when (or @recording-audio? (get @reviewing-audio-filepath chat-id))
{:display :none})))
(defn text-input
[{:keys [refs chat-id sending-image on-content-size-change]}]
@ -182,7 +198,7 @@
last-text-change (reagent/atom nil)
mentions-enabled? (get @mentions-enabled? chat-id)
props
{:style (text-input-style)
{:style (text-input-style chat-id)
:ref (:text-input-ref refs)
:max-font-size-multiplier 1
:accessibility-label :chat-message-input

View File

@ -19,6 +19,19 @@
(focus-input-on-reply reply had-reply text-input-ref)
(when reply
[rn/view
{:style {:padding-horizontal 15
:padding-vertical 8}}
[reply/reply-message reply true]]))))
{:style (merge
{:padding-horizontal 15
:padding-vertical 8}
(when @input/recording-audio?
{:position :absolute
:top 12
:left 0
:right 0
;;When recording an audio and replying at the same time,
;;text input is overlapped by the reply component but
;;text input still have priority over touches, so we need
;;to force the reply component to receive the touches in this
;;scenario, thus we increase its z-index
:z-index 1}))}
[reply/reply-message reply true false
(and @input/recording-audio? (not @input/reviewing-audio?))]]))))

View File

@ -12,3 +12,18 @@
:bottom 0
:left 0
:right 0})
(def buttons-container
{:flex-direction :row
:margin-top 12
:min-height 32})
(defn record-audio-container
[insets]
{:align-items :center
:background-color :transparent
:flex-direction :row
:position :absolute
:left 0
:right 0
:bottom (- (:bottom insets) 7)})

View File

@ -7,10 +7,13 @@
[status-im2.common.not-implemented :as not-implemented]
[status-im2.contexts.chat.messages.composer.controls.style :as style]
[status-im2.contexts.chat.messages.list.view :as messages.list]
[status-im.ui.components.permissions :as permissions]
[status-im.ui2.screens.chat.composer.images.view :as composer-images]
[status-im.utils.utils :as utils-old]
[status-im.ui2.screens.chat.composer.input :as input]))
[status-im.ui2.screens.chat.composer.input :as input]
[status-im2.common.alert.events :as alert]
[react-native.permissions :as permissions]
[react-native.safe-area :as safe-area]
[quo.react :as quo.react]))
(defn send-button
[send-ref {:keys [chat-id images]} on-send]
@ -56,13 +59,73 @@
:size 32}
:i/image])
(defn record-audio
[record-ref chat-id]
[safe-area/consumer
(fn [insets]
[rn/view
{:ref record-ref
:style (style/record-audio-container insets)
:pointer-events :box-none}
[quo/record-audio
{:record-audio-permission-granted @input/record-audio-permission-granted
:on-init (fn [init-fn]
(reset! input/record-audio-reset-fn init-fn)
(reset! input/recording-audio?
(some? (get @input/reviewing-audio-filepath chat-id)))
(when (seq (get @input/input-texts chat-id))
(js/setTimeout #(quo.react/set-native-props
record-ref
#js {:right nil :left -1000}))))
:on-start-recording #(reset! input/recording-audio? true)
:audio-file (get @input/reviewing-audio-filepath chat-id)
:on-reviewing-audio (fn [audio-file]
(swap! input/reviewing-audio-filepath assoc
chat-id
audio-file)
(reset! input/reviewing-audio? true))
:on-send (fn
[{:keys [file-path duration]}]
(rf/dispatch [:chat/send-audio file-path duration])
(reset! input/recording-audio? false)
(reset! input/reviewing-audio? false)
(swap! input/reviewing-audio-filepath dissoc chat-id))
:on-cancel (fn []
(reset! input/recording-audio? false)
(reset! input/reviewing-audio? false)
(swap! input/reviewing-audio-filepath dissoc chat-id))
:on-check-audio-permissions (fn []
(permissions/permission-granted?
:record-audio
#(reset! input/record-audio-permission-granted %)
#(reset! input/record-audio-permission-granted false)))
:on-request-record-audio-permission (fn []
(rf/dispatch
[:request-permissions
{:permissions [:record-audio]
:on-allowed
#(reset! input/record-audio-permission-granted true)
:on-denied
#(js/setTimeout
(fn []
(alert/show-popup
(i18n/label :t/audio-recorder-error)
(i18n/label
:t/audio-recorder-permissions-error)))
50)}]))}]])])
(defn view
[send-ref params insets chat-id images on-send]
[send-ref record-ref params insets chat-id images edit on-send]
[rn/view {:style (style/controls insets)}
[composer-images/images-list images]
[rn/view {:style {:flex-direction :row :margin-top 12}}
[image-button chat-id]
[rn/view {:width 12}]
[reactions-button]
[rn/view {:flex 1}]
[send-button send-ref params on-send]]])
[rn/view {:style style/buttons-container}
(when (and (not @input/recording-audio?)
(nil? (get @input/reviewing-audio-filepath chat-id)))
[:<>
[image-button chat-id]
[rn/view {:width 12}]
[reactions-button]
[rn/view {:flex 1}]
[send-button send-ref params on-send]])]
(when (and (not edit) (not (seq images)))
[record-audio record-ref chat-id])])

View File

@ -12,22 +12,26 @@
[status-im2.contexts.chat.messages.composer.mentions.view :as mentions]
[status-im.ui2.screens.chat.composer.edit.view :as edit]
[status-im.ui2.screens.chat.composer.input :as input]
[status-im.ui2.screens.chat.composer.reply :as reply]))
[status-im.ui2.screens.chat.composer.reply :as reply]
[quo.react :refer [set-native-props]]))
(def initial-content-height (atom nil))
(def keyboard-hiding? (atom false))
(defn minimize
[{:keys [min-y set-bg-opacity set-translate-y set-parent-height]}]
[{:keys [min-y set-bg-opacity set-translate-y set-parent-height refs chat-id]}]
(set-bg-opacity 0)
(set-translate-y (- min-y))
(set-parent-height min-y))
(set-parent-height min-y)
(when-not (seq (get @input/input-texts chat-id))
(set-native-props (:record-ref refs) #js {:right 0 :left 0})))
(defn maximize
[{:keys [max-y set-bg-opacity set-translate-y set-parent-height max-parent-height]}]
[{:keys [max-y set-bg-opacity set-translate-y set-parent-height max-parent-height refs]}]
(set-bg-opacity 1)
(set-translate-y (- max-y))
(set-parent-height max-parent-height))
(set-parent-height max-parent-height)
(set-native-props (:record-ref refs) #js {:right nil :left -1000}))
(defn clean-and-minimize
[{:keys [chat-id refs] :as params}]
@ -42,12 +46,12 @@
(-> (gesture/gesture-pan)
(gesture/on-start
(fn [_]
(if keyboard-shown
(if (and keyboard-shown (not @input/recording-audio?))
(swap! gesture-values assoc :pan-y (reanimated/get-shared-value translate-y))
(input/input-focus text-input-ref))))
(gesture/on-update
(fn [evt]
(when keyboard-shown
(when (and keyboard-shown (not @input/recording-audio?))
(let [tY (oget evt "translationY")]
(swap! gesture-values assoc :dy (- tY (:pdy @gesture-values)))
(swap! gesture-values assoc :pdy tY)
@ -56,7 +60,7 @@
(max (min (+ tY (:pan-y @gesture-values)) (- min-y)) (- max-y)))))))
(gesture/on-end
(fn [_]
(when keyboard-shown
(when (and keyboard-shown (not @input/recording-audio?))
(if (< (:dy @gesture-values) 0)
(maximize params)
(do
@ -153,8 +157,10 @@
[_ _]
(let [text-input-ref (rn/create-ref)
send-ref (rn/create-ref)
record-ref (rn/create-ref)
refs {:send-ref send-ref
:text-input-ref text-input-ref}]
:text-input-ref text-input-ref
:record-ref record-ref}]
(fn [chat-id insets]
[:f>
(fn []
@ -187,7 +193,10 @@
(+ 16
(* 46 (dec (count suggestions)))))
(+ 0
(when (or edit reply) 38)
(when (and
(or edit reply)
(not @input/recording-audio?))
38)
(when (seq images) 80))))
parent-height (reanimated/use-shared-value min-y)
@ -223,7 +232,8 @@
:refs refs}]]]]
(if suggestions?
[mentions/mentions params insets]
[controls/view send-ref params insets chat-id images #(clean-and-minimize params)])
[controls/view send-ref record-ref params insets chat-id images
edit #(clean-and-minimize params)])
;;;;black background
[reanimated/view
{:style (reanimated/apply-animations-to-style

View File

@ -25,7 +25,10 @@
{:keys [edit-enabled show-input? community? can-delete-message-for-everyone?
message-pin-enabled group-chat group-admin?]}]
(concat
(when (and outgoing edit-enabled (not (or deleted? deleted-for-me?)))
(when (and outgoing
edit-enabled
(not (or deleted? deleted-for-me?))
(not= content-type constants/content-type-audio))
[{:type :main
:on-press #(rf/dispatch [:chat.ui/edit-message message-data])
:label (i18n/label :t/edit-message)
@ -37,7 +40,9 @@
:label (i18n/label :t/message-reply)
:icon :i/reply
:id :reply}])
(when (and (not (or deleted? deleted-for-me?)) (not= (get content :text) "placeholder"))
(when (and (not (or deleted? deleted-for-me?))
(not= (get content :text) "placeholder")
(not= content-type constants/content-type-audio))
[{:type :main
:on-press #(react/copy-to-clipboard
(components.reply/get-quoted-text-with-mentions

View File

@ -8,15 +8,36 @@
[utils.i18n :as i18n]
[react-native.permissions :as permissions]))
(defonce record-audio-permission-granted (reagent/atom false))
(defn cool-preview
[]
(let [message (reagent/atom
"Press & hold the mic button to start recording...")
record-audio-permission-granted (reagent/atom false)
on-send #(reset! message (str "onSend event triggered. File path: " %))
on-start-recording #(reset! message "onStartRecording event triggered.")
on-reviewing-audio #(reset! message "onReviewingAudio event triggered.")
on-cancel #(reset! message "onCancel event triggered.")]
(let [message (reagent/atom
"Press & hold the mic button to start recording...")
on-send #(reset! message (str "onSend event triggered. File path: "
%))
on-start-recording #(reset! message "onStartRecording event triggered.")
on-reviewing-audio #(reset! message "onReviewingAudio event triggered.")
on-cancel #(reset! message "onCancel event triggered.")
on-check-audio-permissions (fn []
(permissions/permission-granted?
:record-audio
#(reset! record-audio-permission-granted %)
#(reset! record-audio-permission-granted false)))
on-request-record-audio-permission (fn []
(rf/dispatch
[:request-permissions
{:permissions [:record-audio]
:on-allowed
#(reset! record-audio-permission-granted true)
:on-denied
#(js/setTimeout
(fn []
(alert/show-popup
(i18n/label :t/audio-recorder-error)
(i18n/label
:t/audio-recorder-permissions-error)))
50)}]))]
(fn []
[rn/view
[rn/view
@ -30,25 +51,8 @@
:on-start-recording on-start-recording
:on-reviewing-audio on-reviewing-audio
:on-cancel on-cancel
:on-check-audio-permissions (fn []
(permissions/permission-granted?
:record-audio
#(reset! record-audio-permission-granted %)
#(reset! record-audio-permission-granted false)))
:on-request-record-audio-permission (fn []
(rf/dispatch
[:request-permissions
{:permissions [:record-audio]
:on-allowed
#(reset! record-audio-permission-granted true)
:on-denied
#(js/setTimeout
(fn []
(alert/show-popup
(i18n/label :t/audio-recorder-error)
(i18n/label
:t/audio-recorder-permissions-error)))
50)}]))}]]
:on-check-audio-permissions on-check-audio-permissions
:on-request-record-audio-permission on-request-record-audio-permission}]]
[quo/text {:style {:margin-horizontal 20}} @message]])))
(defn preview-record-audio

View File

@ -58,6 +58,11 @@ jest.mock('@react-native-community/audio-toolkit', () => ({
RECORDING: 4,
PAUSED: 5,
},
PlaybackCategories: {
Playback: 1,
Ambient: 2,
SoloAmbient: 3
},
}));
jest.mock("i18n-js", () => ({