Skip to content

Commit 6d02fa7

Browse files
committed
feat: add functionalities
1 parent 526ad0d commit 6d02fa7

File tree

15 files changed

+300
-147
lines changed

15 files changed

+300
-147
lines changed

example/app.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@
2929
"favicon": "./assets/images/favicon.png"
3030
},
3131
"plugins": [
32-
"expo-router"
32+
"expo-router",
33+
[
34+
"expo-av",
35+
{
36+
"microphonePermission": "Allow $(PRODUCT_NAME) to access your microphone."
37+
}
38+
]
3339
],
3440
"experiments": {
3541
"typedRoutes": true

example/app/(tabs)/_layout.tsx

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,7 @@ export default function TabLayout() {
2020
options={{
2121
title: 'Home',
2222
tabBarIcon: ({ color, focused }) => (
23-
<TabBarIcon name={focused ? 'home' : 'home-outline'} color={color} />
24-
),
25-
}}
26-
/>
27-
<Tabs.Screen
28-
name="explore"
29-
options={{
30-
title: 'Explore',
31-
tabBarIcon: ({ color, focused }) => (
32-
<TabBarIcon name={focused ? 'code-slash' : 'code-slash-outline'} color={color} />
23+
<TabBarIcon name={focused ? 'mic' : 'mic-outline'} color={color} />
3324
),
3425
}}
3526
/>

example/app/(tabs)/explore.tsx

Lines changed: 0 additions & 36 deletions
This file was deleted.

example/app/(tabs)/index.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useRef } from 'react'
22
import { Image, StyleSheet } from 'react-native'
3+
import { Audio } from 'expo-av'
34

45
import { HelloWave } from '@/components/HelloWave'
56
import ParallaxScrollView from '@/components/ParallaxScrollView'
@@ -11,7 +12,10 @@ import { ThemedRecorderSheet, type ThemedRecorderSheetRef } from '@/components/T
1112
const HomeScreen = () => {
1213
const recorderRef = useRef<ThemedRecorderSheetRef>(null)
1314

14-
const openRecorder = () => {
15+
const openRecorder = async () => {
16+
const permissionStatus = await Audio.requestPermissionsAsync()
17+
if (!permissionStatus.granted) return
18+
1519
recorderRef.current?.present()
1620
}
1721

@@ -29,7 +33,7 @@ const HomeScreen = () => {
2933
<ThemedText type="title">Expo Recorder</ThemedText>
3034
<HelloWave />
3135
</ThemedView>
32-
<ThemedButton title="Start Recording" onPress={openRecorder} />
36+
<ThemedButton title="Open Recorder" onPress={openRecorder} />
3337
<ThemedRecorderSheet ref={recorderRef} />
3438
</ParallaxScrollView>
3539
)

example/components/ThemedRecorderSheet.tsx

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
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'
313
import Ionicons from '@expo/vector-icons/Ionicons'
414
import Animated, {
515
Extrapolation,
616
interpolate,
717
useAnimatedStyle,
818
useSharedValue,
19+
withSpring,
20+
type WithSpringConfig,
921
} from 'react-native-reanimated'
1022
import { useSafeAreaInsets } from 'react-native-safe-area-context'
1123
import { Recorder, type RecorderRef } from '@lodev09/expo-recorder'
@@ -14,12 +26,18 @@ import { type TrueSheetProps, TrueSheet } from '@lodev09/react-native-true-sheet
1426
import { useThemeColor } from '@/hooks/useThemeColor'
1527
import { Box } from './Box'
1628
import { Spacing } from '@/constants/Spacing'
29+
import { formatTimer } from '@/utils/formatTimer'
1730

1831
const RECORD_BUTTON_SIZE = 60
1932
const RECORD_BUTTON_BACKGROUND_SIZE = RECORD_BUTTON_SIZE + Spacing.md
2033
const RECORDING_INDICATOR_COLOR = '#d72d66'
2134
const RECORDING_INDICATOR_SCALE = 0.5
2235

36+
const SPRING_SHORT_CONFIG: WithSpringConfig = {
37+
stiffness: 120,
38+
overshootClamping: true,
39+
}
40+
2341
export interface ThemedRecorderSheetProps extends TrueSheetProps {
2442
lightColor?: string
2543
darkColor?: string
@@ -33,19 +51,51 @@ export const ThemedRecorderSheet = forwardRef(
3351

3452
const insets = useSafeAreaInsets()
3553

54+
const [isRecording, setIsRecording] = useState(false)
55+
const [isPlaying, setIsPlaying] = useState(false)
56+
const [position, setPosition] = useState(0)
57+
3658
const recorderRef = useRef<RecorderRef>(null)
3759

3860
const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'recorderSheet')
3961
const iconColor = useThemeColor({}, 'recorderIcon')
4062
const tintColor = useThemeColor({}, 'recorderTint')
4163
const timelineColor = useThemeColor({}, 'recorderTimeline')
42-
const textColor = useThemeColor({}, 'text')
64+
const positionColor = useThemeColor({}, 'text')
4365
const recordBorderColor = useThemeColor({ light: 'rgba(0,0,0,0.3)' }, 'text')
4466
const recorderBackgroundColor = useThemeColor({}, 'recorderBackground')
4567

4668
const scale = useSharedValue(1)
4769

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+
}
4999

50100
const $recordIndicatorStyles: StyleProp<ViewStyle> = [
51101
$recordIndicator,
@@ -72,12 +122,35 @@ export const ThemedRecorderSheet = forwardRef(
72122
ref={recorderRef}
73123
tintColor={tintColor}
74124
timelineColor={timelineColor}
75-
textColor={textColor}
76125
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)}
77145
/>
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>
78151
<Box row justify="space-between" align="center" mt={Spacing.lg}>
79152
<Box>
80-
<TouchableOpacity activeOpacity={0.8} style={$recordControl} onPress={() => undefined}>
153+
<TouchableOpacity activeOpacity={0.5} style={$recordControl} onPress={resetRecording}>
81154
<Ionicons name="refresh" size={Spacing.xl} style={{ color: iconColor }} />
82155
</TouchableOpacity>
83156
</Box>
@@ -88,8 +161,12 @@ export const ThemedRecorderSheet = forwardRef(
88161
</Pressable>
89162
</Box>
90163
<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+
/>
93170
</TouchableOpacity>
94171
</Box>
95172
</Box>
@@ -124,3 +201,9 @@ const $recordIndicator: ViewStyle = {
124201
const $recordControl: ViewStyle = {
125202
padding: Spacing.md,
126203
}
204+
205+
const $positionText: TextStyle = {
206+
fontWeight: 'medium',
207+
fontSize: 28,
208+
textAlign: 'center',
209+
}

example/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"version": "1.0.0",
55
"scripts": {
66
"start": "expo start",
7+
"clean": "expo prebuild --clean",
78
"android": "expo run:android",
89
"ios": "expo run:ios",
910
"web": "expo start --web"
@@ -13,10 +14,12 @@
1314
"@lodev09/expo-recorder": "workspace:^",
1415
"@lodev09/react-native-true-sheet": "^0.11.2",
1516
"@react-navigation/native": "^6.0.2",
17+
"date-fns": "^3.6.0",
1618
"expo": "~51.0.2",
1719
"expo-av": "~14.0.4",
1820
"expo-constants": "~16.0.1",
1921
"expo-font": "~12.0.4",
22+
"expo-haptics": "~13.0.1",
2023
"expo-linking": "~6.3.1",
2124
"expo-router": "~3.5.11",
2225
"expo-splash-screen": "~0.27.4",
File renamed without changes.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lodev09/expo-recorder",
3-
"version": "0.1.0",
3+
"version": "0.0.1",
44
"description": "Audio recorder sheet for your React Native apps ⏺️",
55
"main": "lib/commonjs/index",
66
"module": "lib/module/index",
@@ -55,7 +55,6 @@
5555
"registry": "https://registry.npmjs.org/"
5656
},
5757
"dependencies": {
58-
"date-fns": "*",
5958
"expo-av": "*"
6059
},
6160
"devDependencies": {

0 commit comments

Comments
 (0)