expo-orb 0.1.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +209 -1
- package/android/build.gradle +2 -0
- package/android/src/main/java/expo/modules/breathing/BreathingConfiguration.kt +25 -0
- package/android/src/main/java/expo/modules/breathing/BreathingExerciseView.kt +610 -0
- package/android/src/main/java/expo/modules/breathing/BreathingSharedState.kt +108 -0
- package/android/src/main/java/expo/modules/breathing/BreathingTextCue.kt +51 -0
- package/android/src/main/java/expo/modules/breathing/ExpoBreathingExerciseModule.kt +177 -0
- package/android/src/main/java/expo/modules/breathing/ExpoBreathingExerciseView.kt +144 -0
- package/android/src/main/java/expo/modules/breathing/MorphingBlobView.kt +128 -0
- package/android/src/main/java/expo/modules/breathing/ProgressRingView.kt +50 -0
- package/build/ExpoBreathingExercise.types.d.ts +48 -0
- package/build/ExpoBreathingExercise.types.d.ts.map +1 -0
- package/build/ExpoBreathingExercise.types.js +2 -0
- package/build/ExpoBreathingExercise.types.js.map +1 -0
- package/build/ExpoBreathingExerciseModule.d.ts +16 -0
- package/build/ExpoBreathingExerciseModule.d.ts.map +1 -0
- package/build/ExpoBreathingExerciseModule.js +52 -0
- package/build/ExpoBreathingExerciseModule.js.map +1 -0
- package/build/ExpoBreathingExerciseView.d.ts +4 -0
- package/build/ExpoBreathingExerciseView.d.ts.map +1 -0
- package/build/ExpoBreathingExerciseView.js +7 -0
- package/build/ExpoBreathingExerciseView.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +3 -0
- package/build/index.js.map +1 -1
- package/expo-module.config.json +2 -2
- package/ios/Breathing/BreathingConfiguration.swift +57 -0
- package/ios/Breathing/BreathingExerciseView.swift +451 -0
- package/ios/Breathing/BreathingParticlesView.swift +81 -0
- package/ios/Breathing/BreathingSharedState.swift +84 -0
- package/ios/Breathing/BreathingTextCue.swift +14 -0
- package/ios/Breathing/MorphingBlobView.swift +242 -0
- package/ios/Breathing/ProgressRingView.swift +27 -0
- package/ios/ExpoBreathingExerciseModule.swift +182 -0
- package/ios/ExpoBreathingExerciseView.swift +124 -0
- package/package.json +8 -5
- package/src/ExpoBreathingExercise.types.ts +50 -0
- package/src/ExpoBreathingExerciseModule.ts +67 -0
- package/src/ExpoBreathingExerciseView.tsx +11 -0
- package/src/index.ts +11 -0
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Inspired by and iOS implementation from [metasidd/Orb](https://github.com/metasi
|
|
|
8
8
|
|
|
9
9
|
| iOS | Android |
|
|
10
10
|
|:---:|:-------:|
|
|
11
|
-
|  |  |
|
|
11
|
+
|  |  |
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
@@ -124,6 +124,214 @@ export default function App() {
|
|
|
124
124
|
}
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Breathing Exercise Component
|
|
130
|
+
|
|
131
|
+
A guided breathing exercise component with morphing blob animation, progress ring, and text cues. Supports custom breathing patterns and built-in presets.
|
|
132
|
+
|
|
133
|
+
### Breathing Demo
|
|
134
|
+
|
|
135
|
+
| iOS | Android |
|
|
136
|
+
|:---:|:-------:|
|
|
137
|
+
|  |  |
|
|
138
|
+
|
|
139
|
+
### Basic Usage
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import {
|
|
143
|
+
ExpoBreathingExerciseView,
|
|
144
|
+
startBreathingExercise,
|
|
145
|
+
stopBreathingExercise,
|
|
146
|
+
getBreathingPreset,
|
|
147
|
+
} from 'expo-orb';
|
|
148
|
+
|
|
149
|
+
function BreathingScreen() {
|
|
150
|
+
const handleStart = () => {
|
|
151
|
+
// Use a built-in preset
|
|
152
|
+
startBreathingExercise(getBreathingPreset('box'));
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
|
157
|
+
<ExpoBreathingExerciseView
|
|
158
|
+
style={{ width: 300, height: 300 }}
|
|
159
|
+
blobColors={['#7c3aed', '#3b82f6', '#ec4899']}
|
|
160
|
+
showProgressRing={true}
|
|
161
|
+
showTextCue={true}
|
|
162
|
+
onPhaseChange={(e) => console.log('Phase:', e.nativeEvent.label)}
|
|
163
|
+
onExerciseComplete={(e) => console.log('Done! Cycles:', e.nativeEvent.totalCycles)}
|
|
164
|
+
/>
|
|
165
|
+
<Button title="Start" onPress={handleStart} />
|
|
166
|
+
<Button title="Stop" onPress={stopBreathingExercise} />
|
|
167
|
+
</View>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Breathing Props
|
|
173
|
+
|
|
174
|
+
| Prop | Type | Default | Description |
|
|
175
|
+
|------|------|---------|-------------|
|
|
176
|
+
| `blobColors` | `ColorValue[]` | `['green', 'blue', 'pink']` | Gradient colors for the morphing blob |
|
|
177
|
+
| `innerBlobColor` | `ColorValue` | `'white'` | Color of the inner blob |
|
|
178
|
+
| `glowColor` | `ColorValue` | `'white'` | Color of the glow effects |
|
|
179
|
+
| `particleColor` | `ColorValue` | `'white'` | Color of floating particles |
|
|
180
|
+
| `progressRingColor` | `ColorValue` | `'white'` | Color of the progress ring |
|
|
181
|
+
| `textColor` | `ColorValue` | `'white'` | Color of the instruction text |
|
|
182
|
+
| `showProgressRing` | `boolean` | `true` | Show/hide the progress ring |
|
|
183
|
+
| `showTextCue` | `boolean` | `true` | Show/hide the instruction text (e.g., "Breathe In") |
|
|
184
|
+
| `showInnerBlob` | `boolean` | `true` | Show/hide the inner blob |
|
|
185
|
+
| `showShadow` | `boolean` | `true` | Show/hide drop shadow |
|
|
186
|
+
| `showParticles` | `boolean` | `true` | Show/hide floating particles |
|
|
187
|
+
| `showWavyBlobs` | `boolean` | `true` | Show/hide wavy blob overlays |
|
|
188
|
+
| `showGlowEffects` | `boolean` | `true` | Show/hide rotating glow effects |
|
|
189
|
+
| `pointCount` | `number` | `8` | Number of points for blob morphing |
|
|
190
|
+
| `wobbleIntensity` | `number` | `0.5` | Intensity of wobble animation (0-1) |
|
|
191
|
+
| `style` | `StyleProp<ViewStyle>` | - | Container style (set width/height here) |
|
|
192
|
+
|
|
193
|
+
### Breathing Events
|
|
194
|
+
|
|
195
|
+
| Event | Payload | Description |
|
|
196
|
+
|-------|---------|-------------|
|
|
197
|
+
| `onPhaseChange` | `{ phase, label, phaseIndex, cycle }` | Fired when the breathing phase changes |
|
|
198
|
+
| `onExerciseComplete` | `{ totalCycles, totalDuration }` | Fired when all cycles complete |
|
|
199
|
+
|
|
200
|
+
### Breathing Control Functions
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
import {
|
|
204
|
+
startBreathingExercise,
|
|
205
|
+
stopBreathingExercise,
|
|
206
|
+
pauseBreathingExercise,
|
|
207
|
+
resumeBreathingExercise,
|
|
208
|
+
getBreathingPreset,
|
|
209
|
+
} from 'expo-orb';
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
| Function | Description |
|
|
213
|
+
|----------|-------------|
|
|
214
|
+
| `startBreathingExercise(pattern)` | Start the exercise with a custom pattern or preset |
|
|
215
|
+
| `stopBreathingExercise()` | Stop the current exercise |
|
|
216
|
+
| `pauseBreathingExercise()` | Pause the current exercise |
|
|
217
|
+
| `resumeBreathingExercise()` | Resume a paused exercise |
|
|
218
|
+
| `getBreathingPreset(name)` | Get a built-in preset by name |
|
|
219
|
+
|
|
220
|
+
### Built-in Presets
|
|
221
|
+
|
|
222
|
+
| Preset | Pattern | Cycles | Description |
|
|
223
|
+
|--------|---------|--------|-------------|
|
|
224
|
+
| `'relaxing'` | 4s in, 7s hold, 8s out | 4 | 4-7-8 technique for relaxation |
|
|
225
|
+
| `'box'` | 4s in, 4s hold, 4s out, 4s hold | 4 | Box breathing for focus |
|
|
226
|
+
| `'energizing'` | 2s in, 2s out | 10 | Quick breathing for energy |
|
|
227
|
+
| `'calming'` | 4s in, 6s out | 6 | Extended exhale for calm |
|
|
228
|
+
|
|
229
|
+
### Custom Breathing Patterns
|
|
230
|
+
|
|
231
|
+
Create your own breathing patterns:
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
import { startBreathingExercise, BreathingPattern } from 'expo-orb';
|
|
235
|
+
|
|
236
|
+
const customPattern: BreathingPattern = {
|
|
237
|
+
phases: [
|
|
238
|
+
{ phase: 'inhale', duration: 5000, targetScale: 1.35, label: 'Breathe In' },
|
|
239
|
+
{ phase: 'holdIn', duration: 3000, targetScale: 1.35, label: 'Hold' },
|
|
240
|
+
{ phase: 'exhale', duration: 7000, targetScale: 0.75, label: 'Breathe Out' },
|
|
241
|
+
{ phase: 'holdOut', duration: 2000, targetScale: 0.75, label: 'Rest' },
|
|
242
|
+
],
|
|
243
|
+
cycles: 5, // undefined for infinite
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
startBreathingExercise(customPattern);
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Phase Types
|
|
250
|
+
|
|
251
|
+
| Phase | Description |
|
|
252
|
+
|-------|-------------|
|
|
253
|
+
| `'inhale'` | Breathing in - blob expands |
|
|
254
|
+
| `'holdIn'` | Holding breath after inhale |
|
|
255
|
+
| `'exhale'` | Breathing out - blob contracts |
|
|
256
|
+
| `'holdOut'` | Holding breath after exhale |
|
|
257
|
+
|
|
258
|
+
### Complete Breathing Example
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
import * as React from 'react';
|
|
262
|
+
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
|
|
263
|
+
import {
|
|
264
|
+
ExpoBreathingExerciseView,
|
|
265
|
+
startBreathingExercise,
|
|
266
|
+
stopBreathingExercise,
|
|
267
|
+
pauseBreathingExercise,
|
|
268
|
+
resumeBreathingExercise,
|
|
269
|
+
getBreathingPreset,
|
|
270
|
+
BreathingPreset,
|
|
271
|
+
} from 'expo-orb';
|
|
272
|
+
|
|
273
|
+
export default function BreathingApp() {
|
|
274
|
+
const [currentPhase, setCurrentPhase] = React.useState('');
|
|
275
|
+
const [isPaused, setIsPaused] = React.useState(false);
|
|
276
|
+
|
|
277
|
+
const startPreset = (preset: BreathingPreset) => {
|
|
278
|
+
setIsPaused(false);
|
|
279
|
+
startBreathingExercise(getBreathingPreset(preset));
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<View style={styles.container}>
|
|
284
|
+
<ExpoBreathingExerciseView
|
|
285
|
+
style={styles.breathingView}
|
|
286
|
+
blobColors={['#667eea', '#764ba2', '#f093fb']}
|
|
287
|
+
progressRingColor="#ffffff"
|
|
288
|
+
textColor="#ffffff"
|
|
289
|
+
showProgressRing={true}
|
|
290
|
+
showTextCue={true}
|
|
291
|
+
showParticles={true}
|
|
292
|
+
onPhaseChange={(e) => setCurrentPhase(e.nativeEvent.label)}
|
|
293
|
+
onExerciseComplete={() => setCurrentPhase('Complete!')}
|
|
294
|
+
/>
|
|
295
|
+
|
|
296
|
+
<View style={styles.presets}>
|
|
297
|
+
<TouchableOpacity onPress={() => startPreset('box')}>
|
|
298
|
+
<Text>Box</Text>
|
|
299
|
+
</TouchableOpacity>
|
|
300
|
+
<TouchableOpacity onPress={() => startPreset('relaxing')}>
|
|
301
|
+
<Text>Relaxing</Text>
|
|
302
|
+
</TouchableOpacity>
|
|
303
|
+
<TouchableOpacity onPress={() => startPreset('calming')}>
|
|
304
|
+
<Text>Calming</Text>
|
|
305
|
+
</TouchableOpacity>
|
|
306
|
+
</View>
|
|
307
|
+
|
|
308
|
+
<View style={styles.controls}>
|
|
309
|
+
<TouchableOpacity onPress={() => {
|
|
310
|
+
if (isPaused) {
|
|
311
|
+
resumeBreathingExercise();
|
|
312
|
+
} else {
|
|
313
|
+
pauseBreathingExercise();
|
|
314
|
+
}
|
|
315
|
+
setIsPaused(!isPaused);
|
|
316
|
+
}}>
|
|
317
|
+
<Text>{isPaused ? 'Resume' : 'Pause'}</Text>
|
|
318
|
+
</TouchableOpacity>
|
|
319
|
+
<TouchableOpacity onPress={stopBreathingExercise}>
|
|
320
|
+
<Text>Stop</Text>
|
|
321
|
+
</TouchableOpacity>
|
|
322
|
+
</View>
|
|
323
|
+
</View>
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const styles = StyleSheet.create({
|
|
328
|
+
container: { flex: 1, alignItems: 'center', justifyContent: 'center' },
|
|
329
|
+
breathingView: { width: 280, height: 280 },
|
|
330
|
+
presets: { flexDirection: 'row', gap: 16, marginTop: 32 },
|
|
331
|
+
controls: { flexDirection: 'row', gap: 24, marginTop: 16 },
|
|
332
|
+
});
|
|
333
|
+
```
|
|
334
|
+
|
|
127
335
|
## License
|
|
128
336
|
|
|
129
337
|
MIT
|
package/android/build.gradle
CHANGED
|
@@ -56,5 +56,7 @@ dependencies {
|
|
|
56
56
|
implementation 'androidx.compose.ui:ui-graphics'
|
|
57
57
|
implementation 'androidx.compose.foundation:foundation'
|
|
58
58
|
implementation 'androidx.compose.runtime:runtime'
|
|
59
|
+
implementation 'androidx.compose.material3:material3'
|
|
60
|
+
implementation 'androidx.compose.animation:animation'
|
|
59
61
|
implementation 'androidx.activity:activity-compose:1.8.2'
|
|
60
62
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
package expo.modules.breathing
|
|
2
|
+
|
|
3
|
+
import androidx.compose.ui.graphics.Color
|
|
4
|
+
|
|
5
|
+
data class BreathingConfiguration(
|
|
6
|
+
val blobColors: List<Color> = listOf(
|
|
7
|
+
Color(0xFF66B3E6), // Light blue
|
|
8
|
+
Color(0xFF4D80CC), // Medium blue
|
|
9
|
+
Color(0xFF804DB3) // Purple
|
|
10
|
+
),
|
|
11
|
+
val innerBlobColor: Color = Color.White.copy(alpha = 0.3f),
|
|
12
|
+
val glowColor: Color = Color.White,
|
|
13
|
+
val particleColor: Color = Color.White,
|
|
14
|
+
val progressRingColor: Color = Color.White.copy(alpha = 0.5f),
|
|
15
|
+
val textColor: Color = Color.White,
|
|
16
|
+
val showProgressRing: Boolean = true,
|
|
17
|
+
val showTextCue: Boolean = true,
|
|
18
|
+
val showInnerBlob: Boolean = true,
|
|
19
|
+
val showShadow: Boolean = true,
|
|
20
|
+
val showParticles: Boolean = true,
|
|
21
|
+
val showWavyBlobs: Boolean = true,
|
|
22
|
+
val showGlowEffects: Boolean = true,
|
|
23
|
+
val pointCount: Int = 8,
|
|
24
|
+
val wobbleIntensity: Double = 1.0
|
|
25
|
+
)
|