1
- import React , { forwardRef , useRef , type Ref } from 'react'
2
- import { Pressable , View , type StyleProp , type ViewStyle , TouchableOpacity } from 'react-native'
1
+ import React , { forwardRef , useRef , type Ref , useState } from 'react'
2
+ import {
3
+ Pressable ,
4
+ View ,
5
+ type StyleProp ,
6
+ type ViewStyle ,
7
+ TouchableOpacity ,
8
+ Text ,
9
+ type TextStyle ,
10
+ } from 'react-native'
11
+ import { Audio } from 'expo-av'
12
+ import * as Haptics from 'expo-haptics'
3
13
import Ionicons from '@expo/vector-icons/Ionicons'
4
14
import Animated , {
5
15
Extrapolation ,
6
16
interpolate ,
7
17
useAnimatedStyle ,
8
18
useSharedValue ,
19
+ withSpring ,
20
+ type WithSpringConfig ,
9
21
} from 'react-native-reanimated'
10
22
import { useSafeAreaInsets } from 'react-native-safe-area-context'
11
23
import { Recorder , type RecorderRef } from '@lodev09/expo-recorder'
@@ -14,12 +26,18 @@ import { type TrueSheetProps, TrueSheet } from '@lodev09/react-native-true-sheet
14
26
import { useThemeColor } from '@/hooks/useThemeColor'
15
27
import { Box } from './Box'
16
28
import { Spacing } from '@/constants/Spacing'
29
+ import { formatTimer } from '@/utils/formatTimer'
17
30
18
31
const RECORD_BUTTON_SIZE = 60
19
32
const RECORD_BUTTON_BACKGROUND_SIZE = RECORD_BUTTON_SIZE + Spacing . md
20
33
const RECORDING_INDICATOR_COLOR = '#d72d66'
21
34
const RECORDING_INDICATOR_SCALE = 0.5
22
35
36
+ const SPRING_SHORT_CONFIG : WithSpringConfig = {
37
+ stiffness : 120 ,
38
+ overshootClamping : true ,
39
+ }
40
+
23
41
export interface ThemedRecorderSheetProps extends TrueSheetProps {
24
42
lightColor ?: string
25
43
darkColor ?: string
@@ -33,19 +51,51 @@ export const ThemedRecorderSheet = forwardRef(
33
51
34
52
const insets = useSafeAreaInsets ( )
35
53
54
+ const [ isRecording , setIsRecording ] = useState ( false )
55
+ const [ isPlaying , setIsPlaying ] = useState ( false )
56
+ const [ position , setPosition ] = useState ( 0 )
57
+
36
58
const recorderRef = useRef < RecorderRef > ( null )
37
59
38
60
const backgroundColor = useThemeColor ( { light : lightColor , dark : darkColor } , 'recorderSheet' )
39
61
const iconColor = useThemeColor ( { } , 'recorderIcon' )
40
62
const tintColor = useThemeColor ( { } , 'recorderTint' )
41
63
const timelineColor = useThemeColor ( { } , 'recorderTimeline' )
42
- const textColor = useThemeColor ( { } , 'text' )
64
+ const positionColor = useThemeColor ( { } , 'text' )
43
65
const recordBorderColor = useThemeColor ( { light : 'rgba(0,0,0,0.3)' } , 'text' )
44
66
const recorderBackgroundColor = useThemeColor ( { } , 'recorderBackground' )
45
67
46
68
const scale = useSharedValue ( 1 )
47
69
48
- const toggleRecording = async ( ) => { }
70
+ const toggleRecording = async ( ) => {
71
+ const permissionStatus = await Audio . getPermissionsAsync ( )
72
+ if ( ! permissionStatus . granted ) return
73
+
74
+ Haptics . selectionAsync ( )
75
+ if ( isRecording ) {
76
+ await recorderRef . current ?. stopRecording ( )
77
+ } else {
78
+ await recorderRef . current ?. startRecording ( )
79
+ }
80
+ }
81
+
82
+ const resetRecording = async ( ) => {
83
+ if ( isRecording ) return
84
+
85
+ Haptics . selectionAsync ( )
86
+ await recorderRef . current ?. resetRecording ( )
87
+ }
88
+
89
+ const togglePlayback = async ( ) => {
90
+ if ( isRecording ) return
91
+
92
+ Haptics . selectionAsync ( )
93
+ if ( isPlaying ) {
94
+ await recorderRef . current ?. stopPlayback ( )
95
+ } else {
96
+ await recorderRef . current ?. startPlayback ( )
97
+ }
98
+ }
49
99
50
100
const $recordIndicatorStyles : StyleProp < ViewStyle > = [
51
101
$recordIndicator ,
@@ -72,12 +122,35 @@ export const ThemedRecorderSheet = forwardRef(
72
122
ref = { recorderRef }
73
123
tintColor = { tintColor }
74
124
timelineColor = { timelineColor }
75
- textColor = { textColor }
76
125
backgroundColor = { recorderBackgroundColor }
126
+ onRecordReset = { ( ) => {
127
+ scale . value = 1
128
+ setIsRecording ( false )
129
+ setIsPlaying ( false )
130
+ } }
131
+ onRecordStart = { ( ) => {
132
+ scale . value = withSpring ( RECORDING_INDICATOR_SCALE , SPRING_SHORT_CONFIG )
133
+ setIsRecording ( true )
134
+ } }
135
+ onRecordStop = { ( uri ) => {
136
+ scale . value = withSpring ( 1 , SPRING_SHORT_CONFIG )
137
+ setIsRecording ( false )
138
+
139
+ // Use this uri. Yay! 🎉
140
+ console . log ( uri )
141
+ } }
142
+ onPlaybackStart = { ( ) => setIsPlaying ( true ) }
143
+ onPlaybackStop = { ( ) => setIsPlaying ( false ) }
144
+ onPositionChange = { ( position : number ) => setPosition ( position ) }
77
145
/>
146
+ < View style = { { padding : Spacing . md , marginTop : Spacing . xxl } } >
147
+ < Text style = { [ $positionText , { color : positionColor ?? '#333333' } ] } >
148
+ { formatTimer ( Math . round ( position / 100 ) * 100 , true ) }
149
+ </ Text >
150
+ </ View >
78
151
< Box row justify = "space-between" align = "center" mt = { Spacing . lg } >
79
152
< Box >
80
- < TouchableOpacity activeOpacity = { 0.8 } style = { $recordControl } onPress = { ( ) => undefined } >
153
+ < TouchableOpacity activeOpacity = { 0.5 } style = { $recordControl } onPress = { resetRecording } >
81
154
< Ionicons name = "refresh" size = { Spacing . xl } style = { { color : iconColor } } />
82
155
</ TouchableOpacity >
83
156
</ Box >
@@ -88,8 +161,12 @@ export const ThemedRecorderSheet = forwardRef(
88
161
</ Pressable >
89
162
</ Box >
90
163
< Box >
91
- < TouchableOpacity activeOpacity = { 0.8 } style = { $recordControl } onPress = { ( ) => undefined } >
92
- < Ionicons name = "play" size = { Spacing . xl } style = { { color : iconColor } } />
164
+ < TouchableOpacity activeOpacity = { 0.8 } style = { $recordControl } onPress = { togglePlayback } >
165
+ < Ionicons
166
+ name = { isPlaying ? 'pause' : 'play' }
167
+ size = { Spacing . xl }
168
+ style = { { color : iconColor } }
169
+ />
93
170
</ TouchableOpacity >
94
171
</ Box >
95
172
</ Box >
@@ -124,3 +201,9 @@ const $recordIndicator: ViewStyle = {
124
201
const $recordControl : ViewStyle = {
125
202
padding : Spacing . md ,
126
203
}
204
+
205
+ const $positionText : TextStyle = {
206
+ fontWeight : 'medium' ,
207
+ fontSize : 28 ,
208
+ textAlign : 'center' ,
209
+ }
0 commit comments