Skip to content

Commit ed91337

Browse files
committed
feat: consolidate theme colors
1 parent f52d738 commit ed91337

16 files changed

+609
-441
lines changed

example/app/_layout.tsx

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,35 @@ import { Stack } from 'expo-router';
22
import { HapticProvider } from 'react-native-ahaps';
33
import { GestureHandlerRootView } from 'react-native-gesture-handler';
44
import { RecorderProvider } from '../src/contexts/RecorderContext';
5+
import { ThemeProvider } from '../src/contexts/ThemeContext';
56
import { KeyboardProvider } from 'react-native-keyboard-controller';
67

78
export default function RootLayout() {
89
return (
910
<HapticProvider>
1011
<KeyboardProvider>
11-
<GestureHandlerRootView style={{ flex: 1 }}>
12-
<RecorderProvider>
13-
<Stack
14-
screenOptions={{
15-
headerShown: false,
16-
contentStyle: { backgroundColor: 'transparent' },
17-
}}
18-
>
19-
<Stack.Screen name="index" />
20-
<Stack.Screen name="playground" />
21-
<Stack.Screen name="recorder" />
22-
<Stack.Screen
23-
name="import-modal"
24-
options={{
25-
presentation: 'formSheet',
12+
<ThemeProvider>
13+
<GestureHandlerRootView style={{ flex: 1 }}>
14+
<RecorderProvider>
15+
<Stack
16+
screenOptions={{
17+
headerShown: false,
18+
contentStyle: { backgroundColor: 'transparent' },
2619
}}
27-
/>
28-
</Stack>
29-
</RecorderProvider>
30-
</GestureHandlerRootView>
20+
>
21+
<Stack.Screen name="index" />
22+
<Stack.Screen name="playground" />
23+
<Stack.Screen name="recorder" />
24+
<Stack.Screen
25+
name="import-modal"
26+
options={{
27+
presentation: 'formSheet',
28+
}}
29+
/>
30+
</Stack>
31+
</RecorderProvider>
32+
</GestureHandlerRootView>
33+
</ThemeProvider>
3134
</KeyboardProvider>
3235
</HapticProvider>
3336
);

example/app/import-modal.tsx

Lines changed: 3 additions & 262 deletions
Original file line numberDiff line numberDiff line change
@@ -1,264 +1,5 @@
1-
import {
2-
View,
3-
Text,
4-
TextInput,
5-
TouchableOpacity,
6-
StyleSheet,
7-
Platform,
8-
} from 'react-native';
9-
import { router } from 'expo-router';
10-
import { useState } from 'react';
11-
import { useSafeAreaInsets } from 'react-native-safe-area-context';
12-
import type { RecordedHaptic } from '../src/types/recording';
13-
import { useRecorder } from '../src/contexts/RecorderContext';
14-
import { KeyboardAwareScrollView } from 'react-native-keyboard-controller';
15-
import { recordedHapticSchema } from '../src/schemas/recordingSchema';
16-
import { ZodError } from 'zod';
17-
import { hapticEventsToRecordingEvents } from '../src/utils/hapticPlayback';
18-
import type { HapticEvent } from 'react-native-ahaps';
1+
import { ImportModal } from '../src/screens/ImportModal';
192

20-
const getDuration = (events: HapticEvent[]) => {
21-
return events.reduce((maxTime, event) => {
22-
const eventEnd =
23-
event.type === 'continuous'
24-
? event.relativeTime + event.duration
25-
: event.relativeTime;
26-
return Math.max(maxTime, eventEnd);
27-
}, 0);
28-
};
29-
30-
export default function ImportModal() {
31-
const insets = useSafeAreaInsets();
32-
const { importRecording } = useRecorder();
33-
const [title, setTitle] = useState('');
34-
const [jsonText, setJsonText] = useState('');
35-
const [error, setError] = useState('');
36-
37-
const handleImport = () => {
38-
setError('');
39-
40-
// Validate title
41-
if (!title.trim()) {
42-
setError('Please enter a title');
43-
return;
44-
}
45-
46-
// Validate and parse JSON
47-
try {
48-
const parsed = JSON.parse(jsonText);
49-
50-
// Validate using Zod schema
51-
const validationResult = recordedHapticSchema.safeParse(parsed);
52-
53-
if (!validationResult.success) {
54-
// Format Zod errors for user-friendly display
55-
const zodError = validationResult.error;
56-
const firstError = zodError.issues[0];
57-
const errorPath = firstError?.path.join('.') || 'unknown field';
58-
const errorMessage = firstError?.message || 'Invalid format';
59-
setError(`Validation error at ${errorPath}: ${errorMessage}`);
60-
return;
61-
}
62-
63-
const { events, curves } = validationResult.data;
64-
65-
const duration = getDuration(events);
66-
67-
const recordingEvents = hapticEventsToRecordingEvents(events, curves);
68-
69-
const recording: RecordedHaptic = {
70-
id: Date.now().toString(),
71-
name: title,
72-
createdAt: Date.now(),
73-
duration,
74-
events,
75-
curves,
76-
recordingEvents,
77-
};
78-
79-
// Import the recording
80-
importRecording(recording);
81-
82-
// Navigate back
83-
router.back();
84-
} catch (err) {
85-
if (err instanceof ZodError) {
86-
setError('Invalid recording format. Please check your JSON.');
87-
} else if (err instanceof SyntaxError) {
88-
setError('Invalid JSON format. Please check your input.');
89-
} else {
90-
setError('An error occurred while importing the recording.');
91-
}
92-
console.error('Import error:', err);
93-
}
94-
};
95-
96-
const isPresented = router.canGoBack();
97-
98-
return (
99-
<KeyboardAwareScrollView
100-
style={styles.container}
101-
keyboardShouldPersistTaps="handled"
102-
>
103-
<View style={[styles.header, { paddingTop: insets.top + 16 }]}>
104-
<Text style={styles.headerTitle}>Import Recording</Text>
105-
{isPresented && (
106-
<TouchableOpacity
107-
onPress={() => router.back()}
108-
style={styles.closeButton}
109-
>
110-
<Text style={styles.closeButtonText}></Text>
111-
</TouchableOpacity>
112-
)}
113-
</View>
114-
115-
<View style={styles.content}>
116-
<View style={styles.inputGroup}>
117-
<Text style={styles.label}>Title</Text>
118-
<TextInput
119-
style={styles.titleInput}
120-
value={title}
121-
autoFocus
122-
onChangeText={setTitle}
123-
placeholder="Enter recording name"
124-
placeholderTextColor="#636366"
125-
autoCapitalize="words"
126-
/>
127-
</View>
128-
129-
<View style={styles.inputGroup}>
130-
<Text style={styles.label}>JSON Data</Text>
131-
<TextInput
132-
style={styles.jsonInput}
133-
value={jsonText}
134-
onChangeText={setJsonText}
135-
placeholder="Paste recording JSON here"
136-
placeholderTextColor="#636366"
137-
multiline
138-
textAlignVertical="top"
139-
autoCapitalize="none"
140-
autoCorrect={false}
141-
/>
142-
</View>
143-
144-
{error ? <Text style={styles.errorText}>{error}</Text> : null}
145-
146-
<TouchableOpacity
147-
style={[
148-
styles.importButton,
149-
(!title.trim() || !jsonText.trim()) && styles.importButtonDisabled,
150-
]}
151-
onPress={handleImport}
152-
disabled={!title.trim() || !jsonText.trim()}
153-
>
154-
<Text style={styles.importButtonText}>Import</Text>
155-
</TouchableOpacity>
156-
157-
<Text style={styles.hint}>
158-
Paste the JSON data exported from a recording. The recording will be
159-
added to your list.
160-
</Text>
161-
</View>
162-
</KeyboardAwareScrollView>
163-
);
3+
export default function ImportModalRoute() {
4+
return <ImportModal />;
1645
}
165-
166-
const styles = StyleSheet.create({
167-
container: {
168-
backgroundColor: '#000000',
169-
},
170-
header: {
171-
flexDirection: 'row',
172-
alignItems: 'center',
173-
justifyContent: 'center',
174-
paddingHorizontal: 16,
175-
paddingBottom: 16,
176-
borderBottomWidth: 1,
177-
borderBottomColor: '#1C1C1E',
178-
},
179-
headerTitle: {
180-
fontSize: 20,
181-
fontWeight: '700',
182-
color: '#FFFFFF',
183-
},
184-
closeButton: {
185-
position: 'absolute',
186-
right: 16,
187-
top: 16,
188-
width: 32,
189-
height: 32,
190-
borderRadius: 16,
191-
backgroundColor: '#1C1C1E',
192-
alignItems: 'center',
193-
justifyContent: 'center',
194-
},
195-
closeButtonText: {
196-
fontSize: 18,
197-
color: '#FFFFFF',
198-
fontWeight: '600',
199-
},
200-
content: {
201-
flex: 1,
202-
padding: 16,
203-
},
204-
contentContainer: {
205-
padding: 16,
206-
},
207-
inputGroup: {
208-
marginBottom: 24,
209-
},
210-
label: {
211-
fontSize: 16,
212-
fontWeight: '600',
213-
color: '#FFFFFF',
214-
marginBottom: 8,
215-
},
216-
titleInput: {
217-
backgroundColor: '#1C1C1E',
218-
borderRadius: 12,
219-
padding: 16,
220-
fontSize: 16,
221-
color: '#FFFFFF',
222-
borderWidth: 1,
223-
borderColor: '#2C2C2E',
224-
},
225-
jsonInput: {
226-
backgroundColor: '#1C1C1E',
227-
borderRadius: 12,
228-
padding: 16,
229-
fontSize: 14,
230-
color: '#FFFFFF',
231-
minHeight: 200,
232-
fontFamily: Platform.OS === 'ios' ? 'Menlo' : 'monospace',
233-
borderWidth: 1,
234-
borderColor: '#2C2C2E',
235-
height: 200,
236-
},
237-
errorText: {
238-
color: '#FF3B30',
239-
fontSize: 14,
240-
marginBottom: 16,
241-
textAlign: 'center',
242-
},
243-
importButton: {
244-
backgroundColor: '#007AFF',
245-
borderRadius: 12,
246-
padding: 16,
247-
alignItems: 'center',
248-
marginBottom: 16,
249-
},
250-
importButtonDisabled: {
251-
backgroundColor: '#1C1C1E',
252-
},
253-
importButtonText: {
254-
fontSize: 16,
255-
fontWeight: '700',
256-
color: '#FFFFFF',
257-
},
258-
hint: {
259-
fontSize: 12,
260-
color: '#8E8E93',
261-
textAlign: 'center',
262-
lineHeight: 18,
263-
},
264-
});

0 commit comments

Comments
 (0)